hyzp_ybqx-Commit001:代码刚转换好,编译通过

This commit is contained in:
WinUser01
2021-12-09 20:24:36 +08:00
commit 8c74b14e5c
800 changed files with 66912 additions and 0 deletions
@@ -0,0 +1,11 @@
package com.tencent.liteav.demo.superplayer;
public class SuperPlayerCode {
public static final int OK = 0;
public static final int NET_ERROR = 10001;
public static final int PLAY_URL_EMPTY = 20001;
public static final int LIVE_PLAY_END = 30001;
public static final int LIVE_SHIFT_FAIL = 30002;
public static final int VOD_PLAY_FAIL = 40001;
public static final int VOD_REQUEST_FILE_ID_FAIL = 40002;
}
@@ -0,0 +1,37 @@
package com.tencent.liteav.demo.superplayer;
public class SuperPlayerDef {
public enum PlayerMode {
WINDOW, // 窗口模式
FULLSCREEN, // 全屏模式
FLOAT // 悬浮窗模式
}
public enum PlayerState {
PLAYING(1), // 播放中
PAUSE(2), // 暂停中
LOADING(3), // 缓冲中
END(4); // 结束播放
final int value;
PlayerState(int value) {
this.value = value;
}
public int intValue() {
return value;
}
}
public enum PlayerType {
VOD, // 点播
LIVE, // 直播
LIVE_SHIFT // 直播会看
}
public enum Orientation {
LANDSCAPE, // 横屏
PORTRAIT // 竖屏
}
}
@@ -0,0 +1,67 @@
package com.tencent.liteav.demo.superplayer;
import com.tencent.rtmp.TXLiveConstants;
/**
* Created by yuejiaoli on 2018/7/4.
*
* 超级播放器全局配置类
*/
public class SuperPlayerGlobalConfig {
private static class Singleton {
private static SuperPlayerGlobalConfig sInstance = new SuperPlayerGlobalConfig();
}
public static SuperPlayerGlobalConfig getInstance() {
return Singleton.sInstance;
}
/**
* 默认播放填充模式 ( 默认播放模式为 自适应模式 )
*/
public int renderMode = TXLiveConstants.RENDER_MODE_ADJUST_RESOLUTION;
/**
* 播放器最大缓存个数 ( 默认缓存 5 )
*/
public int maxCacheItem = 5;
/**
* 是否启用悬浮窗 ( 默认开启 true )
*/
public boolean enableFloatWindow = true;
/**
* 是否开启硬件加速 ( 默认开启硬件加速 )
*/
public boolean enableHWAcceleration = true;
/**
* 时移域名 (修改为自己app的时移域名)
*/
public String playShiftDomain = "liteavapp.timeshift.qcloud.com";
/**
* 悬浮窗位置 ( 默认在左上角,初始化一个宽为 810,高为 540的悬浮窗口 )
*/
public TXRect floatViewRect = new TXRect(0, 0, 810, 540);
public final static class TXRect {
public int x;
public int y;
public int width;
public int height;
TXRect(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public TXRect() {
}
}
}
@@ -0,0 +1,73 @@
package com.tencent.liteav.demo.superplayer;
import com.tencent.liteav.demo.superplayer.model.entity.SuperPlayerVideoIdV2;
import java.util.List;
/**
* 超级播放器支持三种方式播放视频:
* 1. 视频 URL
* 填写视频 URL, 如需使用直播时移功能,还需填写appId
* 2. 腾讯云点播 File ID 播放
* 填写 appId 及 videoId (如果使用旧版本V2, 请填写videoIdV2)
* 3. 多码率视频播放
* 是URL播放方式扩展,可同时传入多条URL,用于进行码率切换
*/
public class SuperPlayerModel {
public int appId; // AppId 用于腾讯云点播 File ID 播放及腾讯云直播时移功能
/**
* ------------------------------------------------------------------
* 直接使用URL播放
* <p>
* 支持 RTMP、FLV、MP4、HLS 封装格式
* 使用腾讯云直播时移功能则需要填写appId
* ------------------------------------------------------------------
*/
public String url = ""; // 视频URL
/**
* ------------------------------------------------------------------
* 多码率视频 URL
* <p>
* 用于拥有多个播放地址的多清晰度视频播放
* ------------------------------------------------------------------
*/
public List<SuperPlayerURL> multiURLs;
public int playDefaultIndex; // 指定多码率情况下,默认播放的连接Index
/**
* ------------------------------------------------------------------
* 腾讯云点播 File ID 播放参数
* ------------------------------------------------------------------
*/
public SuperPlayerVideoId videoId;
/*
* 用于兼容旧版本(V2)腾讯云点播 File ID 播放参数(即将废弃,不推荐使用)
*/
@Deprecated
public SuperPlayerVideoIdV2 videoIdV2;
public String title = ""; // 视频文件名 (用于显示在UI层);使用file id播放,若未指定title,则使用FileId返回的Title;使用url播放需要指定title,否则title显示为空
public static class SuperPlayerURL {
public SuperPlayerURL(String url, String qualityName) {
this.qualityName = qualityName;
this.url = url;
}
public SuperPlayerURL() {
}
public String qualityName = "原画"; // 清晰度名称(用于显示在UI层)
public String url = ""; // 该清晰度对应的地址
}
}
@@ -0,0 +1,19 @@
package com.tencent.liteav.demo.superplayer;
/**
* Created by hans on 2019/3/25.
* 使用腾讯云fileId播放
*/
public class SuperPlayerVideoId {
public String fileId; // 腾讯云视频fileId
public String pSign; // v4 开启防盗链必填
@Override
public String toString() {
return "SuperPlayerVideoId{" +
", fileId='" + fileId + '\'' +
", pSign='" + pSign + '\'' +
'}';
}
}
@@ -0,0 +1,913 @@
package com.tencent.liteav.demo.superplayer;
import android.app.Activity;
import android.app.AppOpsManager;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Build;
import android.provider.MediaStore;
import android.provider.Settings;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.Toast;
import com.tencent.liteav.basic.log.TXCLog;
import com.tencent.liteav.demo.superplayer.model.SuperPlayer;
import com.tencent.liteav.demo.superplayer.model.SuperPlayerImpl;
import com.tencent.liteav.demo.superplayer.model.SuperPlayerObserver;
import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo;
import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo;
import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality;
import com.tencent.liteav.demo.superplayer.model.net.LogReport;
import com.tencent.liteav.demo.superplayer.model.utils.NetWatcher;
import com.tencent.liteav.demo.superplayer.ui.player.FloatPlayer;
import com.tencent.liteav.demo.superplayer.ui.player.FullScreenPlayer;
import com.tencent.liteav.demo.superplayer.ui.player.Player;
import com.tencent.liteav.demo.superplayer.ui.player.WindowPlayer;
import com.tencent.liteav.demo.superplayer.ui.view.DanmuView;
import com.tencent.rtmp.TXLivePlayer;
import com.tencent.rtmp.ui.TXCloudVideoView;
import org.leanflutter.plugins.flutter_superplayer.R;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.List;
/**
* 超级播放器view
* <p>
* 具备播放器基本功能,此外还包括横竖屏切换、悬浮窗播放、画质切换、硬件加速、倍速播放、镜像播放、手势控制等功能,同时支持直播与点播
* 使用方式极为简单,只需要在布局文件中引入并获取到该控件,通过{@link #playWithModel(SuperPlayerModel)}传入{@link SuperPlayerModel}即可实现视频播放
* <p>
* 1、播放视频{@link #playWithModel(SuperPlayerModel)}
* 2、设置回调{@link #setPlayerViewCallback(OnSuperPlayerViewCallback)}
* 3、controller回调实现{@link #mControllerCallback}
* 4、退出播放释放内存{@link #resetPlayer()}
*/
public class SuperPlayerView extends RelativeLayout {
private static final String TAG = "SuperPlayerView";
private final int OP_SYSTEM_ALERT_WINDOW = 24; // 支持TYPE_TOAST悬浮窗的最高API版本
private Context mContext;
private ViewGroup mRootView; // SuperPlayerView的根view
private TXCloudVideoView mTXCloudVideoView; // 腾讯云视频播放view
private FullScreenPlayer mFullScreenPlayer; // 全屏模式控制view
private WindowPlayer mWindowPlayer; // 窗口模式控制view
private FloatPlayer mFloatPlayer; // 悬浮窗模式控制view
private DanmuView mDanmuView; // 弹幕
private ViewGroup.LayoutParams mLayoutParamWindowMode; // 窗口播放时SuperPlayerView的布局参数
private ViewGroup.LayoutParams mLayoutParamFullScreenMode; // 全屏播放时SuperPlayerView的布局参数
private LayoutParams mVodControllerWindowParams; // 窗口controller的布局参数
private LayoutParams mVodControllerFullScreenParams; // 全屏controller的布局参数
private WindowManager mWindowManager; // 悬浮窗窗口管理器
private WindowManager.LayoutParams mWindowParams; // 悬浮窗布局参数
private OnSuperPlayerViewCallback mPlayerViewCallback; // SuperPlayerView回调
private NetWatcher mWatcher; // 网络质量监视器
private SuperPlayer mSuperPlayer;
public SuperPlayerView(Context context) {
super(context);
initialize(context);
}
public SuperPlayerView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context);
}
public SuperPlayerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context);
}
private void initialize(Context context) {
mContext = context;
initView();
initPlayer();
}
/**
* 初始化view
*/
private void initView() {
mRootView = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.superplayer_vod_view, null);
mTXCloudVideoView = (TXCloudVideoView) mRootView.findViewById(R.id.superplayer_cloud_video_view);
mFullScreenPlayer = (FullScreenPlayer) mRootView.findViewById(R.id.superplayer_controller_large);
mWindowPlayer = (WindowPlayer) mRootView.findViewById(R.id.superplayer_controller_small);
mFloatPlayer = (FloatPlayer) mRootView.findViewById(R.id.superplayer_controller_float);
mDanmuView = (DanmuView) mRootView.findViewById(R.id.superplayer_danmuku_view);
mVodControllerWindowParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
mVodControllerFullScreenParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
mFullScreenPlayer.setCallback(mControllerCallback);
mWindowPlayer.setCallback(mControllerCallback);
mFloatPlayer.setCallback(mControllerCallback);
removeAllViews();
mRootView.removeView(mDanmuView);
mRootView.removeView(mTXCloudVideoView);
mRootView.removeView(mWindowPlayer);
mRootView.removeView(mFullScreenPlayer);
mRootView.removeView(mFloatPlayer);
addView(mTXCloudVideoView);
addView(mDanmuView);
}
private void initPlayer() {
mSuperPlayer = new SuperPlayerImpl(mContext, mTXCloudVideoView);
mSuperPlayer.setObserver(mSuperPlayerObserver);
if (mSuperPlayer.getPlayerMode() == SuperPlayerDef.PlayerMode.FULLSCREEN) {
addView(mFullScreenPlayer);
mFullScreenPlayer.hide();
} else if (mSuperPlayer.getPlayerMode() == SuperPlayerDef.PlayerMode.WINDOW) {
addView(mWindowPlayer);
mWindowPlayer.hide();
}
post(new Runnable() {
@Override
public void run() {
if (mSuperPlayer.getPlayerMode() == SuperPlayerDef.PlayerMode.WINDOW) {
mLayoutParamWindowMode = getLayoutParams();
}
try {
// 依据上层Parent的LayoutParam类型来实例化一个新的fullscreen模式下的LayoutParam
Class parentLayoutParamClazz = getLayoutParams().getClass();
Constructor constructor = parentLayoutParamClazz.getDeclaredConstructor(int.class, int.class);
mLayoutParamFullScreenMode = (ViewGroup.LayoutParams) constructor.newInstance(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
} catch (Exception e) {
e.printStackTrace();
}
}
});
LogReport.getInstance().setAppName(mContext);
LogReport.getInstance().setPackageName(mContext);
if (mWatcher == null) {
mWatcher = new NetWatcher(mContext);
}
}
/**
* 播放视频
*
* @param model
*/
public void playWithModel(final SuperPlayerModel model) {
if (model.videoId != null) {
mSuperPlayer.play(model.appId, model.videoId.fileId, model.videoId.pSign);
} else if (model.videoIdV2 != null) {
} else if (model.multiURLs != null && !model.multiURLs.isEmpty()) {
mSuperPlayer.play(model.appId, model.multiURLs, model.playDefaultIndex);
} else {
mSuperPlayer.play(model.url);
}
}
/**
* 开始播放
*
* @param url 视频地址
*/
public void play(String url) {
mSuperPlayer.play(url);
}
/**
* 开始播放
*
* @param appId 腾讯云视频appId
* @param url 直播播放地址
*/
public void play(int appId, String url) {
mSuperPlayer.play(appId, url);
}
/**
* 开始播放
*
* @param appId 腾讯云视频appId
* @param fileId 腾讯云视频fileId
* @param psign 防盗链签名,开启防盗链的视频必填,非防盗链视频可不填
*/
public void play(int appId, String fileId, String psign) {
mSuperPlayer.play(appId, fileId, psign);
}
/**
* 多分辨率播放
*
* @param appId 腾讯云视频appId
* @param superPlayerURLS 不同分辨率数据
* @param defaultIndex 默认播放Index
*/
public void play(int appId, List<SuperPlayerModel.SuperPlayerURL> superPlayerURLS, int defaultIndex) {
mSuperPlayer.play(appId, superPlayerURLS, defaultIndex);
}
/**
* 更新标题
*
* @param title 视频名称
*/
private void updateTitle(String title) {
mWindowPlayer.updateTitle(title);
mFullScreenPlayer.updateTitle(title);
}
/**
* resume生命周期回调
*/
public void onResume() {
if (mDanmuView != null && mDanmuView.isPrepared() && mDanmuView.isPaused()) {
mDanmuView.resume();
}
mSuperPlayer.resume();
}
/**
* pause生命周期回调
*/
public void onPause() {
if (mDanmuView != null && mDanmuView.isPrepared()) {
mDanmuView.pause();
}
mSuperPlayer.pauseVod();
}
/**
* 重置播放器
*/
public void resetPlayer() {
if (mDanmuView != null) {
mDanmuView.release();
mDanmuView = null;
}
stopPlay();
}
/**
* 停止播放
*/
private void stopPlay() {
mSuperPlayer.stop();
if (mWatcher != null) {
mWatcher.stop();
}
}
/**
* 设置超级播放器的回掉
*
* @param callback
*/
public void setPlayerViewCallback(OnSuperPlayerViewCallback callback) {
mPlayerViewCallback = callback;
}
/**
* 控制是否全屏显示
*/
private void fullScreen(boolean isFull) {
if (getContext() instanceof Activity) {
Activity activity = (Activity) getContext();
if (isFull) {
//隐藏虚拟按键,并且全屏
View decorView = activity.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);
}
} else {
View decorView = activity.getWindow().getDecorView();
if (decorView == null) return;
if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) { // lower api
decorView.setSystemUiVisibility(View.VISIBLE);
} else if (Build.VERSION.SDK_INT >= 19) {
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
}
}
}
/**
* 初始化controller回调
*/
private Player.Callback mControllerCallback = new Player.Callback() {
@Override
public void onSwitchPlayMode(SuperPlayerDef.PlayerMode playerMode) {
if (playerMode == SuperPlayerDef.PlayerMode.FULLSCREEN) {
fullScreen(true);
} else {
fullScreen(false);
}
mFullScreenPlayer.hide();
mWindowPlayer.hide();
mFloatPlayer.hide();
//请求全屏模式
if (playerMode == SuperPlayerDef.PlayerMode.FULLSCREEN) {
if (mLayoutParamFullScreenMode == null) {
return;
}
removeView(mWindowPlayer);
addView(mFullScreenPlayer, mVodControllerFullScreenParams);
setLayoutParams(mLayoutParamFullScreenMode);
rotateScreenOrientation(SuperPlayerDef.Orientation.LANDSCAPE);
if (mPlayerViewCallback != null) {
mPlayerViewCallback.onStartFullScreenPlay();
}
} else if (playerMode == SuperPlayerDef.PlayerMode.WINDOW) {// 请求窗口模式
// 当前是悬浮窗
if (mSuperPlayer.getPlayerMode() == SuperPlayerDef.PlayerMode.FLOAT) {
try {
Context viewContext = getContext();
Intent intent = null;
if (viewContext instanceof Activity) {
intent = new Intent(viewContext, viewContext.getClass());
} else {
showToast(R.string.superplayer_float_play_fail);
return;
}
mContext.startActivity(intent);
mSuperPlayer.pause();
if (mLayoutParamWindowMode == null) {
return;
}
mWindowManager.removeView(mFloatPlayer);
mSuperPlayer.setPlayerView(mTXCloudVideoView);
mSuperPlayer.resume();
} catch (Exception e) {
e.printStackTrace();
}
} else if (mSuperPlayer.getPlayerMode() == SuperPlayerDef.PlayerMode.FULLSCREEN) { // 当前是全屏模式
if (mLayoutParamWindowMode == null) {
return;
}
removeView(mFullScreenPlayer);
addView(mWindowPlayer, mVodControllerWindowParams);
setLayoutParams(mLayoutParamWindowMode);
rotateScreenOrientation(SuperPlayerDef.Orientation.PORTRAIT);
if (mPlayerViewCallback != null) {
mPlayerViewCallback.onStopFullScreenPlay();
}
}
} else if (playerMode == SuperPlayerDef.PlayerMode.FLOAT) {//请求悬浮窗模式
TXCLog.i(TAG, "requestPlayMode Float :" + Build.MANUFACTURER);
SuperPlayerGlobalConfig prefs = SuperPlayerGlobalConfig.getInstance();
if (!prefs.enableFloatWindow) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 6.0动态申请悬浮窗权限
if (!Settings.canDrawOverlays(mContext)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse("package:" + mContext.getPackageName()));
mContext.startActivity(intent);
return;
}
} else {
if (!checkOp(mContext, OP_SYSTEM_ALERT_WINDOW)) {
showToast(R.string.superplayer_enter_setting_fail);
return;
}
}
mSuperPlayer.pause();
mWindowManager = (WindowManager) mContext.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
mWindowParams = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mWindowParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
mWindowParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mWindowParams.format = PixelFormat.TRANSLUCENT;
mWindowParams.gravity = Gravity.LEFT | Gravity.TOP;
SuperPlayerGlobalConfig.TXRect rect = prefs.floatViewRect;
mWindowParams.x = rect.x;
mWindowParams.y = rect.y;
mWindowParams.width = rect.width;
mWindowParams.height = rect.height;
try {
mWindowManager.addView(mFloatPlayer, mWindowParams);
} catch (Exception e) {
showToast(R.string.superplayer_float_play_fail);
return;
}
TXCloudVideoView videoView = mFloatPlayer.getFloatVideoView();
if (videoView != null) {
mSuperPlayer.setPlayerView(videoView);
mSuperPlayer.resume();
}
// 悬浮窗上报
LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_FLOATMOE, 0, 0);
}
mSuperPlayer.switchPlayMode(playerMode);
}
@Override
public void onBackPressed(SuperPlayerDef.PlayerMode playMode) {
switch (playMode) {
case FULLSCREEN:// 当前是全屏模式,返回切换成窗口模式
onSwitchPlayMode(SuperPlayerDef.PlayerMode.WINDOW);
break;
case WINDOW:// 当前是窗口模式,返回退出播放器
if (mPlayerViewCallback != null) {
mPlayerViewCallback.onClickSmallReturnBtn();
}
break;
case FLOAT:// 当前是悬浮窗,退出
mWindowManager.removeView(mFloatPlayer);
if (mPlayerViewCallback != null) {
mPlayerViewCallback.onClickFloatCloseBtn();
}
break;
default:
break;
}
}
@Override
public void onFloatPositionChange(int x, int y) {
mWindowParams.x = x;
mWindowParams.y = y;
mWindowManager.updateViewLayout(mFloatPlayer, mWindowParams);
}
@Override
public void onPause() {
mSuperPlayer.pause();
if (mSuperPlayer.getPlayerType() != SuperPlayerDef.PlayerType.VOD) {
if (mWatcher != null) {
mWatcher.stop();
}
}
}
@Override
public void onResume() {
if (mSuperPlayer.getPlayerState() == SuperPlayerDef.PlayerState.END) { //重播
mSuperPlayer.reStart();
} else if (mSuperPlayer.getPlayerState() == SuperPlayerDef.PlayerState.PAUSE) { //继续播放
mSuperPlayer.resume();
}
}
@Override
public void onSeekTo(int position) {
mSuperPlayer.seek(position);
}
@Override
public void onResumeLive() {
mSuperPlayer.resumeLive();
}
@Override
public void onDanmuToggle(boolean isOpen) {
if (mDanmuView != null) {
mDanmuView.toggle(isOpen);
}
}
@Override
public void onSnapshot() {
mSuperPlayer.snapshot(new TXLivePlayer.ITXSnapshotListener() {
@Override
public void onSnapshot(Bitmap bitmap) {
if (bitmap != null) {
showSnapshotWindow(bitmap);
} else {
showToast(R.string.superplayer_screenshot_fail);
}
}
});
}
@Override
public void onQualityChange(VideoQuality quality) {
mFullScreenPlayer.updateVideoQuality(quality);
mSuperPlayer.switchStream(quality);
}
@Override
public void onSpeedChange(float speedLevel) {
mSuperPlayer.setRate(speedLevel);
}
@Override
public void onMirrorToggle(boolean isMirror) {
mSuperPlayer.setMirror(isMirror);
}
@Override
public void onHWAccelerationToggle(boolean isAccelerate) {
mSuperPlayer.enableHardwareDecode(isAccelerate);
}
};
/**
* 显示截图窗口
*
* @param bmp
*/
private void showSnapshotWindow(final Bitmap bmp) {
final PopupWindow popupWindow = new PopupWindow(mContext);
popupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
popupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
View view = LayoutInflater.from(mContext).inflate(R.layout.superplayer_layout_new_vod_snap, null);
ImageView imageView = (ImageView) view.findViewById(R.id.superplayer_iv_snap);
imageView.setImageBitmap(bmp);
popupWindow.setContentView(view);
popupWindow.setOutsideTouchable(true);
popupWindow.showAtLocation(mRootView, Gravity.TOP, 1800, 300);
AsyncTask.execute(new Runnable() {
@Override
public void run() {
save2MediaStore(mContext, bmp);
}
});
postDelayed(new Runnable() {
@Override
public void run() {
popupWindow.dismiss();
}
}, 3000);
}
/**
* 旋转屏幕方向
*
* @param orientation
*/
private void rotateScreenOrientation(SuperPlayerDef.Orientation orientation) {
switch (orientation) {
case LANDSCAPE:
((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
break;
case PORTRAIT:
((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
break;
}
}
/**
* 检查悬浮窗权限
* <p>
* API <18,默认有悬浮窗权限,不需要处理。无法接收无法接收触摸和按键事件,不需要权限和无法接受触摸事件的源码分析
* API >= 19 ,可以接收触摸和按键事件
* API >=23,需要在manifest中申请权限,并在每次需要用到权限的时候检查是否已有该权限,因为用户随时可以取消掉。
* API >25,TYPE_TOAST 已经被谷歌制裁了,会出现自动消失的情况
*/
private boolean checkOp(Context context, int op) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
try {
Method method = AppOpsManager.class.getDeclaredMethod("checkOp", int.class, int.class, String.class);
return AppOpsManager.MODE_ALLOWED == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName());
} catch (Exception e) {
TXCLog.e(TAG, Log.getStackTraceString(e));
}
}
return true;
}
public void setControlViewType(String controlViewType) {
if (controlViewType.equals("without")) {
removeView(this.mFullScreenPlayer);
removeView(this.mWindowPlayer);
} else {
mControllerCallback.onSwitchPlayMode(mSuperPlayer.getPlayerMode());
}
}
public void uiHideDanmu() {
mFullScreenPlayer.hideDanmu();
}
public void uiHideReplay() {
mWindowPlayer.hideReplay();
mFullScreenPlayer.hideReplay();
}
public SuperPlayer getSuperPlayer() {
return mSuperPlayer;
}
public Player.Callback getControllerCallback() {
return mControllerCallback;
}
/**
* SuperPlayerView的回调接口
*/
public interface OnSuperPlayerViewCallback {
/**
* 开始全屏播放
*/
void onStartFullScreenPlay();
/**
* 结束全屏播放
*/
void onStopFullScreenPlay();
/**
* 点击悬浮窗模式下的x按钮
*/
void onClickFloatCloseBtn();
/**
* 点击小播放模式的返回按钮
*/
void onClickSmallReturnBtn();
/**
* 开始悬浮窗播放
*/
void onStartFloatWindowPlay();
/**
* 播放状态发生变化
*/
void onPlayStateChange(SuperPlayerDef.PlayerState playerState);
/**
* 播放进度发生变化
*/
void onPlayProgressChange(long current, long duration);
}
public void release() {
if (mWindowPlayer != null) {
mWindowPlayer.release();
}
if (mFullScreenPlayer != null) {
mFullScreenPlayer.release();
}
if (mFloatPlayer != null) {
mFloatPlayer.release();
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
try {
release();
} catch (Throwable e) {
TXCLog.e(TAG, Log.getStackTraceString(e));
}
}
public void switchPlayMode(SuperPlayerDef.PlayerMode playerMode) {
if (playerMode == SuperPlayerDef.PlayerMode.WINDOW) {
if (mControllerCallback != null) {
mControllerCallback.onSwitchPlayMode(SuperPlayerDef.PlayerMode.WINDOW);
}
} else if (playerMode == SuperPlayerDef.PlayerMode.FLOAT) {
if (mPlayerViewCallback != null) {
mPlayerViewCallback.onStartFloatWindowPlay();
}
if (mControllerCallback != null) {
mControllerCallback.onSwitchPlayMode(SuperPlayerDef.PlayerMode.FLOAT);
}
}
}
public SuperPlayerDef.PlayerMode getPlayerMode() {
return mSuperPlayer.getPlayerMode();
}
public SuperPlayerDef.PlayerState getPlayerState() {
return mSuperPlayer.getPlayerState();
}
public float getPlayerRate() {
return mSuperPlayer.getPlayerRate();
}
private SuperPlayerObserver mSuperPlayerObserver = new SuperPlayerObserver() {
@Override
public void onPlayBegin(String name) {
mPlayerViewCallback.onPlayStateChange(SuperPlayerDef.PlayerState.PLAYING);
mWindowPlayer.updatePlayState(SuperPlayerDef.PlayerState.PLAYING);
mFullScreenPlayer.updatePlayState(SuperPlayerDef.PlayerState.PLAYING);
updateTitle(name);
mWindowPlayer.hideBackground();
if (mDanmuView != null && mDanmuView.isPrepared() && mDanmuView.isPaused()) {
mDanmuView.resume();
}
if (mWatcher != null) {
mWatcher.exitLoading();
}
}
@Override
public void onPlayPause() {
mPlayerViewCallback.onPlayStateChange(SuperPlayerDef.PlayerState.PAUSE);
mWindowPlayer.updatePlayState(SuperPlayerDef.PlayerState.PAUSE);
mFullScreenPlayer.updatePlayState(SuperPlayerDef.PlayerState.PAUSE);
}
@Override
public void onPlayStop() {
mPlayerViewCallback.onPlayStateChange(SuperPlayerDef.PlayerState.END);
mWindowPlayer.updatePlayState(SuperPlayerDef.PlayerState.END);
mFullScreenPlayer.updatePlayState(SuperPlayerDef.PlayerState.END);
// 清空关键帧和视频打点信息
if (mWatcher != null) {
mWatcher.stop();
}
}
@Override
public void onPlayLoading() {
mPlayerViewCallback.onPlayStateChange(SuperPlayerDef.PlayerState.LOADING);
mWindowPlayer.updatePlayState(SuperPlayerDef.PlayerState.LOADING);
mFullScreenPlayer.updatePlayState(SuperPlayerDef.PlayerState.LOADING);
if (mWatcher != null) {
mWatcher.enterLoading();
}
}
@Override
public void onPlayProgress(long current, long duration) {
mPlayerViewCallback.onPlayProgressChange(current, duration);
mWindowPlayer.updateVideoProgress(current, duration);
mFullScreenPlayer.updateVideoProgress(current, duration);
}
@Override
public void onSeek(int position) {
if (mSuperPlayer.getPlayerType() != SuperPlayerDef.PlayerType.VOD) {
if (mWatcher != null) {
mWatcher.stop();
}
}
}
@Override
public void onSwitchStreamStart(boolean success, SuperPlayerDef.PlayerType playerType, VideoQuality quality) {
if (playerType == SuperPlayerDef.PlayerType.LIVE) {
if (success) {
Toast.makeText(mContext, "正在切换到" + quality.title + "...", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(mContext, "切换" + quality.title + "清晰度失败,请稍候重试", Toast.LENGTH_SHORT).show();
}
}
}
@Override
public void onSwitchStreamEnd(boolean success, SuperPlayerDef.PlayerType playerType, VideoQuality quality) {
if (playerType == SuperPlayerDef.PlayerType.LIVE) {
if (success) {
Toast.makeText(mContext, "清晰度切换成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(mContext, "清晰度切换失败", Toast.LENGTH_SHORT).show();
}
}
}
@Override
public void onPlayerTypeChange(SuperPlayerDef.PlayerType playType) {
mWindowPlayer.updatePlayType(playType);
mFullScreenPlayer.updatePlayType(playType);
}
@Override
public void onPlayTimeShiftLive(TXLivePlayer player, String url) {
if (mWatcher == null) {
mWatcher = new NetWatcher(mContext);
}
mWatcher.start(url, player);
}
@Override
public void onVideoQualityListChange(List<VideoQuality> videoQualities, VideoQuality defaultVideoQuality) {
mFullScreenPlayer.setVideoQualityList(videoQualities);
mFullScreenPlayer.updateVideoQuality(defaultVideoQuality);
}
@Override
public void onVideoImageSpriteAndKeyFrameChanged(PlayImageSpriteInfo info, List<PlayKeyFrameDescInfo> list) {
mFullScreenPlayer.updateImageSpriteInfo(info);
mFullScreenPlayer.updateKeyFrameDescInfo(list);
}
@Override
public void onError(int code, String message) {
showToast(message);
}
};
private void showToast(String message) {
Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
}
private void showToast(int resId) {
Toast.makeText(mContext, resId, Toast.LENGTH_SHORT).show();
}
public static void save2MediaStore(Context context, Bitmap image) {
File sdcardDir = context.getExternalFilesDir(null);
if (sdcardDir == null) {
Log.e(TAG, "sdcardDir is null");
return;
}
File appDir = new File(sdcardDir, "superplayer");
if (!appDir.exists()) {
appDir.mkdir();
}
long dateSeconds = System.currentTimeMillis() / 1000;
String fileName = dateSeconds + ".jpg";
File file = new File(appDir, fileName);
String filePath = file.getAbsolutePath();
File f = new File(filePath);
if (f.exists()) {
f.delete();
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(f);
image.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
// Save the screenshot to the MediaStore
ContentValues values = new ContentValues();
ContentResolver resolver = context.getContentResolver();
values.put(MediaStore.Images.ImageColumns.DATA, filePath);
values.put(MediaStore.Images.ImageColumns.TITLE, fileName);
values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, fileName);
values.put(MediaStore.Images.ImageColumns.DATE_ADDED, dateSeconds);
values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, dateSeconds);
values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/jpeg");
values.put(MediaStore.Images.ImageColumns.WIDTH, image.getWidth());
values.put(MediaStore.Images.ImageColumns.HEIGHT, image.getHeight());
Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
OutputStream out = resolver.openOutputStream(uri);
image.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.flush();
out.close();
// update file size in the database
values.clear();
values.put(MediaStore.Images.ImageColumns.SIZE, new File(filePath).length());
resolver.update(uri, values, null, null);
} catch (Exception e) {
TXCLog.e(TAG, Log.getStackTraceString(e));
}
}
}
@@ -0,0 +1,148 @@
package com.tencent.liteav.demo.superplayer.model;
import com.tencent.liteav.demo.superplayer.SuperPlayerDef;
import com.tencent.liteav.demo.superplayer.SuperPlayerModel;
import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality;
import com.tencent.rtmp.TXLivePlayer;
import com.tencent.rtmp.ui.TXCloudVideoView;
import java.util.List;
public interface SuperPlayer {
/**
* 开始播放
*
* @param url 视频地址
*/
void play(String url);
/**
* 开始播放
*
* @param appId 腾讯云视频appId
* @param url 直播播放地址
*/
void play(int appId, String url);
/**
* 开始播放
*
* @param appId 腾讯云视频appId
* @param fileId 腾讯云视频fileId
* @param psign 防盗链签名,开启防盗链的视频必填,非防盗链视频可不填
*/
void play(int appId, String fileId, String psign);
/**
* 多分辨率播放
* @param appId 腾讯云视频appId
* @param superPlayerURLS 不同分辨率数据
* @param defaultIndex 默认播放Index
*/
void play(int appId, List<SuperPlayerModel.SuperPlayerURL> superPlayerURLS, int defaultIndex);
/**
* 重播
*/
void reStart();
/**
* 暂停播放
*/
void pause();
/**
* 暂停点播视频
*/
void pauseVod();
/**
* 恢复播放
*/
void resume();
/**
* 恢复直播播放,从直播时移播放中,恢复到直播播放。
*/
void resumeLive();
/**
* 停止播放
*/
void stop();
/**
* 销毁播放器
*/
void destroy();
/**
* 切换播放器模式
*
* @param playerMode {@link SuperPlayerDef.PlayerMode#WINDOW } 窗口模式
* {@link SuperPlayerDef.PlayerMode#FULLSCREEN } 全屏模式
* {@link SuperPlayerDef.PlayerMode#FLOAT } 悬浮窗模式
*/
void switchPlayMode(SuperPlayerDef.PlayerMode playerMode);
void enableHardwareDecode(boolean enable);
void setPlayerView(TXCloudVideoView videoView);
void seek(int position);
void snapshot(TXLivePlayer.ITXSnapshotListener listener);
void setRate(float speedLevel);
void setMirror(boolean isMirror);
void switchStream(VideoQuality quality);
void setLoop(boolean isLoop);
String getPlayURL();
/**
* 获取当前播放器模式
*
* @return {@link SuperPlayerDef.PlayerMode#WINDOW } 窗口模式
* {@link SuperPlayerDef.PlayerMode#FULLSCREEN } 全屏模式
* {@link SuperPlayerDef.PlayerMode#FLOAT } 悬浮窗模式
*/
SuperPlayerDef.PlayerMode getPlayerMode();
/**
* 获取当前播放器状态
*
* @return {@link SuperPlayerDef.PlayerState#PLAYING } 播放中
* {@link SuperPlayerDef.PlayerState#PAUSE } 暂停中
* {@link SuperPlayerDef.PlayerState#LOADING } 缓冲中
* {@link SuperPlayerDef.PlayerState#END } 结束播放
*/
SuperPlayerDef.PlayerState getPlayerState();
/**
* 获取当前播放器类型
*
* @return {@link SuperPlayerDef.PlayerType#LIVE } 直播
* {@link SuperPlayerDef.PlayerType#LIVE_SHIFT } 直播时移
* {@link SuperPlayerDef.PlayerType#VOD } 点播
*/
SuperPlayerDef.PlayerType getPlayerType();
/**
* 获取当前播放器速率
*
*/
float getPlayerRate();
/**
* 设置播放器状态回调
*
* @param observer {@link SuperPlayerObserver}
*/
void setObserver(SuperPlayerObserver observer);
}
@@ -0,0 +1,914 @@
package com.tencent.liteav.demo.superplayer.model;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import com.tencent.liteav.basic.log.TXCLog;
import com.tencent.liteav.demo.superplayer.SuperPlayerCode;
import com.tencent.liteav.demo.superplayer.SuperPlayerDef;
import com.tencent.liteav.demo.superplayer.SuperPlayerGlobalConfig;
import com.tencent.liteav.demo.superplayer.SuperPlayerModel;
import com.tencent.liteav.demo.superplayer.SuperPlayerVideoId;
import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo;
import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo;
import com.tencent.liteav.demo.superplayer.model.entity.ResolutionName;
import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality;
import com.tencent.liteav.demo.superplayer.model.net.LogReport;
import com.tencent.liteav.demo.superplayer.model.protocol.IPlayInfoProtocol;
import com.tencent.liteav.demo.superplayer.model.protocol.IPlayInfoRequestCallback;
import com.tencent.liteav.demo.superplayer.model.protocol.PlayInfoParams;
import com.tencent.liteav.demo.superplayer.model.protocol.PlayInfoProtocolV2;
import com.tencent.liteav.demo.superplayer.model.protocol.PlayInfoProtocolV4;
import com.tencent.liteav.demo.superplayer.model.utils.VideoQualityUtils;
import com.tencent.rtmp.ITXLivePlayListener;
import com.tencent.rtmp.ITXVodPlayListener;
import com.tencent.rtmp.TXBitrateItem;
import com.tencent.rtmp.TXLiveBase;
import com.tencent.rtmp.TXLiveConstants;
import com.tencent.rtmp.TXLivePlayConfig;
import com.tencent.rtmp.TXLivePlayer;
import com.tencent.rtmp.TXVodPlayConfig;
import com.tencent.rtmp.TXVodPlayer;
import com.tencent.rtmp.ui.TXCloudVideoView;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SuperPlayerImpl implements SuperPlayer, ITXVodPlayListener, ITXLivePlayListener {
private static final String TAG = "SuperPlayerImpl";
private static final int SUPERPLAYER_MODE = 1;
private static final int SUPPORT_MAJOR_VERSION = 8;
private static final int SUPPORT_MINOR_VERSION = 5;
private Context mContext;
private TXCloudVideoView mVideoView; // 腾讯云视频播放view
private IPlayInfoProtocol mCurrentProtocol; // 当前视频信息协议类
private TXVodPlayer mVodPlayer; // 点播播放器
private TXVodPlayConfig mVodPlayConfig; // 点播播放器配置
private TXLivePlayer mLivePlayer; // 直播播放器
private TXLivePlayConfig mLivePlayConfig; // 直播播放器配置
private SuperPlayerModel mCurrentModel; // 当前播放的model
private SuperPlayerObserver mObserver;
private VideoQuality mVideoQuality;
private SuperPlayerDef.PlayerType mCurrentPlayType = SuperPlayerDef.PlayerType.VOD; // 当前播放类型
private SuperPlayerDef.PlayerMode mCurrentPlayMode = SuperPlayerDef.PlayerMode.WINDOW; // 当前播放模式
private SuperPlayerDef.PlayerState mCurrentPlayState = SuperPlayerDef.PlayerState.PLAYING; // 当前播放状态
private float mCurrentPlayRate = 1; // 当前播放速率
private String mCurrentPlayVideoURL; // 当前播放的URL
private int mSeekPos; // 记录切换硬解时的播放时间
private long mReportLiveStartTime = -1; // 直播开始时间,用于上报使用时长
private long mReportVodStartTime = -1; // 点播开始时间,用于上报使用时长
private long mMaxLiveProgressTime; // 观看直播的最大时长
private boolean mIsMultiBitrateStream; // 是否是多码流url播放
private boolean mIsPlayWithFileId; // 是否是腾讯云fileId播放
private boolean mDefaultQualitySet; // 标记播放多码流url时是否设置过默认画质
private boolean mChangeHWAcceleration; // 切换硬解后接收到第一个关键帧前的标记位
private String mFileId;
private int mAppId;
public SuperPlayerImpl(Context context, TXCloudVideoView videoView) {
initialize(context, videoView);
}
/**
* 直播播放器事件回调
*
* @param event
* @param param
*/
@Override
public void onPlayEvent(int event, Bundle param) {
if (event != TXLiveConstants.PLAY_EVT_PLAY_PROGRESS) {
String playEventLog = "TXLivePlayer onPlayEvent event: " + event + ", " + param.getString(TXLiveConstants.EVT_DESCRIPTION);
TXCLog.d(TAG, playEventLog);
}
switch (event) {
case TXLiveConstants.PLAY_EVT_VOD_PLAY_PREPARED: //视频播放开始
case TXLiveConstants.PLAY_EVT_PLAY_BEGIN:
updatePlayerState(SuperPlayerDef.PlayerState.PLAYING);
break;
case TXLiveConstants.PLAY_ERR_NET_DISCONNECT:
case TXLiveConstants.PLAY_EVT_PLAY_END:
if (mCurrentPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) { // 直播时移失败,返回直播
mLivePlayer.resumeLive();
updatePlayerType(SuperPlayerDef.PlayerType.LIVE);
onError(SuperPlayerCode.LIVE_SHIFT_FAIL, "时移失败,返回直播");
updatePlayerState(SuperPlayerDef.PlayerState.PLAYING);
} else {
stop();
updatePlayerState(SuperPlayerDef.PlayerState.END);
if (event == TXLiveConstants.PLAY_ERR_NET_DISCONNECT) {
onError(SuperPlayerCode.NET_ERROR, "网络不给力,点击重试");
} else {
onError(SuperPlayerCode.LIVE_PLAY_END, param.getString(TXLiveConstants.EVT_DESCRIPTION));
}
}
break;
case TXLiveConstants.PLAY_EVT_PLAY_LOADING:
// case TXLiveConstants.PLAY_WARNING_RECONNECT: //暂时去掉,回调该状态时,播放画面可能是正常的,loading 状态只在 TXLiveConstants.PLAY_EVT_PLAY_LOADING 处理
updatePlayerState(SuperPlayerDef.PlayerState.LOADING);
break;
case TXLiveConstants.PLAY_EVT_RCV_FIRST_I_FRAME:
break;
case TXLiveConstants.PLAY_EVT_STREAM_SWITCH_SUCC:
updateStreamEndStatus(true, SuperPlayerDef.PlayerType.LIVE, mVideoQuality);
break;
case TXLiveConstants.PLAY_ERR_STREAM_SWITCH_FAIL:
updateStreamEndStatus(false, SuperPlayerDef.PlayerType.LIVE, mVideoQuality);
break;
case TXLiveConstants.PLAY_EVT_PLAY_PROGRESS:
int progress = param.getInt(TXLiveConstants.EVT_PLAY_PROGRESS_MS);
mMaxLiveProgressTime = progress > mMaxLiveProgressTime ? progress : mMaxLiveProgressTime;
updatePlayProgress(progress / 1000, mMaxLiveProgressTime / 1000);
break;
default:
break;
}
}
/**
* 直播播放器网络状态回调
*
* @param bundle
*/
@Override
public void onNetStatus(Bundle bundle) {
}
/**
* 点播播放器事件回调
*
* @param player
* @param event
* @param param
*/
@Override
public void onPlayEvent(TXVodPlayer player, int event, Bundle param) {
if (event != TXLiveConstants.PLAY_EVT_PLAY_PROGRESS) {
String playEventLog = "TXVodPlayer onPlayEvent event: " + event + ", " + param.getString(TXLiveConstants.EVT_DESCRIPTION);
TXCLog.d(TAG, playEventLog);
}
switch (event) {
case TXLiveConstants.PLAY_EVT_VOD_PLAY_PREPARED://视频播放开始
updatePlayerState(SuperPlayerDef.PlayerState.PLAYING);
if (mIsMultiBitrateStream) {
List<TXBitrateItem> bitrateItems = mVodPlayer.getSupportedBitrates();
if (bitrateItems == null || bitrateItems.size() == 0) {
return;
}
Collections.sort(bitrateItems); //masterPlaylist多清晰度,按照码率排序,从低到高
List<VideoQuality> videoQualities = new ArrayList<>();
int size = bitrateItems.size();
List<ResolutionName> resolutionNames = (mCurrentProtocol != null) ? mCurrentProtocol.getResolutionNameList() : null;
for (int i = 0; i < size; i++) {
TXBitrateItem bitrateItem = bitrateItems.get(i);
VideoQuality quality;
if (resolutionNames != null) {
quality = VideoQualityUtils.convertToVideoQuality(bitrateItem, mCurrentProtocol.getResolutionNameList());
} else {
quality = VideoQualityUtils.convertToVideoQuality(bitrateItem, i);
}
videoQualities.add(quality);
}
if (!mDefaultQualitySet) {
mVodPlayer.setBitrateIndex(bitrateItems.get(bitrateItems.size() - 1).index); //默认播放码率最高的
mDefaultQualitySet = true;
}
updateVideoQualityList(videoQualities, null);
}
break;
case TXLiveConstants.PLAY_EVT_RCV_FIRST_I_FRAME:
if (mChangeHWAcceleration) { //切换软硬解码器后,重新seek位置
TXCLog.i(TAG, "seek pos:" + mSeekPos);
seek(mSeekPos);
mChangeHWAcceleration = false;
}
break;
case TXLiveConstants.PLAY_EVT_PLAY_END:
updatePlayerState(SuperPlayerDef.PlayerState.END);
break;
case TXLiveConstants.PLAY_EVT_PLAY_PROGRESS:
int progress = param.getInt(TXLiveConstants.EVT_PLAY_PROGRESS_MS);
int duration = param.getInt(TXLiveConstants.EVT_PLAY_DURATION_MS);
updatePlayProgress(progress / 1000, duration / 1000);
break;
case TXLiveConstants.PLAY_EVT_PLAY_BEGIN:
updatePlayerState(SuperPlayerDef.PlayerState.PLAYING);
break;
default:
break;
}
if (event < 0) {// 播放点播文件失败
mVodPlayer.stopPlay(true);
updatePlayerState(SuperPlayerDef.PlayerState.PAUSE);
onError(SuperPlayerCode.VOD_PLAY_FAIL, param.getString(TXLiveConstants.EVT_DESCRIPTION));
}
}
/**
* 点播播放器网络状态回调
*
* @param player
* @param bundle
*/
@Override
public void onNetStatus(TXVodPlayer player, Bundle bundle) {
}
private void initialize(Context context, TXCloudVideoView videoView) {
mContext = context;
mVideoView = videoView;
initLivePlayer(mContext);
initVodPlayer(mContext);
}
/**
* 初始化点播播放器
*
* @param context
*/
private void initVodPlayer(Context context) {
mVodPlayer = new TXVodPlayer(context);
SuperPlayerGlobalConfig config = SuperPlayerGlobalConfig.getInstance();
mVodPlayConfig = new TXVodPlayConfig();
File sdcardDir = context.getExternalFilesDir(null);
if (sdcardDir != null) {
mVodPlayConfig.setCacheFolderPath(sdcardDir.getPath() + "/txcache");
}
mVodPlayConfig.setMaxCacheItems(config.maxCacheItem);
mVodPlayer.setConfig(mVodPlayConfig);
mVodPlayer.setRenderMode(config.renderMode);
mVodPlayer.setVodListener(this);
mVodPlayer.enableHardwareDecode(config.enableHWAcceleration);
}
/**
* 初始化直播播放器
*
* @param context
*/
private void initLivePlayer(Context context) {
mLivePlayer = new TXLivePlayer(context);
SuperPlayerGlobalConfig config = SuperPlayerGlobalConfig.getInstance();
mLivePlayConfig = new TXLivePlayConfig();
mLivePlayer.setConfig(mLivePlayConfig);
mLivePlayer.setRenderMode(config.renderMode);
mLivePlayer.setRenderRotation(TXLiveConstants.RENDER_ROTATION_PORTRAIT);
mLivePlayer.setPlayListener(this);
mLivePlayer.enableHardwareDecode(config.enableHWAcceleration);
}
/**
* 播放视频
*
* @param model
*/
public void playWithModel(final SuperPlayerModel model) {
mCurrentModel = model;
stop();
PlayInfoParams params = new PlayInfoParams();
params.appId = model.appId;
if (model.videoId != null) {
params.fileId = model.videoId.fileId;
params.videoId = model.videoId;
mCurrentProtocol = new PlayInfoProtocolV4(params);
} else if (model.videoIdV2 != null) {
params.fileId = model.videoIdV2.fileId;
params.videoIdV2 = model.videoIdV2;
mCurrentProtocol = new PlayInfoProtocolV2(params);
} else {
mCurrentProtocol = null; // 当前播放的是非v2和v4协议视频,将其置空
}
mFileId = params.fileId;
mAppId = params.appId;
updateVideoImageSpriteAndKeyFrame(null, null);
if (model.videoId != null || model.videoIdV2 != null) { // 根据FileId播放
mCurrentProtocol.sendRequest(new IPlayInfoRequestCallback() {
@Override
public void onSuccess(IPlayInfoProtocol protocol, PlayInfoParams param) {
TXCLog.i(TAG, "onSuccess: protocol params = " + param.toString());
mReportVodStartTime = System.currentTimeMillis();
mVodPlayer.setPlayerView(mVideoView);
playModeVideo(mCurrentProtocol);
updatePlayerType(SuperPlayerDef.PlayerType.VOD);
updatePlayProgress(0, 0);
updateVideoImageSpriteAndKeyFrame(mCurrentProtocol.getImageSpriteInfo(), mCurrentProtocol.getKeyFrameDescInfo());
}
@Override
public void onError(int errCode, String message) {
TXCLog.i(TAG, "onFail: errorCode = " + errCode + " message = " + message);
SuperPlayerImpl.this.onError(SuperPlayerCode.VOD_REQUEST_FILE_ID_FAIL, "播放视频文件失败 code = " + errCode + " msg = " + message);
}
});
} else { // 根据URL播放
String videoURL = null;
List<VideoQuality> videoQualities = new ArrayList<>();
VideoQuality defaultVideoQuality = null;
if (model.multiURLs != null && !model.multiURLs.isEmpty()) {// 多码率URL播放
int i = 0;
for (SuperPlayerModel.SuperPlayerURL superPlayerURL : model.multiURLs) {
if (i == model.playDefaultIndex) {
videoURL = superPlayerURL.url;
}
videoQualities.add(new VideoQuality(i++, superPlayerURL.qualityName, superPlayerURL.url));
}
defaultVideoQuality = videoQualities.get(model.playDefaultIndex);
} else if (!TextUtils.isEmpty(model.url)) { // 传统URL模式播放
videoURL = model.url;
}
if (TextUtils.isEmpty(videoURL)) {
onError(SuperPlayerCode.PLAY_URL_EMPTY, "播放视频失败,播放链接为空");
return;
}
if (isRTMPPlay(videoURL)) { // 直播播放器:普通RTMP流播放
mReportLiveStartTime = System.currentTimeMillis();
mLivePlayer.setPlayerView(mVideoView);
playLiveURL(videoURL, TXLivePlayer.PLAY_TYPE_LIVE_RTMP);
} else if (isFLVPlay(videoURL)) { // 直播播放器:直播FLV流播放
mReportLiveStartTime = System.currentTimeMillis();
mLivePlayer.setPlayerView(mVideoView);
playTimeShiftLiveURL(model.appId, videoURL);
if (model.multiURLs != null && !model.multiURLs.isEmpty()) {
startMultiStreamLiveURL(videoURL);
}
} else { // 点播播放器:播放点播文件
mReportVodStartTime = System.currentTimeMillis();
mVodPlayer.setPlayerView(mVideoView);
playVodURL(videoURL);
}
boolean isLivePlay = (isRTMPPlay(videoURL) || isFLVPlay(videoURL));
updatePlayerType(isLivePlay ? SuperPlayerDef.PlayerType.LIVE : SuperPlayerDef.PlayerType.VOD);
updatePlayProgress(0, 0);
updateVideoQualityList(videoQualities, defaultVideoQuality);
}
}
/**
* 播放v2或v4协议视频
*
* @param protocol
*/
private void playModeVideo(IPlayInfoProtocol protocol) {
playVodURL(protocol.getUrl());
List<VideoQuality> videoQualities = protocol.getVideoQualityList();
mIsMultiBitrateStream = videoQualities == null;
VideoQuality defaultVideoQuality = protocol.getDefaultVideoQuality();
updateVideoQualityList(videoQualities, defaultVideoQuality);
}
/**
* 播放非v2和v4协议视频
*
* @param model
*/
private void playModeVideo(SuperPlayerModel model) {
if (model.multiURLs != null && !model.multiURLs.isEmpty()) {// 多码率URL播放
for (int i = 0; i < model.multiURLs.size(); i++) {
if (i == model.playDefaultIndex) {
playVodURL(model.multiURLs.get(i).url);
}
}
} else if (!TextUtils.isEmpty(model.url)) {
playVodURL(model.url);
}
}
/**
* 播放直播URL
*/
private void playLiveURL(String url, int playType) {
mCurrentPlayVideoURL = url;
if (mLivePlayer != null) {
mLivePlayer.setPlayListener(this);
int result = mLivePlayer.startPlay(url, playType); // result返回值:0 success; -1 empty url; -2 invalid url; -3 invalid playType;
if (result != 0) {
TXCLog.e(TAG, "playLiveURL videoURL:" + url + ",result:" + result);
} else {
updatePlayerState(SuperPlayerDef.PlayerState.PLAYING);
}
}
}
/**
* 播放点播url
*/
private void playVodURL(String url) {
if (url == null || "".equals(url)) {
return;
}
mCurrentPlayVideoURL = url;
if (url.contains(".m3u8")) {
mIsMultiBitrateStream = true;
}
if (mVodPlayer != null) {
mDefaultQualitySet = false;
mVodPlayer.setStartTime(0);
mVodPlayer.setAutoPlay(true);
mVodPlayer.setVodListener(this);
String drmType = "plain";
if (mCurrentProtocol != null) {
TXCLog.d(TAG, "TOKEN: " + mCurrentProtocol.getToken());
mVodPlayer.setToken(mCurrentProtocol.getToken());
String type = mCurrentProtocol.getDRMType();
if (type!=null && !type.isEmpty()) {
drmType = type;
}
} else {
mVodPlayer.setToken(null);
}
int ret = 0;
if (isVersionSupportAppendUrl()) {
Uri uri = Uri.parse(url);
String query = uri.getQuery();
if(query==null || query.isEmpty()) {
query = "";
} else {
query = query + "&";
if (query.contains("spfileid") || query.contains("spdrmtype") || query.contains("spappid")) {
TXCLog.e(TAG, "url contains superplay key. " + query);
}
}
query += "spfileid=" + mFileId + "&spdrmtype=" + drmType + "&spappid=" + mAppId;
Uri newUri = uri.buildUpon().query(query).build();
TXCLog.i(TAG, "playVodURL: newurl = " + Uri.decode(newUri.toString()) + " ;url= " + url);
ret = mVodPlayer.startPlay(Uri.decode(newUri.toString()));
} else {
ret = mVodPlayer.startPlay(url);
}
if (ret == 0) {
updatePlayerState(SuperPlayerDef.PlayerState.PLAYING);
}
}
mIsPlayWithFileId = false;
}
private boolean isVersionSupportAppendUrl() {
String strVersion = TXLiveBase.getSDKVersionStr();
String[] strVers = strVersion.split("\\.");
if (strVers.length <= 1) {
return false;
}
int majorVer = 0;
int minorVer = 0;
try{
majorVer = Integer.parseInt(strVers[0]);
minorVer = Integer.parseInt(strVers[1]);
}
catch (NumberFormatException e){
TXCLog.e(TAG, "parse version failed.", e);
majorVer = 0;
minorVer = 0;
}
Log.i(TAG, strVersion + " , " + majorVer + " , " + minorVer);
return majorVer > SUPPORT_MAJOR_VERSION || (majorVer == SUPPORT_MAJOR_VERSION && minorVer >= SUPPORT_MINOR_VERSION) ;
}
/**
* 播放时移直播url
*/
private void playTimeShiftLiveURL(int appId, String url) {
final String bizid = url.substring(url.indexOf("//") + 2, url.indexOf("."));
final String domian = SuperPlayerGlobalConfig.getInstance().playShiftDomain;
final String streamid = url.substring(url.lastIndexOf("/") + 1, url.lastIndexOf("."));
TXCLog.i(TAG, "bizid:" + bizid + ",streamid:" + streamid + ",appid:" + appId);
playLiveURL(url, TXLivePlayer.PLAY_TYPE_LIVE_FLV);
int bizidNum = -1;
try {
bizidNum = Integer.parseInt(bizid);
} catch (NumberFormatException e) {
e.printStackTrace();
TXCLog.e(TAG, "playTimeShiftLiveURL: bizidNum error = " + bizid);
}
mLivePlayer.prepareLiveSeek(domian, bizidNum);
}
/**
* 配置多码流url
*
* @param url
*/
private void startMultiStreamLiveURL(String url) {
mLivePlayConfig.setAutoAdjustCacheTime(false);
mLivePlayConfig.setMaxAutoAdjustCacheTime(5);
mLivePlayConfig.setMinAutoAdjustCacheTime(5);
mLivePlayer.setConfig(mLivePlayConfig);
if (mObserver != null) {
mObserver.onPlayTimeShiftLive(mLivePlayer, url);
}
}
/**
* 上报播放时长
*/
private void reportPlayTime() {
if (mReportLiveStartTime != -1) {
long reportEndTime = System.currentTimeMillis();
long diff = (reportEndTime - mReportLiveStartTime) / 1000;
LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_LIVE_TIME, diff, 0);
mReportLiveStartTime = -1;
}
if (mReportVodStartTime != -1) {
long reportEndTime = System.currentTimeMillis();
long diff = (reportEndTime - mReportVodStartTime) / 1000;
LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_VOD_TIME, diff, mIsPlayWithFileId ? 1 : 0);
mReportVodStartTime = -1;
}
}
/**
* 更新播放进度
*
* @param current 当前播放进度(秒)
* @param duration 总时长(秒)
*/
private void updatePlayProgress(long current, long duration) {
if (mObserver != null) {
mObserver.onPlayProgress(current, duration);
}
}
/**
* 更新播放类型
*
* @param playType
*/
private void updatePlayerType(SuperPlayerDef.PlayerType playType) {
if (playType != mCurrentPlayType) {
mCurrentPlayType = playType;
}
if (mObserver != null) {
mObserver.onPlayerTypeChange(playType);
}
}
/**
* 更新播放状态
*
* @param playState
*/
private void updatePlayerState(SuperPlayerDef.PlayerState playState) {
mCurrentPlayState = playState;
if (mObserver == null) {
return;
}
switch (playState) {
case PLAYING:
mObserver.onPlayBegin(getPlayName());
break;
case PAUSE:
mObserver.onPlayPause();
break;
case LOADING:
mObserver.onPlayLoading();
break;
case END:
mObserver.onPlayStop();
break;
}
}
private void updateStreamStartStatus(boolean success, SuperPlayerDef.PlayerType playerType, VideoQuality quality) {
if (mObserver != null) {
mObserver.onSwitchStreamStart(success, playerType, quality);
}
}
private void updateStreamEndStatus(boolean success, SuperPlayerDef.PlayerType playerType, VideoQuality quality) {
if (mObserver != null) {
mObserver.onSwitchStreamEnd(success, playerType, quality);
}
}
private void updateVideoQualityList(List<VideoQuality> videoQualities, VideoQuality defaultVideoQuality) {
if (mObserver != null) {
mObserver.onVideoQualityListChange(videoQualities, defaultVideoQuality);
}
}
private void updateVideoImageSpriteAndKeyFrame(PlayImageSpriteInfo info, List<PlayKeyFrameDescInfo> list) {
if (mObserver != null) {
mObserver.onVideoImageSpriteAndKeyFrameChanged(info, list);
}
}
private void onError(int code, String message) {
if (mObserver != null) {
mObserver.onError(code, message);
}
}
private String getPlayName() {
String title = "";
if (mCurrentModel != null && !TextUtils.isEmpty(mCurrentModel.title)) {
title = mCurrentModel.title;
} else if (mCurrentProtocol != null && !TextUtils.isEmpty(mCurrentProtocol.getName())) {
title = mCurrentProtocol.getName();
}
return title;
}
/**
* 是否是RTMP协议
*
* @param videoURL
* @return
*/
private boolean isRTMPPlay(String videoURL) {
return !TextUtils.isEmpty(videoURL) && videoURL.startsWith("rtmp");
}
/**
* 是否是HTTP-FLV协议
*
* @param videoURL
* @return
*/
private boolean isFLVPlay(String videoURL) {
return (!TextUtils.isEmpty(videoURL) && videoURL.startsWith("http://")
|| videoURL.startsWith("https://")) && videoURL.contains(".flv");
}
@Override
public void play(String url) {
SuperPlayerModel model = new SuperPlayerModel();
model.url = url;
playWithModel(model);
}
@Override
public void play(int appId, String url) {
SuperPlayerModel model = new SuperPlayerModel();
model.appId = appId;
model.url = url;
playWithModel(model);
}
@Override
public void play(int appId, String fileId, String psign) {
SuperPlayerVideoId videoId = new SuperPlayerVideoId();
videoId.fileId = fileId;
videoId.pSign = psign;
SuperPlayerModel model = new SuperPlayerModel();
model.appId = appId;
model.videoId = videoId;
playWithModel(model);
}
@Override
public void play(int appId, List<SuperPlayerModel.SuperPlayerURL> superPlayerURLS, int defaultIndex) {
SuperPlayerModel model = new SuperPlayerModel();
model.appId = appId;
model.multiURLs = superPlayerURLS;
model.playDefaultIndex = defaultIndex;
playWithModel(model);
}
@Override
public void reStart() {
if (mCurrentPlayType == SuperPlayerDef.PlayerType.LIVE || mCurrentPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) {
if (isRTMPPlay(mCurrentPlayVideoURL)) {
playLiveURL(mCurrentPlayVideoURL, TXLivePlayer.PLAY_TYPE_LIVE_RTMP);
} else if (isFLVPlay(mCurrentPlayVideoURL)) {
playTimeShiftLiveURL(mCurrentModel.appId, mCurrentPlayVideoURL);
if (mCurrentModel.multiURLs != null && !mCurrentModel.multiURLs.isEmpty()) {
startMultiStreamLiveURL(mCurrentPlayVideoURL);
}
}
} else {
playVodURL(mCurrentPlayVideoURL);
}
}
@Override
public void pause() {
if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) {
mVodPlayer.pause();
} else {
mLivePlayer.pause();
}
updatePlayerState(SuperPlayerDef.PlayerState.PAUSE);
}
@Override
public void pauseVod() {
if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) {
mVodPlayer.pause();
}
updatePlayerState(SuperPlayerDef.PlayerState.PAUSE);
}
@Override
public void resume() {
if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) {
mVodPlayer.resume();
} else {
mLivePlayer.resume();
}
updatePlayerState(SuperPlayerDef.PlayerState.PLAYING);
}
@Override
public void resumeLive() {
if (mCurrentPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) {
mLivePlayer.resumeLive();
}
updatePlayerType(SuperPlayerDef.PlayerType.LIVE);
}
@Override
public void stop() {
if (mVodPlayer != null) {
mVodPlayer.stopPlay(false);
}
if (mLivePlayer != null) {
mLivePlayer.stopPlay(false);
mVideoView.removeVideoView();
}
updatePlayerState(SuperPlayerDef.PlayerState.END);
reportPlayTime();
}
@Override
public void destroy() {
}
@Override
public void switchPlayMode(SuperPlayerDef.PlayerMode playerMode) {
if (mCurrentPlayMode == playerMode) {
return;
}
mCurrentPlayMode = playerMode;
}
@Override
public void enableHardwareDecode(boolean enable) {
if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) {
mChangeHWAcceleration = true;
mVodPlayer.enableHardwareDecode(enable);
mSeekPos = (int) mVodPlayer.getCurrentPlaybackTime();
TXCLog.i(TAG, "save pos:" + mSeekPos);
stop();
if (mCurrentProtocol == null) { // 当protocol为空时,则说明当前播放视频为非v2和v4视频
playModeVideo(mCurrentModel);
} else {
playModeVideo(mCurrentProtocol);
}
} else {
mLivePlayer.enableHardwareDecode(enable);
playWithModel(mCurrentModel);
}
// 硬件加速上报
if (enable) {
LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_HW_DECODE, 0, 0);
} else {
LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_SOFT_DECODE, 0, 0);
}
}
@Override
public void setPlayerView(TXCloudVideoView videoView) {
if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) {
mVodPlayer.setPlayerView(videoView);
} else {
mLivePlayer.setPlayerView(videoView);
}
}
@Override
public void seek(int position) {
if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) {
if (mVodPlayer != null) {
mVodPlayer.seek(position);
}
} else {
updatePlayerType(SuperPlayerDef.PlayerType.LIVE_SHIFT);
LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_TIMESHIFT, 0, 0);
if (mLivePlayer != null) {
mLivePlayer.seek(position);
}
}
if (mObserver != null) {
mObserver.onSeek(position);
}
}
@Override
public void snapshot(TXLivePlayer.ITXSnapshotListener listener) {
if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) {
mVodPlayer.snapshot(listener);
} else if (mCurrentPlayType == SuperPlayerDef.PlayerType.LIVE) {
mLivePlayer.snapshot(listener);
} else {
listener.onSnapshot(null);
}
}
@Override
public void setRate(float speedLevel) {
if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) {
mVodPlayer.setRate(speedLevel);
mCurrentPlayRate = speedLevel;
}
//速度改变上报
LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_CHANGE_SPEED, 0, 0);
}
@Override
public void setMirror(boolean isMirror) {
if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) {
mVodPlayer.setMirror(isMirror);
}
if (isMirror) {
LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_MIRROR, 0, 0);
}
}
@Override
public void switchStream(VideoQuality quality) {
mVideoQuality = quality;
if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) {
if (mVodPlayer != null) {
if (quality.url != null) { // br!=0;index=-1;url!=null //br=0;index!=-1;url!=null
// 说明是非多bitrate的m3u8子流,需要手动seek
float currentTime = mVodPlayer.getCurrentPlaybackTime();
mVodPlayer.stopPlay(true);
TXCLog.i(TAG, "onQualitySelect quality.url:" + quality.url);
mVodPlayer.setStartTime(currentTime);
mVodPlayer.startPlay(quality.url);
} else { //br!=0;index!=-1;url=null
TXCLog.i(TAG, "setBitrateIndex quality.index:" + quality.index);
// 说明是多bitrate的m3u8子流,会自动无缝seek
mVodPlayer.setBitrateIndex(quality.index);
}
updateStreamStartStatus(true, SuperPlayerDef.PlayerType.VOD, quality);
}
} else {
boolean success = false;
if (mLivePlayer != null && !TextUtils.isEmpty(quality.url)) {
int result = mLivePlayer.switchStream(quality.url);
success = result >= 0;
}
updateStreamStartStatus(success, SuperPlayerDef.PlayerType.LIVE, quality);
}
//清晰度上报
LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_CHANGE_RESOLUTION, 0, 0);
}
@Override
public void setLoop(boolean isLoop) {
if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) {
mVodPlayer.setLoop(isLoop);
}
}
@Override
public String getPlayURL() {
return mCurrentPlayVideoURL;
}
@Override
public SuperPlayerDef.PlayerMode getPlayerMode() {
return mCurrentPlayMode;
}
@Override
public SuperPlayerDef.PlayerState getPlayerState() {
return mCurrentPlayState;
}
@Override
public SuperPlayerDef.PlayerType getPlayerType() {
return mCurrentPlayType;
}
public float getPlayerRate() {
return mCurrentPlayRate;
}
@Override
public void setObserver(SuperPlayerObserver observer) {
mObserver = observer;
}
}
@@ -0,0 +1,57 @@
package com.tencent.liteav.demo.superplayer.model;
import com.tencent.liteav.demo.superplayer.SuperPlayerDef;
import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo;
import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo;
import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality;
import com.tencent.rtmp.TXLivePlayer;
import java.util.List;
public abstract class SuperPlayerObserver {
/**
* 开始播放
* @param name 当前视频名称
*/
public void onPlayBegin(String name) {}
/**
* 播放暂停
*/
public void onPlayPause() {}
/**
* 播放器停止
*/
public void onPlayStop() {}
/**
* 播放器进入Loading状态
*/
public void onPlayLoading() {}
/**
* 播放进度回调
*
* @param current
* @param duration
*/
public void onPlayProgress(long current, long duration) {}
public void onSeek(int position) {}
public void onSwitchStreamStart(boolean success, SuperPlayerDef.PlayerType playerType, VideoQuality quality){}
public void onSwitchStreamEnd(boolean success, SuperPlayerDef.PlayerType playerType, VideoQuality quality){}
public void onError(int code, String message) {}
public void onPlayerTypeChange(SuperPlayerDef.PlayerType playType) {}
public void onPlayTimeShiftLive(TXLivePlayer player, String url) {}
public void onVideoQualityListChange(List<VideoQuality> videoQualities, VideoQuality defaultVideoQuality) {}
public void onVideoImageSpriteAndKeyFrameChanged(PlayImageSpriteInfo info, List<PlayKeyFrameDescInfo> list) {}
}
@@ -0,0 +1,21 @@
package com.tencent.liteav.demo.superplayer.model.entity;
/**
* Created by hans on 2019/3/25.
* <p>
* 自适应码流信息
*/
public class EncryptedStreamingInfo {
public String drmType;
public String url;
@Override
public String toString() {
return "TCEncryptedStreamingInfo{" +
", drmType='" + drmType + '\'' +
", url='" + url + '\'' +
'}';
}
}
@@ -0,0 +1,20 @@
package com.tencent.liteav.demo.superplayer.model.entity;
import java.util.List;
/**
* 视频雪碧图信息
*/
public class PlayImageSpriteInfo {
public List<String> imageUrls; // 图片链接URL
public String webVttUrl; // web vtt描述文件下载URL
@Override
public String toString() {
return "TCPlayImageSpriteInfo{" +
"imageUrls=" + imageUrls +
", webVttUrl='" + webVttUrl + '\'' +
'}';
}
}
@@ -0,0 +1,67 @@
package com.tencent.liteav.demo.superplayer.model.entity;
/**
* Created by annidy on 2017/12/20.
* <p>
* 视频播放信息
*/
public class PlayInfoStream {
public int height;
public int width;
public int size;
public int duration;
public int bitrate;
public int definition;
public String id;
public String name;
public String url;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getHeight() {
return height;
}
public int getDuration() {
return duration;
}
public int getBitrate() {
return bitrate;
}
public void setHeight(int height) {
this.height = height;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public void setDuration(int duration) {
this.duration = duration;
}
public void setBitrate(int bitrate) {
this.bitrate = bitrate;
}
}
@@ -0,0 +1,18 @@
package com.tencent.liteav.demo.superplayer.model.entity;
/**
* 视频关键帧信息
*/
public class PlayKeyFrameDescInfo {
public String content; // 描述信息
public float time; // 关键帧时间(秒)
@Override
public String toString() {
return "TCPlayKeyFrameDescInfo{" +
"content='" + content + '\'' +
", time=" + time +
'}';
}
}
@@ -0,0 +1,22 @@
package com.tencent.liteav.demo.superplayer.model.entity;
/**
* 自适应码流视频画质别名
*/
public class ResolutionName {
public String name; // 画质名称
public String type; // 类型 可能的取值有 video 和 audio
public int width;
public int height;
@Override
public String toString() {
return "TCResolutionName{" +
"width='" + width + '\'' +
"height='" + height + '\'' +
"type='" + type + '\'' +
", name=" + name +
'}';
}
}
@@ -0,0 +1,22 @@
package com.tencent.liteav.demo.superplayer.model.entity;
public class SuperPlayerVideoIdV2 {
public String fileId; // 腾讯云视频fileId
public String timeout; // 【可选】加密链接超时时间戳,转换为16进制小写字符串,腾讯云 CDN 服务器会根据该时间判断该链接是否有效。
public String us; // 【可选】唯一标识请求,增加链接唯一性
public String sign; // 【可选】防盗链签名
public int exper = -1; // 【V2可选】试看时长,单位:秒。可选
@Override
public String toString() {
return "SuperPlayerVideoId{" +
", fileId='" + fileId + '\'' +
", timeout='" + timeout + '\'' +
", exper=" + exper +
", us='" + us + '\'' +
", sign='" + sign + '\'' +
'}';
}
}
@@ -0,0 +1,40 @@
package com.tencent.liteav.demo.superplayer.model.entity;
import java.util.List;
/**
* Created by yuejiaoli on 2018/7/6.
*
* 视频画质信息
*/
public class VideoClassification {
private String id;
private String name;
private List<Integer> definitionList;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Integer> getDefinitionList() {
return definitionList;
}
public void setDefinitionList(List<Integer> definitionList) {
this.definitionList = definitionList;
}
}
@@ -0,0 +1,25 @@
package com.tencent.liteav.demo.superplayer.model.entity;
/**
* Created by yuejiaoli on 2018/7/7.
* <p>
* 清晰度
*/
public class VideoQuality {
public int index;
public int bitrate;
public String name;
public String title;
public String url;
public VideoQuality() {
}
public VideoQuality(int index, String title, String url) {
this.index = index;
this.title = title;
this.url = url;
}
}
@@ -0,0 +1,144 @@
package com.tencent.liteav.demo.superplayer.model.net;
import android.os.AsyncTask;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
/**
* Created by hans on 2018/9/11.
* <p>
* 超级播放器模块由于涉及查询视频信息,所以需要有一个内置的HTTP请求模块
* <p>
* 为了不引入额外的网络请求库,这里使用原生的Java HTTPURLConnection实现
* <p>
* 推荐您修改网络模块,使用您项目中的网络请求库,如okHTTP、Volley等
*/
public class HttpURLClient {
private static class Holder {
static final HttpURLClient INSTANCE = new HttpURLClient();
}
public static HttpURLClient getInstance() {
return Holder.INSTANCE;
}
/**
* get请求
*
* @param urlStr
* @param callback
*/
public void get(final String urlStr, final OnHttpCallback callback) {
AsyncTask.execute(new Runnable() {
@Override
public void run() {
BufferedReader bufferedReader = null;
try {
URL url = new URL(urlStr);
URLConnection connection = url.openConnection();
connection.setConnectTimeout(15000);
connection.setReadTimeout(15000);
connection.connect();
InputStream in = connection.getInputStream();
if (in == null) {
if (callback != null)
callback.onError();
return;
}
bufferedReader = new BufferedReader(new InputStreamReader(in));
String line = null;
StringBuilder sb = new StringBuilder();
while ((line = bufferedReader.readLine()) != null) {
sb.append(line);
}
if (callback != null)
callback.onSuccess(sb.toString());
} catch (IOException e) {
e.printStackTrace();
if (callback != null)
callback.onError();
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
});
}
/**
* post json数据请求
*
* @param urlStr
* @param callback
*/
public void postJson(final String urlStr, final String json, final OnHttpCallback callback) {
AsyncTask.execute(new Runnable() {
@Override
public void run() {
BufferedReader bufferedReader = null;
try {
URL url = new URL(urlStr);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(15000);
connection.setReadTimeout(15000);
connection.setRequestMethod("POST");
connection.addRequestProperty("Content-Type", "application/json; charset=utf-8");
connection.setDoInput(true);
connection.setDoOutput(true);
connection.connect();
OutputStream outputStream = connection.getOutputStream();
outputStream.write(json.getBytes());
outputStream.flush();
outputStream.close();
InputStream in = connection.getInputStream();
if (in == null) {
if (callback != null)
callback.onError();
return;
}
bufferedReader = new BufferedReader(new InputStreamReader(in));
String line = null;
StringBuilder sb = new StringBuilder();
while ((line = bufferedReader.readLine()) != null) {
sb.append(line);
}
if (callback != null)
callback.onSuccess(sb.toString());
} catch (IOException e) {
e.printStackTrace();
if (callback != null)
callback.onError();
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
});
}
public interface OnHttpCallback {
void onSuccess(String result);
void onError();
}
}
@@ -0,0 +1,102 @@
package com.tencent.liteav.demo.superplayer.model.net;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import com.tencent.liteav.basic.log.TXCLog;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Created by liyuejiao on 2018/7/19.
*
* 数据上报模块
*/
public class LogReport {
private static final String TAG = "TCLogReport";
private String mAppName;
private String mPackageName;
//ELK上报事件
public static final String ELK_ACTION_CHANGE_RESOLUTION = "change_resolution";
public static final String ELK_ACTION_TIMESHIFT = "timeshift";
public static final String ELK_ACTION_FLOATMOE = "floatmode";
public static final String ELK_ACTION_LIVE_TIME = "superlive";
public static final String ELK_ACTION_VOD_TIME = "supervod";
public static final String ELK_ACTION_CHANGE_SPEED = "change_speed";
public static final String ELK_ACTION_MIRROR = "mirror";
public static final String ELK_ACTION_SOFT_DECODE = "soft_decode";
public static final String ELK_ACTION_HW_DECODE = "hw_decode";
public static final String ELK_ACTION_IMAGE_SPRITE = "image_sprite";
public static final String ELK_ACTION_PLAYER_POINT = "player_point";
private LogReport() {
}
private static class Holder {
private static LogReport instance = new LogReport();
}
public static LogReport getInstance() {
return Holder.instance;
}
public void uploadLogs(String action, long usedtime, int fileid) {
String reqUrl = "https://ilivelog.qcloud.com";
String body = "";
try {
JSONObject jsonObject = new JSONObject();
jsonObject.put("action", action);
jsonObject.put("fileid", fileid);
jsonObject.put("type", "log");
jsonObject.put("bussiness", "superplayer");
jsonObject.put("usedtime", usedtime);
jsonObject.put("platform", "android");
if (mAppName != null) {
jsonObject.put("appname", mAppName);
}
if (mPackageName != null) {
jsonObject.put("appidentifier", mPackageName);
}
body = jsonObject.toString();
TXCLog.d(TAG, body);
} catch (JSONException e) {
e.printStackTrace();
}
HttpURLClient.getInstance().postJson(reqUrl, body, new HttpURLClient.OnHttpCallback() {
@Override
public void onSuccess(String result) {
}
@Override
public void onError() {
}
});
}
public void setAppName(Context context) {
if (context == null) {
return;
}
ApplicationInfo applicationInfo = context.getApplicationInfo();
int stringId = applicationInfo.labelRes;
mAppName = stringId == 0 ? applicationInfo.nonLocalizedLabel.toString() : context.getString(stringId);
}
public void setPackageName(Context context) {
if (context == null) {
return;
}
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
// 当前版本的包名
mPackageName = info.packageName;
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,83 @@
package com.tencent.liteav.demo.superplayer.model.protocol;
import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo;
import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo;
import com.tencent.liteav.demo.superplayer.model.entity.ResolutionName;
import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality;
import java.util.List;
/**
* 视频信息协议解析接口
*/
public interface IPlayInfoParser {
/**
* 获取未加密视频播放url,若没有获取sampleaes url
*
* @return url字符串
*/
String getURL();
/**
* 获取加密视频播放url
*
* @return url字符串
*/
String getEncryptedURL(PlayInfoConstant.EncryptedURLType type);
/**
* 获取加密token
*
* @return token字符串
*/
String getToken();
/**
* 获取视频名称
*
* @return 视频名称字符串
*/
String getName();
/**
* 获取雪碧图信息
*
* @return 雪碧图信息对象
*/
PlayImageSpriteInfo getImageSpriteInfo();
/**
* 获取关键帧信息
*
* @return 关键帧信息数组
*/
List<PlayKeyFrameDescInfo> getKeyFrameDescInfo();
/**
* 获取画质信息
*
* @return 画质信息数组
*/
List<VideoQuality> getVideoQualityList();
/**
* 获取默认画质信息
*
* @return 默认画质信息对象
*/
VideoQuality getDefaultVideoQuality();
/**
* 获取视频画质别名列表
*
* @return 画质别名数组
*/
List<ResolutionName> getResolutionNameList();
/**
* 获取 DRM 加密类型
* @return
*/
String getDRMType();
}
@@ -0,0 +1,102 @@
package com.tencent.liteav.demo.superplayer.model.protocol;
import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo;
import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo;
import com.tencent.liteav.demo.superplayer.model.entity.ResolutionName;
import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality;
import java.util.List;
/**
* 视频信息协议接口
*/
public interface IPlayInfoProtocol {
/**
* 发送视频信息协议网络请求
*
* @param callback 协议请求回调
*/
void sendRequest(IPlayInfoRequestCallback callback);
/**
* 中途取消请求
*/
void cancelRequest();
/**
* 获取视频播放url
*
* @return 视频播放url字符串
*/
String getUrl();
/**
* 获取加密视频播放url
*
* @return url字符串
*/
String getEncyptedUrl(PlayInfoConstant.EncryptedURLType type);
/**
* 获取加密token
*
* @return token字符串
*/
String getToken();
/**
* 获取视频名称
*
* @return 视频名称字符串
*/
String getName();
/**
* 获取雪碧图信息
*
* @return 雪碧图信息对象
*/
PlayImageSpriteInfo getImageSpriteInfo();
/**
* 获取关键帧信息
*
* @return 关键帧信息数组
*/
List<PlayKeyFrameDescInfo> getKeyFrameDescInfo();
/**
* 获取画质信息
*
* @return 画质信息数组
*/
List<VideoQuality> getVideoQualityList();
/**
* 获取默认画质
*
* @return 默认画质信息对象
*/
VideoQuality getDefaultVideoQuality();
/**
* 获取视频画质别名列表
*
* @return 画质别名数组
*/
List<ResolutionName> getResolutionNameList();
/**
* 透传内容
*
* @return 透传内容
*/
String getPenetrateContext();
/**
* 获取 DRM 加密类型
* @return
*/
String getDRMType();
}
@@ -0,0 +1,23 @@
package com.tencent.liteav.demo.superplayer.model.protocol;
/**
* 视频信息协议请求回调接口
*/
public interface IPlayInfoRequestCallback {
/**
* 成功回调
*
* @param protocol 视频信息协议实现类
* @param param 视频信息协议输入参数
*/
void onSuccess(IPlayInfoProtocol protocol, PlayInfoParams param);
/**
* 错误回调
*
* @param errCode 错误码
* @param message 错误信息
*/
void onError(int errCode, String message);
}
@@ -0,0 +1,20 @@
package com.tencent.liteav.demo.superplayer.model.protocol;
public class PlayInfoConstant {
public enum EncryptedURLType {
SIMPLEAES("SimpleAES"),
WIDEVINE("widevine");
EncryptedURLType(String type){
value = type;
}
private String value;
public String getValue(){
return value;
}
}
}
@@ -0,0 +1,29 @@
package com.tencent.liteav.demo.superplayer.model.protocol;
import com.tencent.liteav.demo.superplayer.SuperPlayerVideoId;
import com.tencent.liteav.demo.superplayer.model.entity.SuperPlayerVideoIdV2;
/**
* 视频信息协议解析需要传入的参数
*/
public class PlayInfoParams {
//必选
public int appId; // 腾讯云视频appId
public String fileId; // 腾讯云视频fileId
public SuperPlayerVideoId videoId; //v4 协议参数
public SuperPlayerVideoIdV2 videoIdV2; //v2 协议参数
public PlayInfoParams() {
}
@Override
public String toString() {
return "TCPlayInfoParams{" +
", appId='" + appId + '\'' +
", fileId='" + fileId + '\'' +
", v4='" + (videoId != null ? videoId.toString() : "") + '\'' +
", v2='" + (videoIdV2 != null ? videoIdV2.toString() : "") + '\'' +
'}';
}
}
@@ -0,0 +1,455 @@
package com.tencent.liteav.demo.superplayer.model.protocol;
import android.util.Log;
import com.tencent.liteav.basic.log.TXCLog;
import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo;
import com.tencent.liteav.demo.superplayer.model.entity.PlayInfoStream;
import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo;
import com.tencent.liteav.demo.superplayer.model.entity.ResolutionName;
import com.tencent.liteav.demo.superplayer.model.entity.VideoClassification;
import com.tencent.liteav.demo.superplayer.model.utils.VideoQualityUtils;
import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
/**
* V2视频信息协议解析实现类
*
* 负责解析V2视频信息协议请求响应的Json数据
*/
public class PlayInfoParserV2 implements IPlayInfoParser{
private static final String TAG = "TCPlayInfoParserV2";
private JSONObject mResponse; // 协议请求返回的Json数据
//播放器配置信息
private String mDefaultVideoClassification; // 默认视频清晰度名称
private List<VideoClassification> mVideoClassificationList; // 视频清晰度信息列表
private PlayImageSpriteInfo mImageSpriteInfo; // 雪碧图信息
private List<PlayKeyFrameDescInfo> mKeyFrameDescInfo; // 关键帧打点信息
//视频信息
private String mName; // 视频名称
private PlayInfoStream mSourceStream; // 源视频流信息
private PlayInfoStream mMasterPlayList; // 主播放视频流信息
private LinkedHashMap<String, PlayInfoStream> mTranscodePlayList; // 转码视频信息列表
private String mURL; // 视频播放url
private List<VideoQuality> mVideoQualityList; // 视频画质信息列表
private VideoQuality mDefaultVideoQuality; // 默认视频画质
public PlayInfoParserV2(JSONObject response) {
mResponse = response;
parsePlayInfo();
}
/**
* 从视频信息协议请求响应的Json数据中解析出视频信息
*
* 解析流程:
*
* 1、解析播放器信息(playerInfo)字段,获取视频清晰度列表{@link #mVideoClassificationList}以及默认清晰度{@link #mDefaultVideoClassification}
*
* 2、解析雪碧图信息(imageSpriteInfo)字段,获取雪碧图信息{@link #mImageSpriteInfo}
*
* 3、解析关键帧信息(keyFrameDescInfo)字段,获取关键帧信息{@link #mKeyFrameDescInfo}
*
* 4、解析视频信息(videoInfo)字段,获取视频名称{@link #mName}、源视频信息{@link #mSourceStream}、
* 主视频列表{@link #mMasterPlayList}、转码视频列表{@link #mTranscodePlayList}
*
* 5、从主视频列表、转码视频列表、源视频信息中解析出视频播放url{@link #mURL}、画质信息{@link #mVideoQualityList}、
* 默认画质{@link #mDefaultVideoQuality}
*/
private void parsePlayInfo() {
try {
JSONObject playerInfo = mResponse.optJSONObject("playerInfo");
if (playerInfo != null) {
mDefaultVideoClassification = parseDefaultVideoClassification(playerInfo);
mVideoClassificationList = parseVideoClassificationList(playerInfo);
}
JSONObject imageSpriteInfo = mResponse.optJSONObject("imageSpriteInfo");
if (imageSpriteInfo != null) {
mImageSpriteInfo = parseImageSpriteInfo(imageSpriteInfo);
}
JSONObject keyFrameDescInfo = mResponse.optJSONObject("keyFrameDescInfo");
if (keyFrameDescInfo != null) {
mKeyFrameDescInfo = parseKeyFrameDescInfo(keyFrameDescInfo);
}
JSONObject videoInfo = mResponse.optJSONObject("videoInfo");
if (videoInfo != null) {
mName = parseName(videoInfo);
mSourceStream = parseSourceStream(videoInfo);
mMasterPlayList = parseMasterPlayList(videoInfo);
mTranscodePlayList = parseTranscodePlayList(videoInfo);
}
parseVideoInfo();
} catch (JSONException e) {
TXCLog.e(TAG, Log.getStackTraceString(e));
}
}
/**
* 解析默认视频清晰度信息
*
* @param playerInfo 包含默认视频清晰度信息的Json对象
* @return 默认视频清晰度名称字符串
*/
private String parseDefaultVideoClassification(JSONObject playerInfo) throws JSONException {
return playerInfo.getString("defaultVideoClassification");
}
/**
* 解析视频清晰度信息
*
* @param playerInfo 包含默认视频类别信息的Json对象
* @return 视频清晰度信息数组
*/
private List<VideoClassification> parseVideoClassificationList(JSONObject playerInfo) throws JSONException {
List<VideoClassification> arrayList = new ArrayList<>();
JSONArray videoClassificationArray = playerInfo.getJSONArray("videoClassification");
if (videoClassificationArray != null) {
for (int i = 0; i < videoClassificationArray.length(); i++) {
JSONObject object = videoClassificationArray.getJSONObject(i);
VideoClassification classification = new VideoClassification();
classification.setId(object.getString("id"));
classification.setName(object.getString("name"));
List<Integer> definitionList = new ArrayList<>();
JSONArray array = object.getJSONArray("definitionList");
if (array != null) {
for (int j = 0; j < array.length(); j++) {
int definition = array.getInt(j);
definitionList.add(definition);
}
}
classification.setDefinitionList(definitionList);
arrayList.add(classification);
}
}
return arrayList;
}
/**
* 解析雪碧图信息
*
* @param imageSpriteInfo 包含雪碧图信息的Json对象
* @return 雪碧图信息对象
*/
private PlayImageSpriteInfo parseImageSpriteInfo(JSONObject imageSpriteInfo) throws JSONException {
JSONArray imageSpriteList = imageSpriteInfo.getJSONArray("imageSpriteList");
if (imageSpriteList != null) {
JSONObject spriteJSONObject = imageSpriteList.getJSONObject(imageSpriteList.length() - 1); //获取最后一个来解析
PlayImageSpriteInfo info = new PlayImageSpriteInfo();
info.webVttUrl = spriteJSONObject.getString("webVttUrl");
JSONArray jsonArray = spriteJSONObject.getJSONArray("imageUrls");
List<String> imageUrls = new ArrayList<>();
for (int i = 0; i < jsonArray.length(); i++) {
String url = jsonArray.getString(i);
imageUrls.add(url);
}
info.imageUrls = imageUrls;
return info;
}
return null;
}
/**
*解析关键帧打点信息
*
* @param keyFrameDescInfo 包含关键帧信息的Json对象
* @return 关键帧信息数组
*/
private List<PlayKeyFrameDescInfo> parseKeyFrameDescInfo(JSONObject keyFrameDescInfo) throws JSONException {
JSONArray jsonArr = keyFrameDescInfo.getJSONArray("keyFrameDescList");
if (jsonArr != null) {
List<PlayKeyFrameDescInfo> infoList = new ArrayList<>();
for (int i = 0; i < jsonArr.length(); i++) {
String content = jsonArr.getJSONObject(i).getString("content");
long time = jsonArr.getJSONObject(i).getLong("timeOffset");
float timeS = (float) (time / 1000.0);//转换为秒
PlayKeyFrameDescInfo info = new PlayKeyFrameDescInfo();
try {
info.content = URLDecoder.decode(content, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
info.content = "";
}
info.time = timeS;
infoList.add(info);
}
return infoList;
}
return null;
}
/**
* 解析视频名称
*
* @param videoInfo 包含视频名称信息的Json对象
* @return 视频名称字符串
* @throws JSONException
*/
private String parseName(JSONObject videoInfo) throws JSONException {
JSONObject basicInfo = videoInfo.getJSONObject("basicInfo");
if (basicInfo != null) {
return basicInfo.getString("name");
}
return null;
}
/**
* 解析源视频流信息
*
* @param videoInfo 包含源视频流信息的Json对象
* @return 源视频流信息对象
*/
private PlayInfoStream parseSourceStream(JSONObject videoInfo) throws JSONException {
if (!videoInfo.has("sourceVideo"))
return null;
JSONObject sourceVideo = videoInfo.getJSONObject("sourceVideo");
if (sourceVideo != null) {
PlayInfoStream stream = new PlayInfoStream();
stream.url = sourceVideo.getString("url");
stream.duration = sourceVideo.getInt("duration");
stream.width = sourceVideo.getInt("width");
stream.height = sourceVideo.getInt("height");
stream.size = sourceVideo.getInt("size");
stream.bitrate = sourceVideo.getInt("bitrate");
return stream;
}
return null;
}
/**
* 解析主播放视频流信息
*
* @param videoInfo 包含主播放视频流信息的Json对象
* @return 主播放视频流信息对象
*/
private PlayInfoStream parseMasterPlayList(JSONObject videoInfo) throws JSONException {
if (!videoInfo.has("masterPlayList"))
return null;
JSONObject masterPlayList = videoInfo.getJSONObject("masterPlayList");
if (masterPlayList != null) {
PlayInfoStream stream = new PlayInfoStream();
stream.url = masterPlayList.getString("url");
return stream;
}
return null;
}
/**
* 解析转码视频流信息
*
* 转码视频流信息{@link #mTranscodePlayList}中不包含清晰度名称,需要与视频清晰度信息{@link #mVideoClassificationList}做匹配
*
* @param videoInfo 包含转码视频流信息的Json对象
* @return 转码视频信息列表 key: 清晰度名称 value: 视频流信息
*/
private LinkedHashMap<String, PlayInfoStream> parseTranscodePlayList(JSONObject videoInfo) throws JSONException {
List<PlayInfoStream> transcodeList = parseStreamList(videoInfo);
if (transcodeList == null) return mTranscodePlayList;
for (int i = 0; i < transcodeList.size(); i++) {
PlayInfoStream stream = transcodeList.get(i);
// 匹配清晰度
if (mVideoClassificationList != null) {
for (int j = 0; j < mVideoClassificationList.size(); j++) {
VideoClassification classification = mVideoClassificationList.get(j);
List<Integer> definitionList = classification.getDefinitionList();
if (definitionList.contains(stream.definition)) {
stream.id = classification.getId();
stream.name = classification.getName();
}
}
}
}
//清晰度去重
LinkedHashMap<String, PlayInfoStream> idList = new LinkedHashMap<>();
for (int i = 0; i < transcodeList.size(); i++) {
PlayInfoStream stream = transcodeList.get(i);
if (!idList.containsKey(stream.id)) {
idList.put(stream.id, stream);
} else {
PlayInfoStream copy = idList.get(stream.id);
if (copy.getUrl().endsWith("mp4")) { // 列表中url是mp4,则进行下一步
continue;
}
if (stream.getUrl().endsWith("mp4")) { // 新判断的url是mp4,则替换列表中
idList.remove(copy.id);
idList.put(stream.id, stream);
}
}
}
//按清晰度排序
return idList;
}
/**
* 解析转码视频信息
*
* @param videoInfo 包含转码视频信息的Json对象
* @return 转码视频是信息数组
*/
private List<PlayInfoStream> parseStreamList(JSONObject videoInfo) throws JSONException {
List<PlayInfoStream> streamList = new ArrayList<>();
JSONArray transcodeList = videoInfo.optJSONArray("transcodeList");
if (transcodeList != null) {
for (int i = 0; i < transcodeList.length(); i++) {
JSONObject transcode = transcodeList.getJSONObject(i);
PlayInfoStream stream = new PlayInfoStream();
stream.url = transcode.getString("url");
stream.duration = transcode.getInt("duration");
stream.width = transcode.getInt("width");
stream.height = transcode.getInt("height");
stream.size = transcode.getInt("size");
stream.bitrate = transcode.getInt("bitrate");
stream.definition = transcode.getInt("definition");
streamList.add(stream);
}
}
return streamList;
}
/**
* 解析视频播放url、画质列表、默认画质
*
* V2协议响应Json数据中可能包含多个视频播放信息:主播放视频信息{@link #mMasterPlayList}、转码视频{@link #mTranscodePlayList}、
* 源视频{@link #mSourceStream}, 播放优先级依次递减
*
* 从优先级最高的视频信息中解析出播放信息
*/
private void parseVideoInfo() {
//有主播放视频信息时,从中解析出支持多码率播放的url
if (mMasterPlayList != null) {
mURL = mMasterPlayList.getUrl();
return;
}
//无主播放信息,从转码视频信息中解析出各码流信息
if (mTranscodePlayList != null && mTranscodePlayList.size() != 0) {
PlayInfoStream stream = mTranscodePlayList.get(mDefaultVideoClassification);
String videoURL = null;
if (stream != null) {
videoURL = stream.getUrl();
} else {
for (PlayInfoStream stream1 : mTranscodePlayList.values()) {
if (stream1 != null && stream1.getUrl() != null) {
stream = stream1;
videoURL = stream1.getUrl();
break;
}
}
}
if (videoURL != null) {
mVideoQualityList = VideoQualityUtils.convertToVideoQualityList(mTranscodePlayList);
mDefaultVideoQuality = VideoQualityUtils.convertToVideoQuality(stream);
mURL = videoURL;
return;
}
}
//无主播放信息、转码信息,从源视频信息中解析出播放信息
if (mSourceStream != null) {
if (mDefaultVideoClassification != null) {
mDefaultVideoQuality = VideoQualityUtils.convertToVideoQuality(mSourceStream, mDefaultVideoClassification);
mVideoQualityList = new ArrayList<>();
mVideoQualityList.add(mDefaultVideoQuality);
}
mURL = mSourceStream.getUrl();
}
}
/**
* 获取视频播放url
*
* @return url字符串
*/
@Override
public String getURL() {
return mURL;
}
@Override
public String getEncryptedURL(PlayInfoConstant.EncryptedURLType type) {
return null;
}
@Override
public String getToken() {
return null;
}
/**
* 获取视频名称
*
* @return 视频名称字符串
*/
@Override
public String getName() {
return mName;
}
/**
* 获取雪碧图信息
*
* @return 雪碧图信息对象
*/
@Override
public PlayImageSpriteInfo getImageSpriteInfo() {
return mImageSpriteInfo;
}
/**
* 获取关键帧信息
*
* @return 关键帧信息数组
*/
@Override
public List<PlayKeyFrameDescInfo> getKeyFrameDescInfo() {
return mKeyFrameDescInfo;
}
/**
* 获取画质信息
*
* @return 画质信息数组
*/
@Override
public List<VideoQuality> getVideoQualityList() {
return mVideoQualityList;
}
/**
* 获取默认画质信息
*
* @return 默认画质信息对象
*/
@Override
public VideoQuality getDefaultVideoQuality() {
return mDefaultVideoQuality;
}
/**
* 获取视频画质别名列表
*
* @return 画质别名数组
*/
@Override
public List<ResolutionName> getResolutionNameList() {
return null;
}
@Override
public String getDRMType() {
return "";
}
}
@@ -0,0 +1,228 @@
package com.tencent.liteav.demo.superplayer.model.protocol;
import android.text.TextUtils;
import android.util.Log;
import com.tencent.liteav.basic.log.TXCLog;
import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo;
import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo;
import com.tencent.liteav.demo.superplayer.model.entity.EncryptedStreamingInfo;
import com.tencent.liteav.demo.superplayer.model.entity.ResolutionName;
import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
/**
* V4视频协议解析实现类
*
* 负责解析V4视频信息协议请求响应的Json数据
*/
public class PlayInfoParserV4 implements IPlayInfoParser {
private static final String TAG = "TCPlayInfoParserV4";
private JSONObject mResponse; // 协议请求返回的Json数据
private String mName; // 视频名称
private String mURL; // 未加密视频播放url
private String mToken; // DRM token
private List<EncryptedStreamingInfo> mEncryptedStreamingInfoList;// 加密视频播放url 数组
private PlayImageSpriteInfo mImageSpriteInfo; // 雪碧图信息
private List<PlayKeyFrameDescInfo> mKeyFrameDescInfo; // 关键帧信息
private List<ResolutionName> mResolutionNameList; // 自适应码流画质名称匹配信息
private String mDRMType;
public PlayInfoParserV4(JSONObject response) {
mResponse = response;
parsePlayInfo();
}
private void parseSubStreams(JSONArray substreams) throws JSONException {
if (substreams != null && substreams.length() > 0) {
mResolutionNameList = new ArrayList<>();
for (int i = 0; i < substreams.length(); i++) {
JSONObject jsonObject = substreams.getJSONObject(i);
ResolutionName resolutionName = new ResolutionName();
int width = jsonObject.optInt("width");
int height = jsonObject.optInt("height");
resolutionName.width = width;
resolutionName.height = height;
resolutionName.name = jsonObject.optString("resolutionName");
resolutionName.type = jsonObject.optString("type");
mResolutionNameList.add(resolutionName);
}
}
}
/**
* 从视频信息协议请求响应的Json数据中解析出视频信息
*/
private void parsePlayInfo() {
try {
JSONObject media = mResponse.getJSONObject("media");
if (media != null) {
//解析视频名称
JSONObject basicInfo = media.optJSONObject("basicInfo");
if (basicInfo != null) {
mName = basicInfo.optString("name");
}
//解析视频播放url
JSONObject streamingInfo = media.getJSONObject("streamingInfo");
if (streamingInfo != null) {
JSONObject plainoutObj = streamingInfo.optJSONObject("plainOutput");//未加密的输出
if (plainoutObj != null) {
mURL = plainoutObj.optString("url");//未加密直接解析出视频url
parseSubStreams(plainoutObj.optJSONArray("subStreams"));
}
JSONArray drmoutputobj = streamingInfo.optJSONArray("drmOutput");//加密输出
if (drmoutputobj != null && drmoutputobj.length() > 0) {
mEncryptedStreamingInfoList = new ArrayList<>();
for (int i = 0; i < drmoutputobj.length(); i++) {
JSONObject jsonObject = drmoutputobj.optJSONObject(i);
String drmType = jsonObject.optString("type");
String url = jsonObject.optString("url");
EncryptedStreamingInfo info = new EncryptedStreamingInfo();
info.drmType = drmType;
info.url = url;
mDRMType = drmType;
mEncryptedStreamingInfoList.add(info);
parseSubStreams(jsonObject.optJSONArray("subStreams"));
}
}
mToken = streamingInfo.optString("drmToken");
}
//解析雪碧图信息
JSONObject imageSpriteInfo = media.optJSONObject("imageSpriteInfo");
if (imageSpriteInfo != null) {
mImageSpriteInfo = new PlayImageSpriteInfo();
mImageSpriteInfo.webVttUrl = imageSpriteInfo.getString("webVttUrl");
JSONArray jsonArray = imageSpriteInfo.optJSONArray("imageUrls");
if (jsonArray != null && jsonArray.length() > 0) {
List<String> imageUrls = new ArrayList<>();
for (int i = 0; i < jsonArray.length(); i++) {
String url = jsonArray.getString(i);
imageUrls.add(url);
}
mImageSpriteInfo.imageUrls = imageUrls;
}
}
//解析关键帧信息
JSONObject keyFrameDescInfo = media.optJSONObject("keyFrameDescInfo");
if (keyFrameDescInfo != null) {
mKeyFrameDescInfo = new ArrayList<>();
JSONArray keyFrameDescList = keyFrameDescInfo.optJSONArray("keyFrameDescList");
if (keyFrameDescList != null && keyFrameDescList.length() > 0) {
for (int i = 0; i < keyFrameDescList.length(); i++) {
JSONObject jsonObject = keyFrameDescList.getJSONObject(i);
PlayKeyFrameDescInfo info = new PlayKeyFrameDescInfo();
info.time = jsonObject.optLong("timeOffset");
info.content = jsonObject.optString("content");
mKeyFrameDescInfo.add(info);
}
}
}
}
} catch (JSONException e) {
TXCLog.e(TAG, Log.getStackTraceString(e));
}
}
/**
* 获取视频播放url
*
* @return url字符串
*/
@Override
public String getURL() {
String url = mURL;
if (!TextUtils.isEmpty(mToken)) {
url = getEncryptedURL(PlayInfoConstant.EncryptedURLType.SIMPLEAES);
}
return url;
}
@Override
public String getEncryptedURL(PlayInfoConstant.EncryptedURLType type) {
for (EncryptedStreamingInfo info : mEncryptedStreamingInfoList) {
if (info.drmType != null && info.drmType.equalsIgnoreCase(type.getValue())) {
return info.url;
}
}
return null;
}
@Override
public String getToken() {
return TextUtils.isEmpty(mToken) ? null : mToken;
}
/**
* 获取视频名称
*
* @return 视频名称字符串
*/
@Override
public String getName() {
return mName;
}
/**
* 获取雪碧图信息
*
* @return 雪碧图信息对象
*/
@Override
public PlayImageSpriteInfo getImageSpriteInfo() {
return mImageSpriteInfo;
}
/**
* 获取关键帧信息
*
* @return 关键帧信息数组
*/
@Override
public List<PlayKeyFrameDescInfo> getKeyFrameDescInfo() {
return mKeyFrameDescInfo;
}
/**
* 获取画质信息
*
* @return 画质信息数组
*/
@Override
public List<VideoQuality> getVideoQualityList() {
return null;
}
/**
* 获取默认画质信息
*
* @return 默认画质信息对象
*/
@Override
public VideoQuality getDefaultVideoQuality() {
return null;
}
/**
* 获取视频画质别名列表
*
* @return 画质别名数组
*/
@Override
public List<ResolutionName> getResolutionNameList() {
return mResolutionNameList;
}
@Override
public String getDRMType() {
return mDRMType;
}
}
@@ -0,0 +1,275 @@
package com.tencent.liteav.demo.superplayer.model.protocol;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import com.tencent.liteav.basic.log.TXCLog;
import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo;
import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo;
import com.tencent.liteav.demo.superplayer.model.entity.ResolutionName;
import com.tencent.liteav.demo.superplayer.model.net.HttpURLClient;
import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.List;
/**
* V2视频信息协议实现类
* <p>
* 负责V2视频信息协议的请求控制与数据获取
*/
public class PlayInfoProtocolV2 implements IPlayInfoProtocol {
private static final String TAG = "TCPlayInfoProtocolV2";
private final String BASE_URLS_V2 = "https://playvideo.qcloud.com/getplayinfo/v2"; // V2协议请求地址
private Handler mMainHandler; // 用于切换线程
private PlayInfoParams mParams; // 协议请求输入的参数
private IPlayInfoParser mParser; // 协议请求返回Json的解析对象
public PlayInfoProtocolV2(PlayInfoParams params) {
mParams = params;
mMainHandler = new Handler(Looper.getMainLooper());
}
/**
* 发送视频信息协议网络请求
*
* @param callback 协议请求回调
*/
@Override
public void sendRequest(final IPlayInfoRequestCallback callback) {
if (mParams.fileId == null) {
return;
}
String urlStr = makeUrlString();
TXCLog.i(TAG, "getVodByFileId: url = " + urlStr);
HttpURLClient.getInstance().get(urlStr, new HttpURLClient.OnHttpCallback() {
@Override
public void onSuccess(String result) {
TXCLog.i(TAG, "http request success: result = " + result);
parseJson(result, callback);
runOnMainThread(new Runnable() {
@Override
public void run() {
callback.onSuccess(PlayInfoProtocolV2.this, mParams);
}
});
}
@Override
public void onError() {
runOnMainThread(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.onError(-1, "http request error.");
}
}
});
}
});
}
/**
* 拼装协议请求url
*
* @return 协议请求url字符串
*/
private String makeUrlString() {
String urlStr = String.format("%s/%d/%s", BASE_URLS_V2, mParams.appId, mParams.fileId);
if (mParams.videoIdV2 != null) {
String query = makeQueryString(mParams.videoIdV2.timeout, mParams.videoIdV2.us, mParams.videoIdV2.exper, mParams.videoIdV2.sign);
if (query != null) {
urlStr = urlStr + "?" + query;
}
}
return urlStr;
}
/**
* 拼装协议请求url中的query字段
*
* @param timeout 加密链接超时时间戳
* @param us 唯一标识请求
* @param exper 试看时长,单位:秒,十进制数值
* @param sign 签名字符串
* @return query字段字符串
*/
private String makeQueryString(String timeout, String us, int exper, String sign) {
StringBuilder str = new StringBuilder();
if (timeout != null) {
str.append("t=" + timeout + "&");
}
if (us != null) {
str.append("us=" + us + "&");
}
if (sign != null) {
str.append("sign=" + sign + "&");
}
if (exper >= 0) {
str.append("exper=" + exper + "&");
}
if (str.length() > 1) {
str.deleteCharAt(str.length() - 1);
}
return str.toString();
}
/**
* 中途取消请求
*/
@Override
public void cancelRequest() {
}
/**
* 获取视频播放url
*
* @return 视频播放url字符串
*/
@Override
public String getUrl() {
return mParser == null ? null : mParser.getURL();
}
@Override
public String getEncyptedUrl(PlayInfoConstant.EncryptedURLType type) {
return mParser.getEncryptedURL(type);
}
@Override
public String getToken() {
return mParser.getToken();
}
/**
* 获取视频名称
*
* @return 视频名称字符串
*/
@Override
public String getName() {
return mParser == null ? null : mParser.getName();
}
/**
* 获取雪碧图信息
*
* @return 雪碧图信息对象
*/
@Override
public PlayImageSpriteInfo getImageSpriteInfo() {
return mParser == null ? null : mParser.getImageSpriteInfo();
}
/**
* 获取关键帧信息
*
* @return 关键帧信息数组
*/
@Override
public List<PlayKeyFrameDescInfo> getKeyFrameDescInfo() {
return mParser == null ? null : mParser.getKeyFrameDescInfo();
}
/**
* 获取画质信息
*
* @return 画质信息数组
*/
@Override
public List<VideoQuality> getVideoQualityList() {
return mParser == null ? null : mParser.getVideoQualityList();
}
/**
* 获取默认画质
*
* @return 默认画质信息对象
*/
@Override
public VideoQuality getDefaultVideoQuality() {
return mParser == null ? null : mParser.getDefaultVideoQuality();
}
/**
* 解析视频信息协议请求响应的Json数据
*
* @param content 响应Json字符串
* @param callback 协议请求回调
*/
private boolean parseJson(String content, final IPlayInfoRequestCallback callback) {
if (TextUtils.isEmpty(content)) {
TXCLog.e(TAG, "parseJsonV2 err, content is empty!");
runOnMainThread(new Runnable() {
@Override
public void run() {
callback.onError(-1, "request return error!");
}
});
return false;
}
try {
JSONObject jsonObject = new JSONObject(content);
final int code = jsonObject.getInt("code");
final String message = jsonObject.optString("message");
TXCLog.e(TAG, message);
if (code == 0) {
mParser = new PlayInfoParserV2(jsonObject);
} else {
runOnMainThread(new Runnable() {
@Override
public void run() {
callback.onError(code, message);
}
});
return false;
}
} catch (JSONException e) {
e.printStackTrace();
TXCLog.e(TAG, "parseJson err");
}
return true;
}
/**
* 切换到主线程
* <p>
* 从视频协议请求回调的子线程切换回主线程
*
* @param r 需要在主线程中执行的任务
*/
private void runOnMainThread(Runnable r) {
if (Looper.myLooper() == mMainHandler.getLooper()) {
r.run();
} else {
mMainHandler.post(r);
}
}
/**
* 获取视频画质别名列表
*
* @return 画质别名数组
*/
@Override
public List<ResolutionName> getResolutionNameList() {
return mParser == null ? null : mParser.getResolutionNameList();
}
@Override
public String getPenetrateContext() {
return null;
}
@Override
public String getDRMType() {
return mParser != null ? mParser.getDRMType() : "";
}
}
@@ -0,0 +1,291 @@
package com.tencent.liteav.demo.superplayer.model.protocol;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import com.tencent.liteav.basic.log.TXCLog;
import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo;
import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo;
import com.tencent.liteav.demo.superplayer.model.entity.ResolutionName;
import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality;
import com.tencent.liteav.demo.superplayer.model.net.HttpURLClient;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.List;
/**
* V4视频信息协议实现类
*
* 负责V4视频信息协议的请求控制与数据获取
*/
public class PlayInfoProtocolV4 implements IPlayInfoProtocol {
private static final String TAG = "TCPlayInfoProtocolV4";
private final String BASE_URLS_V4 = "https://playvideo.qcloud.com/getplayinfo/v4"; // V4协议请求地址
private Handler mMainHandler; // 用于切换线程
private PlayInfoParams mParams; // 协议请求输入的参数
private IPlayInfoParser mParser; // 协议请求返回Json的解析对象
private String mRequestContext;//透传字段
public PlayInfoProtocolV4(PlayInfoParams params) {
mParams = params;
mMainHandler = new Handler(Looper.getMainLooper());
}
/**
* 发送视频信息协议网络请求
*
* @param callback 协议请求回调
*/
@Override
public void sendRequest(final IPlayInfoRequestCallback callback) {
if (mParams.fileId == null) {
return;
}
String urlString = makeUrlString();
HttpURLClient.getInstance().get(urlString, new HttpURLClient.OnHttpCallback() {
@Override
public void onSuccess(String result) {
boolean ret = parseJson(result, callback);
if (ret) {
runOnMainThread(new Runnable() {
@Override
public void run() {
callback.onSuccess(PlayInfoProtocolV4.this, mParams);
}
});
}
}
@Override
public void onError() {
runOnMainThread(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.onError(-1, "http request error.");
}
}
});
}
});
}
/**
* 解析视频信息协议请求响应的Json数据
*
* @param content 响应Json字符串
* @param callback 协议请求回调
*/
private boolean parseJson(String content, final IPlayInfoRequestCallback callback) {
if (TextUtils.isEmpty(content)) {
TXCLog.e(TAG, "parseJson err, content is empty!");
runOnMainThread(new Runnable() {
@Override
public void run() {
callback.onError(-1, "request return error!");
}
});
return false;
}
try {
JSONObject jsonObject = new JSONObject(content);
final int code = jsonObject.getInt("code");
final String message = jsonObject.optString("message");
final String warning = jsonObject.optString("warning");
mRequestContext = jsonObject.optString("context");
TXCLog.i(TAG, "context : " + mRequestContext);
TXCLog.i(TAG, "message: " + message);
TXCLog.i(TAG, "warning: " + warning);
if (code == 0) {
int version = jsonObject.getInt("version");
if (version == 2) {
mParser = new PlayInfoParserV2(jsonObject);
} else if (version == 4) {
mParser = new PlayInfoParserV4(jsonObject);
}
} else {
runOnMainThread(new Runnable() {
@Override
public void run() {
callback.onError(code, message);
}
});
return false;
}
} catch (JSONException e) {
e.printStackTrace();
TXCLog.e(TAG, "parseJson err");
}
return true;
}
/**
* 拼装协议请求url
*
* @return 协议请求url字符串
*/
private String makeUrlString() {
String urlStr = String.format("%s/%d/%s", BASE_URLS_V4, mParams.appId, mParams.fileId);
String psign = makeJWTSignature(mParams);
String query = null;
if (mParams.videoId != null) {
query = makeQueryString(null, psign, null);
}
if (!TextUtils.isEmpty(query)) {
urlStr = urlStr + "?" + query;
}
TXCLog.d(TAG, "request url: " + urlStr);
return urlStr;
}
public static String makeJWTSignature(PlayInfoParams params) {
if (params.videoId != null && !TextUtils.isEmpty(params.videoId.pSign)) {
return params.videoId.pSign;
}
return null;
}
/**
* 拼装协议请求url中的query字段
*
* @return query字段字符串
*/
private String makeQueryString(String pcfg, String psign, String content) {
StringBuilder str = new StringBuilder();
if (!TextUtils.isEmpty(pcfg)) {
str.append("pcfg=" + pcfg + "&");
}
if (!TextUtils.isEmpty(psign)) {
str.append("psign=" + psign + "&");
}
if (!TextUtils.isEmpty(content)) {
str.append("context=" + content + "&");
}
if (str.length() > 1) {
str.deleteCharAt(str.length() - 1);
}
return str.toString();
}
/**
* 中途取消请求
*/
@Override
public void cancelRequest() {
}
/**
* 获取视频播放url
*
* @return 视频播放url字符串
*/
@Override
public String getUrl() {
return mParser == null ? null : mParser.getURL();
}
@Override
public String getEncyptedUrl(PlayInfoConstant.EncryptedURLType type) {
return mParser == null ? null : mParser.getEncryptedURL(type);
}
@Override
public String getToken() {
return mParser == null ? null : mParser.getToken();
}
/**
* 获取视频名称
*
* @return 视频名称字符串
*/
@Override
public String getName() {
return mParser == null ? null : mParser.getName();
}
/**
* 获取雪碧图信息
*
* @return 雪碧图信息对象
*/
@Override
public PlayImageSpriteInfo getImageSpriteInfo() {
return mParser == null ? null : mParser.getImageSpriteInfo();
}
/**
* 获取关键帧信息
*
* @return 关键帧信息数组
*/
@Override
public List<PlayKeyFrameDescInfo> getKeyFrameDescInfo() {
return mParser == null ? null : mParser.getKeyFrameDescInfo();
}
/**
* 获取画质信息
*
* @return 画质信息数组
*/
@Override
public List<VideoQuality> getVideoQualityList() {
return mParser == null ? null : mParser.getVideoQualityList();
}
/**
* 获取默认画质
*
* @return 默认画质信息对象
*/
@Override
public VideoQuality getDefaultVideoQuality() {
return mParser == null ? null : mParser.getDefaultVideoQuality();
}
/**
* 切换到主线程
* <p>
* 从视频协议请求回调的子线程切换回主线程
*
* @param r 需要在主线程中执行的任务
*/
private void runOnMainThread(Runnable r) {
if (Looper.myLooper() == mMainHandler.getLooper()) {
r.run();
} else {
mMainHandler.post(r);
}
}
/**
* 获取视频画质别名列表
*
* @return 画质别名数组
*/
@Override
public List<ResolutionName> getResolutionNameList() {
return mParser == null ? null : mParser.getResolutionNameList();
}
@Override
public String getPenetrateContext() {
return mRequestContext;
}
@Override
public String getDRMType() {
return mParser != null ? mParser.getDRMType() : "";
}
}
@@ -0,0 +1,133 @@
package com.tencent.liteav.demo.superplayer.model.utils;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.widget.Toast;
import com.tencent.rtmp.TXLivePlayer;
import com.tencent.rtmp.TXLog;
import java.lang.ref.WeakReference;
/**
* 网络质量监视工具
*
* 当loading次数大于等于3次时,提示用户切换到低清晰度
*/
public class NetWatcher {
private final static int WATCH_TIME = 30000; // 监控总时长ms
private final static int MAX_LOADING_TIME = 10000; // 一次loading的判定时长ms
private final static int MAX_LOADING_COUNT = 3; // 弹出切换清晰度提示框的loading总次数
private WeakReference<Context> mContext;
private WeakReference<TXLivePlayer> mLivePlayer; // 直播播放器
private String mPlayURL = ""; // 播放的url
private int mLoadingCount = 0; // 记录loading次数
private long mLoadingTime = 0; // 记录单次loading的时长
private long mLoadingStartTime = 0; // loading开始的时间
private boolean mWatching; // 是否正在监控
public NetWatcher(Context context) {
mContext = new WeakReference<>(context);
}
/**
* 开始监控网络
*
* @param playUrl 播放的url
* @param player 播放器
*/
public void start(String playUrl, TXLivePlayer player) {
mWatching = true;
mLivePlayer = new WeakReference<>(player);
mPlayURL = playUrl;
mLoadingCount = 0;
mLoadingTime= 0;
mLoadingStartTime = 0;
TXLog.w("NetWatcher", "net check start watch ");
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.postDelayed(new Runnable() {
@Override
public void run() {
TXLog.w("NetWatcher", "net check loading count = "+mLoadingCount+" loading time = "+mLoadingTime);
if (mLoadingCount >= MAX_LOADING_COUNT || mLoadingTime >= MAX_LOADING_TIME) {
showSwitchStreamDialog();
}
mLoadingCount = 0;
mLoadingTime = 0;
}
}, WATCH_TIME);
}
/**
* 停止监控
*/
public void stop() {
mWatching = false;
mLoadingCount = 0;
mLoadingTime= 0;
mLoadingStartTime = 0;
mPlayURL = "";
mLivePlayer = null;
TXLog.w("NetWatcher", "net check stop watch");
}
/**
* 开始loading计时
*/
public void enterLoading() {
if (mWatching) {
mLoadingCount++;
mLoadingStartTime = System.currentTimeMillis();
}
}
/**
* 结束loading计时
*/
public void exitLoading() {
if (mWatching) {
if (mLoadingStartTime != 0) {
mLoadingTime += System.currentTimeMillis() - mLoadingStartTime;
mLoadingStartTime = 0;
}
}
}
/**
* 弹出切换清晰度的提示框
*/
private void showSwitchStreamDialog() {
final Context context = mContext.get();
if (context == null) return;
AlertDialog alertDialog = new AlertDialog.Builder(context).create();
alertDialog.setMessage("检测到您的网络较差,建议切换清晰度");
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
TXLivePlayer player = mLivePlayer!=null ? mLivePlayer.get() : null;
String videoUrl = mPlayURL.replace(".flv","_900.flv");
if (player != null && !TextUtils.isEmpty(videoUrl)) {
int result = player.switchStream(videoUrl);
if (result < 0) {
Toast.makeText(context,"切换高清清晰度失败,请稍候重试", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context,"正在为您切换为高清清晰度,请稍候...", Toast.LENGTH_SHORT).show();
}
}
dialog.dismiss();
}
});
alertDialog.show();
}
}
@@ -0,0 +1,212 @@
package com.tencent.liteav.demo.superplayer.model.utils;
import android.app.Activity;
import android.app.Service;
import android.content.ContentResolver;
import android.content.Context;
import android.media.AudioManager;
import android.provider.Settings;
import android.view.MotionEvent;
import android.view.Window;
import android.view.WindowManager;
/**
* 手势控制视频播放进度、调节亮度音量的工具
*/
public class VideoGestureDetector {
// 手势类型
private static final int NONE = 0; // 无效果
private static final int VOLUME = 1; // 音量
private static final int BRIGHTNESS = 2; // 亮度
private static final int VIDEO_PROGRESS = 3; // 播放进度
private int mScrollMode = NONE; // 手势类型
private VideoGestureListener mVideoGestureListener; // 回调
private int mVideoWidth; // 视频宽度px
// 亮度相关
private float mBrightness = 1; // 当前亮度(0.0~1.0)
private Window mWindow; // 当前window
private WindowManager.LayoutParams mLayoutParams; // 用于获取和设置屏幕亮度
private ContentResolver mResolver; // 用于获取当前屏幕亮度
// 音量相关
private AudioManager mAudioManager; // 音频管理器,用于设置音量
private int mMaxVolume = 0; // 最大音量值
private int mOldVolume = 0; // 记录调节音量之前的旧音量值
// 视频进度相关
private int mVideoProgress; // 记录滑动后的进度,在回调中抛出
private int mDownProgress; // 滑动开始时的视频播放进度
/**
* 手势临界值,当两滑动事件坐标的水平差值>20时判定为{@link #VIDEO_PROGRESS}, 否则判定为{@link #VOLUME}或者{@link #BRIGHTNESS}
*/
private int offsetX = 20;
//手势灵敏度 0.0~1.0
private float mSensitivity = 0.3f; // 调节音量、亮度的灵敏度
public VideoGestureDetector(Context context) {
mAudioManager = (AudioManager) context.getSystemService(Service.AUDIO_SERVICE);
mMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
if (context instanceof Activity) {
mWindow = ((Activity) context).getWindow();
mLayoutParams = mWindow.getAttributes();
mBrightness = mLayoutParams.screenBrightness;
}
mResolver = context.getContentResolver();
}
/**
* 设置回调
*
* @param videoGestureListener
*/
public void setVideoGestureListener(VideoGestureListener videoGestureListener) {
mVideoGestureListener = videoGestureListener;
}
/**
* 重置数据以开始新的一次滑动
*
* @param videoWidth 视频宽度px
* @param downProgress 手势按下时视频的播放进度(秒)
*/
public void reset(int videoWidth, int downProgress) {
mVideoProgress = 0;
mVideoWidth = videoWidth;
mScrollMode = NONE;
mOldVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
mBrightness = mLayoutParams.screenBrightness;
if (mBrightness == -1) {
//一开始是默认亮度的时候,获取系统亮度,计算比例值
mBrightness = getBrightness() / 255.0f;
}
mDownProgress = downProgress;
}
/**
* 获取当前是否是视频进度滑动手势
*
* @return
*/
public boolean isVideoProgressModel() {
return mScrollMode == VIDEO_PROGRESS;
}
/**
* 获取滑动后对应的视频进度
*
* @return
*/
public int getVideoProgress() {
return mVideoProgress;
}
/**
* 滑动手势操控类别判定
*
* @param height 滑动事件的高度px
* @param downEvent 按下事件
* @param moveEvent 滑动事件
* @param distanceX 滑动水平距离
* @param distanceY 滑动竖直距离
*/
public void check(int height, MotionEvent downEvent, MotionEvent moveEvent, float distanceX, float distanceY) {
switch (mScrollMode) {
case NONE:
//offset是让快进快退不要那么敏感的值
if (Math.abs(downEvent.getX() - moveEvent.getX()) > offsetX) {
mScrollMode = VIDEO_PROGRESS;
} else {
int halfVideoWidth = mVideoWidth / 2;
if (downEvent.getX() < halfVideoWidth) {
mScrollMode = BRIGHTNESS;
} else {
mScrollMode = VOLUME;
}
}
break;
case VOLUME:
int value = height / mMaxVolume;
int newVolume = (int) ((downEvent.getY() - moveEvent.getY()) / value * mSensitivity + mOldVolume);
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newVolume, AudioManager.FLAG_PLAY_SOUND);
float volumeProgress = newVolume / Float.valueOf(mMaxVolume) * 100;
if (mVideoGestureListener != null) {
mVideoGestureListener.onVolumeGesture(volumeProgress);
}
break;
case BRIGHTNESS:
float newBrightness = height == 0 ? 0 : (downEvent.getY() - moveEvent.getY()) / height * mSensitivity;
newBrightness += mBrightness;
if (newBrightness < 0) {
newBrightness = 0;
} else if (newBrightness > 1) {
newBrightness = 1;
}
if (mLayoutParams != null) {
mLayoutParams.screenBrightness = newBrightness;
}
if (mWindow != null) {
mWindow.setAttributes(mLayoutParams);
}
if (mVideoGestureListener != null) {
mVideoGestureListener.onBrightnessGesture(newBrightness);
}
break;
case VIDEO_PROGRESS:
float dis = moveEvent.getX() - downEvent.getX();
float percent = dis / mVideoWidth;
mVideoProgress = (int) (mDownProgress + percent * 100);
if (mVideoGestureListener != null) {
mVideoGestureListener.onSeekGesture(mVideoProgress);
}
break;
}
}
/**
* 获取当前亮度
*
* @return
*/
private int getBrightness() {
if (mResolver != null) {
return Settings.System.getInt(mResolver, Settings.System.SCREEN_BRIGHTNESS, 255);
} else {
return 255;
}
}
/**
* 回调
*/
public interface VideoGestureListener {
/**
* 亮度调节回调
*
* @param newBrightness 滑动后的新亮度值
*/
void onBrightnessGesture(float newBrightness);
/**
* 音量调节回调
*
* @param volumeProgress 滑动后的新音量值
*/
void onVolumeGesture(float volumeProgress);
/**
* 播放进度调节回调
*
* @param seekProgress 滑动后的新视频进度
*/
void onSeekGesture(int seekProgress);
}
}
@@ -0,0 +1,156 @@
package com.tencent.liteav.demo.superplayer.model.utils;
import com.tencent.liteav.basic.log.TXCLog;
import com.tencent.liteav.demo.superplayer.model.entity.PlayInfoStream;
import com.tencent.liteav.demo.superplayer.model.entity.ResolutionName;
import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality;
import com.tencent.rtmp.TXBitrateItem;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* Created by yuejiaoli on 2018/7/6.
*
* 清晰度转换工具
*/
public class VideoQualityUtils {
private static final String TAG = "TCVideoQualityUtil";
/**
* 从比特流信息转换为清晰度信息
*
* @param bitrateItem
* @return
*/
public static VideoQuality convertToVideoQuality(TXBitrateItem bitrateItem, int index) {
VideoQuality quality = new VideoQuality();
quality.bitrate = bitrateItem.bitrate;
quality.index = bitrateItem.index;
switch (index) {
case 0:
quality.name = "FLU";
quality.title = "流畅";
break;
case 1:
quality.name = "SD";
quality.title = "标清";
break;
case 2:
quality.name = "HD";
quality.title = "高清";
break;
case 3:
quality.name = "FHD";
quality.title = "超清";
break;
case 4:
quality.name = "2K";
quality.title = "2K";
break;
case 5:
quality.name = "4K";
quality.title = "4K";
break;
case 6:
quality.name = "8K";
quality.title = "8K";
break;
}
return quality;
}
/**
* 从源视频信息与视频类别信息转换为清晰度信息
*
* @param sourceStream
* @param classification
* @return
*/
public static VideoQuality convertToVideoQuality(PlayInfoStream sourceStream, String classification) {
VideoQuality quality = new VideoQuality();
quality.bitrate = sourceStream.getBitrate();
if (classification.equals("FLU")) {
quality.name = "FLU";
quality.title = "流畅";
} else if (classification.equals("SD")) {
quality.name = "SD";
quality.title = "标清";
} else if (classification.equals("HD")) {
quality.name = "HD";
quality.title = "高清";
} else if (classification.equals("FHD")) {
quality.name = "FHD";
quality.title = "全高清";
} else if (classification.equals("2K")) {
quality.name = "2K";
quality.title = "2K";
} else if (classification.equals("4K")) {
quality.name = "4K";
quality.title = "4K";
}
quality.url = sourceStream.url;
quality.index = -1;
return quality;
}
/**
* 从{@link PlayInfoStream}转换为{@link VideoQuality}
*
* @param stream
* @return
*/
public static VideoQuality convertToVideoQuality(PlayInfoStream stream) {
VideoQuality qulity = new VideoQuality();
qulity.bitrate = stream.getBitrate();
qulity.name = stream.id;
qulity.title = stream.name;
qulity.url = stream.url;
qulity.index = -1;
return qulity;
}
/**
* 从转码列表转换为清晰度列表
*
* @param transcodeList
* @return
*/
public static List<VideoQuality> convertToVideoQualityList(HashMap<String, PlayInfoStream> transcodeList) {
List<VideoQuality> videoQualities = new ArrayList<>();
for (String classification : transcodeList.keySet()) {
VideoQuality videoQuality = convertToVideoQuality(transcodeList.get(classification));
videoQualities.add(videoQuality);
}
return videoQualities;
}
/**
* 根据视频清晰度别名表从码率信息转换为视频清晰度
*
* @param bitrateItem 码率
* @param resolutionNames 清晰度别名表
* @return
*/
public static VideoQuality convertToVideoQuality(TXBitrateItem bitrateItem, List<ResolutionName> resolutionNames) {
VideoQuality quality = new VideoQuality();
quality.bitrate = bitrateItem.bitrate;
quality.index = bitrateItem.index;
boolean getName = false;
for (ResolutionName resolutionName : resolutionNames) {
if (((resolutionName.width == bitrateItem.width && resolutionName.height == bitrateItem.height) || (resolutionName.width == bitrateItem.height && resolutionName.height == bitrateItem.width))
&& "video".equalsIgnoreCase(resolutionName.type)) {
quality.title = resolutionName.name;
getName = true;
break;
}
}
if (!getName) {
TXCLog.i(TAG, "error: could not get quality name!");
}
return quality;
}
}
@@ -0,0 +1,162 @@
package com.tencent.liteav.demo.superplayer.ui.player;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.AttributeSet;
import android.view.View;
import android.widget.RelativeLayout;
import com.tencent.liteav.demo.superplayer.SuperPlayerDef;
import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo;
import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo;
import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality;
import java.util.List;
/**
* 播放器公共逻辑
*/
public abstract class AbsPlayer extends RelativeLayout implements Player {
protected static final int MAX_SHIFT_TIME = 7200; // demo演示直播时移是MAX_SHIFT_TIMEs,即2小时
protected Callback mControllerCallback; // 播放控制回调
protected Runnable mHideViewRunnable = new Runnable() {
@Override
public void run() {
hide();
}
};
public AbsPlayer(Context context) {
super(context);
}
public AbsPlayer(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AbsPlayer(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void setCallback(Callback callback) {
mControllerCallback = callback;
}
@Override
public void setWatermark(Bitmap bmp, float x, float y) {
}
@Override
public void show() {
}
@Override
public void hide() {
}
@Override
public void release() {
}
@Override
public void updatePlayState(SuperPlayerDef.PlayerState playState) {
}
@Override
public void setVideoQualityList(List<VideoQuality> list) {
}
@Override
public void updateTitle(String title) {
}
@Override
public void updateVideoProgress(long current, long duration) {
}
@Override
public void updatePlayType(SuperPlayerDef.PlayerType type) {
}
@Override
public void setBackground(Bitmap bitmap) {
}
@Override
public void showBackground() {
}
@Override
public void hideBackground() {
}
@Override
public void updateVideoQuality(VideoQuality videoQuality) {
}
@Override
public void updateImageSpriteInfo(PlayImageSpriteInfo info) {
}
@Override
public void updateKeyFrameDescInfo(List<PlayKeyFrameDescInfo> list) {
}
/**
* 设置控件的可见性
*
* @param view 目标控件
* @param isVisible 显示:true 隐藏:false
*/
protected void toggleView(View view, boolean isVisible) {
view.setVisibility(isVisible ? View.VISIBLE : View.GONE);
}
/**
* 将秒数转换为hh:mm:ss的格式
*
* @param second
* @return
*/
protected String formattedTime(long second) {
String formatTime;
long h, m, s;
h = second / 3600;
m = (second % 3600) / 60;
s = (second % 3600) % 60;
if (h == 0) {
formatTime = asTwoDigit(m) + ":" + asTwoDigit(s);
} else {
formatTime = asTwoDigit(h) + ":" + asTwoDigit(m) + ":" + asTwoDigit(s);
}
return formatTime;
}
protected String asTwoDigit(long digit) {
String value = "";
if (digit < 10) {
value = "0";
}
value += String.valueOf(digit);
return value;
}
}
@@ -0,0 +1,150 @@
package com.tencent.liteav.demo.superplayer.ui.player;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import com.tencent.liteav.demo.superplayer.SuperPlayerDef;
import com.tencent.liteav.demo.superplayer.SuperPlayerGlobalConfig;
import com.tencent.rtmp.ui.TXCloudVideoView;
import org.leanflutter.plugins.flutter_superplayer.R;
import java.lang.reflect.Field;
/**
* 悬浮窗模式播放控件
* <p>
* 1、滑动以移动悬浮窗,点击悬浮窗回到窗口模式{@link #onTouchEvent(MotionEvent)}
* <p>
* 2、关闭悬浮窗{@link #onClick(View)}
*/
public class FloatPlayer extends AbsPlayer implements View.OnClickListener {
private TXCloudVideoView mFloatVideoView; // 悬浮窗中的视频播放view
private int mStatusBarHeight; // 系统状态栏的高度
private float mXDownInScreen; // 按下事件距离屏幕左边界的距离
private float mYDownInScreen; // 按下事件距离屏幕上边界的距离
private float mXInScreen; // 滑动事件距离屏幕左边界的距离
private float mYInScreen; // 滑动事件距离屏幕上边界的距离
private float mXInView; // 滑动事件距离自身左边界的距离
private float mYInView; // 滑动事件距离自身上边界的距离
public FloatPlayer(Context context) {
super(context);
initView(context);
}
public FloatPlayer(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public FloatPlayer(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
/**
* 初始化view
*/
private void initView(Context context) {
LayoutInflater.from(context).inflate(R.layout.superplayer_vod_player_float, this);
mFloatVideoView = (TXCloudVideoView) findViewById(R.id.superplayer_float_cloud_video_view);
ImageView ivClose = (ImageView) findViewById(R.id.superplayer_iv_close);
ivClose.setOnClickListener(this);
}
/**
* 获取悬浮窗中的视频播放view
*/
public TXCloudVideoView getFloatVideoView() {
return mFloatVideoView;
}
/**
* 设置点击事件监听,实现点击关闭按钮后关闭悬浮窗
*/
@Override
public void onClick(View view) {
int i = view.getId();
if (i == R.id.superplayer_iv_close) {
if (mControllerCallback != null) {
mControllerCallback.onBackPressed(SuperPlayerDef.PlayerMode.FLOAT);
}
}
}
/**
* 重写触摸事件监听,实现悬浮窗随手指移动
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mXInView = event.getX();
mYInView = event.getY();
mXDownInScreen = event.getRawX();
mYDownInScreen = event.getRawY() - getStatusBarHeight();
mXInScreen = event.getRawX();
mYInScreen = event.getRawY() - getStatusBarHeight();
break;
case MotionEvent.ACTION_MOVE: //悬浮窗随手指移动
mXInScreen = event.getRawX();
mYInScreen = event.getRawY() - getStatusBarHeight();
updateViewPosition();
break;
case MotionEvent.ACTION_UP:
if (mXDownInScreen == mXInScreen && mYDownInScreen == mYInScreen) {//手指没有滑动视为点击,回到窗口模式
if (mControllerCallback != null) {
mControllerCallback.onSwitchPlayMode(SuperPlayerDef.PlayerMode.WINDOW);
}
}
break;
default:
break;
}
return true;
}
/**
* 获取系统状态栏高度
*/
private int getStatusBarHeight() {
if (mStatusBarHeight == 0) {
try {
Class<?> c = Class.forName("com.android.internal.R$dimen");
Object o = c.newInstance();
Field field = c.getField("status_bar_height");
int x = (Integer) field.get(o);
mStatusBarHeight = getResources().getDimensionPixelSize(x);
} catch (Exception e) {
e.printStackTrace();
}
}
return mStatusBarHeight;
}
/**
* 更新悬浮窗的位置信息,在回调{@link Callback#onFloatPositionChange(int, int)}中实现悬浮窗移动
*/
private void updateViewPosition() {
int x = (int) (mXInScreen - mXInView);
int y = (int) (mYInScreen - mYInView);
SuperPlayerGlobalConfig.TXRect rect = SuperPlayerGlobalConfig.getInstance().floatViewRect;
if (rect != null) {
rect.x = x;
rect.y = y;
}
if (mControllerCallback != null) {
mControllerCallback.onFloatPositionChange(x, y);
}
}
}
@@ -0,0 +1,930 @@
package com.tencent.liteav.demo.superplayer.ui.player;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.tencent.liteav.demo.superplayer.SuperPlayerDef;
import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo;
import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo;
import com.tencent.liteav.demo.superplayer.model.net.LogReport;
import com.tencent.liteav.demo.superplayer.model.utils.VideoGestureDetector;
import com.tencent.liteav.demo.superplayer.ui.view.PointSeekBar;
import com.tencent.liteav.demo.superplayer.ui.view.VideoProgressLayout;
import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality;
import com.tencent.liteav.demo.superplayer.ui.view.VodMoreView;
import com.tencent.liteav.demo.superplayer.ui.view.VodQualityView;
import com.tencent.liteav.demo.superplayer.ui.view.VolumeBrightnessProgressLayout;
import com.tencent.rtmp.TXImageSprite;
import org.leanflutter.plugins.flutter_superplayer.R;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
/**
* 全屏模式播放控件
*
* 除{@link WindowPlayer}基本功能外,还包括进度条关键帧打点信息显示与跳转、快进快退时缩略图的显示、切换画质
* 镜像播放、硬件加速、倍速播放、弹幕、截图等功能
*
* 1、点击事件监听{@link #onClick(View)}
*
* 2、触摸事件监听{@link #onTouchEvent(MotionEvent)}
*
* 3、进度条滑动事件监听{@link #onProgressChanged(PointSeekBar, int, boolean)}
* {@link #onStartTrackingTouch(PointSeekBar)}{@link #onStopTrackingTouch(PointSeekBar)}
*
* 4、进度条打点信息点击监听{@link #onSeekBarPointClick(View, int)}
*
* 5、切换画质监听{@link #onQualitySelect(VideoQuality)}
*
* 6、倍速播放监听{@link #onSpeedChange(float)}
*
* 7、镜像播放监听{@link #onMirrorChange(boolean)}
*
* 8、硬件加速监听{@link #onHWAcceleration(boolean)}
*
*/
public class FullScreenPlayer extends AbsPlayer implements View.OnClickListener,
VodMoreView.Callback, VodQualityView.Callback, PointSeekBar.OnSeekBarChangeListener, PointSeekBar.OnSeekBarPointClickListener{
// UI控件
private RelativeLayout mLayoutTop; // 顶部标题栏布局
private LinearLayout mLayoutBottom; // 底部进度条所在布局
private ImageView mIvPause; // 暂停播放按钮
private TextView mTvTitle; // 视频名称文本
private TextView mTvBackToLive; // 返回直播文本
private ImageView mIvWatermark; // 水印
private TextView mTvCurrent; // 当前进度文本
private TextView mTvDuration; // 总时长文本
private PointSeekBar mSeekBarProgress; // 播放进度条
private LinearLayout mLayoutReplay; // 重播按钮所在布局
private ProgressBar mPbLiveLoading; // 加载圈
private VolumeBrightnessProgressLayout mGestureVolumeBrightnessProgressLayout; // 音量亮度调节布局
private VideoProgressLayout mGestureVideoProgressLayout; // 手势快进提示布局
private TextView mTvQuality; // 当前画质文本
private ImageView mIvBack; // 顶部标题栏中的返回按钮
private ImageView mIvDanmu; // 弹幕按钮
private ImageView mIvSnapshot; // 截屏按钮
private ImageView mIvLock; // 锁屏按钮
private ImageView mIvMore; // 更多设置弹窗按钮
private VodQualityView mVodQualityView; // 画质列表弹窗
private VodMoreView mVodMoreView; // 更多设置弹窗
private TextView mTvVttText; // 关键帧打点信息文本
private HideLockViewRunnable mHideLockViewRunnable; // 隐藏锁屏按钮子线程
private GestureDetector mGestureDetector; // 手势检测监听器
private VideoGestureDetector mVideoGestureDetector; // 手势控制工具
private boolean isShowing; // 自身是否可见
private boolean mIsChangingSeekBarProgress; // 进度条是否正在拖动,避免SeekBar由于视频播放的update而跳动
private SuperPlayerDef.PlayerType mPlayType; // 当前播放视频类型
private SuperPlayerDef.PlayerState mCurrentPlayState = SuperPlayerDef.PlayerState.END; // 当前播放状态
private long mDuration; // 视频总时长
private long mLivePushDuration; // 直播推流总时长
private long mProgress; // 当前播放进度
private Bitmap mBackgroundBmp; // 背景图
private Bitmap mWaterMarkBmp; // 水印图
private float mWaterMarkBmpX; // 水印x坐标
private float mWaterMarkBmpY; // 水印y坐标
private boolean mBarrageOn; // 弹幕是否开启
private boolean mLockScreen; // 是否锁屏
private TXImageSprite mTXImageSprite; // 雪碧图信息
private List<PlayKeyFrameDescInfo> mTXPlayKeyFrameDescInfoList; // 关键帧信息
private int mSelectedPos = -1; // 点击的关键帧时间点
private VideoQuality mDefaultVideoQuality; // 默认画质
private List<VideoQuality> mVideoQualityList; // 画质列表
private boolean mFirstShowQuality; // 是都是首次显示画质信息
public FullScreenPlayer(Context context) {
super(context);
initialize(context);
}
public FullScreenPlayer(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context);
}
public FullScreenPlayer(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context);
}
/**
* 初始化控件、手势检测监听器、亮度/音量/播放进度的回调
*/
private void initialize(Context context) {
initView(context);
mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
if (mLockScreen) return false;
togglePlayState();
show();
if (mHideViewRunnable != null) {
removeCallbacks(mHideViewRunnable);
postDelayed(mHideViewRunnable, 7000);
}
return true;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
toggle();
return true;
}
@Override
public boolean onScroll(MotionEvent downEvent, MotionEvent moveEvent, float distanceX, float distanceY) {
if (mLockScreen) return false;
if (downEvent == null || moveEvent == null) {
return false;
}
if (mVideoGestureDetector != null && mGestureVolumeBrightnessProgressLayout != null) {
mVideoGestureDetector.check(mGestureVolumeBrightnessProgressLayout.getHeight(), downEvent, moveEvent, distanceX, distanceY);
}
return true;
}
@Override
public boolean onDown(MotionEvent e) {
if (mLockScreen) return true;
if (mVideoGestureDetector != null) {
mVideoGestureDetector.reset(getWidth(), mSeekBarProgress.getProgress());
}
return true;
}
});
mGestureDetector.setIsLongpressEnabled(false);
mVideoGestureDetector = new VideoGestureDetector(getContext());
mVideoGestureDetector.setVideoGestureListener(new VideoGestureDetector.VideoGestureListener() {
@Override
public void onBrightnessGesture(float newBrightness) {
if (mGestureVolumeBrightnessProgressLayout != null) {
mGestureVolumeBrightnessProgressLayout.setProgress((int) (newBrightness * 100));
mVodMoreView.setBrightProgress((int) (newBrightness * 100));
mGestureVolumeBrightnessProgressLayout.setImageResource(R.drawable.superplayer_ic_light_max);
mGestureVolumeBrightnessProgressLayout.show();
}
}
@Override
public void onVolumeGesture(float volumeProgress) {
if (mGestureVolumeBrightnessProgressLayout != null) {
mGestureVolumeBrightnessProgressLayout.setImageResource(R.drawable.superplayer_ic_volume_max);
mGestureVolumeBrightnessProgressLayout.setProgress((int) volumeProgress);
mGestureVolumeBrightnessProgressLayout.show();
}
}
@Override
public void onSeekGesture(int progress) {
mIsChangingSeekBarProgress = true;
if (mGestureVideoProgressLayout != null) {
if (progress > mSeekBarProgress.getMax()) {
progress = mSeekBarProgress.getMax();
}
if (progress < 0) {
progress = 0;
}
mGestureVideoProgressLayout.setProgress(progress);
mGestureVideoProgressLayout.show();
float percentage = ((float) progress) / mSeekBarProgress.getMax();
float currentTime = (mDuration * percentage);
if (mPlayType == SuperPlayerDef.PlayerType.LIVE || mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) {
if (mLivePushDuration > MAX_SHIFT_TIME) {
currentTime = (int) (mLivePushDuration - MAX_SHIFT_TIME * (1 - percentage));
} else {
currentTime = mLivePushDuration * percentage;
}
mGestureVideoProgressLayout.setTimeText(formattedTime((long) currentTime));
} else {
mGestureVideoProgressLayout.setTimeText(formattedTime((long) currentTime) + " / " + formattedTime((long) mDuration));
}
setThumbnail(progress);
}
if (mSeekBarProgress!= null)
mSeekBarProgress.setProgress(progress);
}
});
}
/**
* 初始化view
*/
private void initView(Context context) {
mHideLockViewRunnable = new HideLockViewRunnable(this);
LayoutInflater.from(context).inflate(R.layout.superplayer_vod_player_fullscreen, this);
mLayoutTop = (RelativeLayout) findViewById(R.id.superplayer_rl_top);
mLayoutTop.setOnClickListener(this);
mLayoutBottom = (LinearLayout) findViewById(R.id.superplayer_ll_bottom);
mLayoutBottom.setOnClickListener(this);
mLayoutReplay = (LinearLayout) findViewById(R.id.superplayer_ll_replay);
mIvBack = (ImageView) findViewById(R.id.superplayer_iv_back);
mIvLock = (ImageView) findViewById(R.id.superplayer_iv_lock);
mTvTitle = (TextView) findViewById(R.id.superplayer_tv_title);
mIvPause = (ImageView) findViewById(R.id.superplayer_iv_pause);
mIvDanmu = (ImageView) findViewById(R.id.superplayer_iv_danmuku);
mIvMore = (ImageView) findViewById(R.id.superplayer_iv_more);
mIvSnapshot = (ImageView) findViewById(R.id.superplayer_iv_snapshot);
mTvCurrent = (TextView) findViewById(R.id.superplayer_tv_current);
mTvDuration = (TextView) findViewById(R.id.superplayer_tv_duration);
mSeekBarProgress = (PointSeekBar) findViewById(R.id.superplayer_seekbar_progress);
mSeekBarProgress.setProgress(0);
mSeekBarProgress.setOnPointClickListener(this);
mSeekBarProgress.setOnSeekBarChangeListener(this);
mTvQuality = (TextView) findViewById(R.id.superplayer_tv_quality);
mTvBackToLive = (TextView) findViewById(R.id.superplayer_tv_back_to_live);
mPbLiveLoading = (ProgressBar) findViewById(R.id.superplayer_pb_live);
mVodQualityView = (VodQualityView) findViewById(R.id.superplayer_vod_quality);
mVodQualityView.setCallback(this);
mVodMoreView = (VodMoreView) findViewById(R.id.superplayer_vod_more);
mVodMoreView.setCallback(this);
mTvBackToLive.setOnClickListener(this);
mLayoutReplay.setOnClickListener(this);
mIvLock.setOnClickListener(this);
mIvBack.setOnClickListener(this);
mIvPause.setOnClickListener(this);
mIvDanmu.setOnClickListener(this);
mIvSnapshot.setOnClickListener(this);
mIvMore.setOnClickListener(this);
mTvQuality.setOnClickListener(this);
mTvVttText = (TextView) findViewById(R.id.superplayer_large_tv_vtt_text);
mTvVttText.setOnClickListener(this);
if (mDefaultVideoQuality != null) {
mTvQuality.setText(mDefaultVideoQuality.title);
}
mGestureVolumeBrightnessProgressLayout = (VolumeBrightnessProgressLayout) findViewById(R.id.superplayer_gesture_progress);
mGestureVideoProgressLayout = (VideoProgressLayout) findViewById(R.id.superplayer_video_progress_layout);
mIvWatermark = (ImageView) findViewById(R.id.superplayer_large_iv_water_mark);
}
/**
* 切换播放状态
*
* 双击和点击播放/暂停按钮会触发此方法
*/
private void togglePlayState() {
switch (mCurrentPlayState) {
case PAUSE:
case END:
if (mControllerCallback != null) {
mControllerCallback.onResume();
}
break;
case PLAYING:
case LOADING:
if (mControllerCallback != null) {
mControllerCallback.onPause();
}
mLayoutReplay.setVisibility(View.GONE);
break;
}
show();
}
/**
* 切换自身的可见性
*/
private void toggle() {
if (!mLockScreen) {
if (isShowing) {
hide();
} else {
show();
if (mHideViewRunnable != null) {
removeCallbacks(mHideViewRunnable);
postDelayed(mHideViewRunnable, 7000);
}
}
} else {
mIvLock.setVisibility(VISIBLE);
if (mHideLockViewRunnable!=null) {
removeCallbacks(mHideLockViewRunnable);
postDelayed(mHideLockViewRunnable, 7000);
}
}
if (mVodMoreView.getVisibility() == VISIBLE) {
mVodMoreView.setVisibility(GONE);
}
}
/**
* 设置水印
*
* @param bmp 水印图
* @param x 水印的x坐标
* @param y 水印的y坐标
*/
@Override
public void setWatermark(Bitmap bmp, float x, float y) {
mWaterMarkBmp = bmp;
mWaterMarkBmpY = y;
mWaterMarkBmpX = x;
}
/**
* 显示控件
*/
@Override
public void show() {
isShowing = true;
mLayoutTop.setVisibility(View.VISIBLE);
mLayoutBottom.setVisibility(View.VISIBLE);
if (mHideLockViewRunnable!=null) {
removeCallbacks(mHideLockViewRunnable);
}
mIvLock.setVisibility(VISIBLE);
if (mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) {
if (mLayoutBottom.getVisibility() == VISIBLE)
mTvBackToLive.setVisibility(View.VISIBLE);
}
List<PointSeekBar.PointParams> pointParams = new ArrayList<>();
if (mTXPlayKeyFrameDescInfoList != null)
for (PlayKeyFrameDescInfo info : mTXPlayKeyFrameDescInfoList) {
int progress = (int) (info.time / mDuration * mSeekBarProgress.getMax());
pointParams.add(new PointSeekBar.PointParams(progress, Color.WHITE));
}
mSeekBarProgress.setPointList(pointParams);
}
/**
* 隐藏控件
*/
@Override
public void hide() {
isShowing = false;
mLayoutTop.setVisibility(View.GONE);
mLayoutBottom.setVisibility(View.GONE);
mVodQualityView.setVisibility(View.GONE);
mTvVttText.setVisibility(GONE);
mIvLock.setVisibility(GONE);
if (mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) {
mTvBackToLive.setVisibility(View.GONE);
}
}
/**
* 释放控件的内存
*/
@Override
public void release() {
releaseTXImageSprite();
}
@Override
public void updatePlayState(SuperPlayerDef.PlayerState playState) {
switch (playState) {
case PLAYING:
mIvPause.setImageResource(R.drawable.superplayer_ic_vod_pause_normal);
toggleView(mPbLiveLoading, false);
toggleView(mLayoutReplay, false);
break;
case LOADING:
mIvPause.setImageResource(R.drawable.superplayer_ic_vod_pause_normal);
toggleView(mPbLiveLoading, true);
toggleView(mLayoutReplay, false);
break;
case PAUSE:
mIvPause.setImageResource(R.drawable.superplayer_ic_vod_play_normal);
toggleView(mLayoutReplay, false);
break;
case END:
mIvPause.setImageResource(R.drawable.superplayer_ic_vod_play_normal);
toggleView(mLayoutReplay, true);
break;
}
mCurrentPlayState = playState;
}
/**
* 设置视频画质信息
*
* @param list 画质列表
*/
@Override
public void setVideoQualityList(List<VideoQuality> list) {
mVideoQualityList = list;
mFirstShowQuality = false;
}
/**
* 更新视频名称
*
* @param title 视频名称
*/
@Override
public void updateTitle(String title) {
mTvTitle.setText(title);
}
/**
* 更新是屁播放进度
*
* @param current 当前进度(秒)
* @param duration 视频总时长(秒)
*/
@Override
public void updateVideoProgress(long current, long duration) {
mProgress = current < 0 ? 0 : current;
mDuration = duration < 0 ? 0 : duration;
mTvCurrent.setText(formattedTime(mProgress));
float percentage = mDuration > 0 ? ((float) mProgress / (float) mDuration) : 1.0f;
if (mProgress == 0) {
mLivePushDuration = 0;
percentage = 0;
}
if (mPlayType == SuperPlayerDef.PlayerType.LIVE || mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) {
mLivePushDuration = mLivePushDuration > mProgress ? mLivePushDuration : mProgress;
long leftTime = mDuration - mProgress;
mDuration = mDuration > MAX_SHIFT_TIME ? MAX_SHIFT_TIME : mDuration;
percentage = 1 - (float) leftTime / (float) mDuration;
}
if (percentage >= 0 && percentage <= 1) {
int progress = Math.round(percentage * mSeekBarProgress.getMax());
if (!mIsChangingSeekBarProgress)
mSeekBarProgress.setProgress(progress);
mTvDuration.setText(formattedTime(mDuration));
}
}
@Override
public void updatePlayType(SuperPlayerDef.PlayerType type) {
mPlayType = type;
switch (type) {
case VOD:
mTvBackToLive.setVisibility(View.GONE);
mVodMoreView.updatePlayType(SuperPlayerDef.PlayerType.VOD);
mTvDuration.setVisibility(View.VISIBLE);
break;
case LIVE:
mTvBackToLive.setVisibility(View.GONE);
mTvDuration.setVisibility(View.GONE);
mVodMoreView.updatePlayType(SuperPlayerDef.PlayerType.LIVE);
mSeekBarProgress.setProgress(100);
break;
case LIVE_SHIFT:
if (mLayoutBottom.getVisibility() == VISIBLE) {
mTvBackToLive.setVisibility(View.VISIBLE);
}
mTvDuration.setVisibility(View.GONE);
mVodMoreView.updatePlayType(SuperPlayerDef.PlayerType.LIVE_SHIFT);
break;
}
}
/**
* 更新视频播放画质
*
* @param videoQuality 画质
*/
@Override
public void updateVideoQuality(VideoQuality videoQuality) {
if(videoQuality==null){
mTvQuality.setText("");
return;
}
mDefaultVideoQuality = videoQuality;
if (mTvQuality != null) {
mTvQuality.setText(videoQuality.title);
}
if (mVideoQualityList != null && mVideoQualityList.size() != 0) {
for (int i = 0 ; i < mVideoQualityList.size(); i++) {
VideoQuality quality = mVideoQualityList.get(i);
if (quality!=null && quality.title!=null &&quality.title.equals(mDefaultVideoQuality.title)) {
mVodQualityView.setDefaultSelectedQuality(i);
break;
}
}
}
}
/**
* 更新雪碧图信息
*
* @param info 雪碧图信息
*/
@Override
public void updateImageSpriteInfo(PlayImageSpriteInfo info) {
if (mTXImageSprite != null) {
releaseTXImageSprite();
}
// 有缩略图的时候不显示进度
mGestureVideoProgressLayout.setProgressVisibility(info == null || info.imageUrls == null || info.imageUrls.size() == 0);
if (mPlayType == SuperPlayerDef.PlayerType.VOD) {
mTXImageSprite = new TXImageSprite(getContext());
if (info != null) {
// 雪碧图ELK上报
LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_IMAGE_SPRITE, 0, 0);
mTXImageSprite.setVTTUrlAndImageUrls(info.webVttUrl, info.imageUrls);
} else {
mTXImageSprite.setVTTUrlAndImageUrls(null, null);
}
}
}
private void releaseTXImageSprite() {
if (mTXImageSprite != null) {
mTXImageSprite.release();
mTXImageSprite = null;
}
}
/**
* 更新关键帧信息
*
* @param list 关键帧信息列表
*/
@Override
public void updateKeyFrameDescInfo(List<PlayKeyFrameDescInfo> list) {
mTXPlayKeyFrameDescInfoList = list;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mGestureDetector != null)
mGestureDetector.onTouchEvent(event);
if (!mLockScreen) {
if (event.getAction() == MotionEvent.ACTION_UP && mVideoGestureDetector != null && mVideoGestureDetector.isVideoProgressModel()) {
int progress = mVideoGestureDetector.getVideoProgress();
if (progress > mSeekBarProgress.getMax()) {
progress = mSeekBarProgress.getMax();
}
if (progress < 0) {
progress = 0;
}
mSeekBarProgress.setProgress(progress);
int seekTime = 0;
float percentage = progress * 1.0f / mSeekBarProgress.getMax();
if (mPlayType == SuperPlayerDef.PlayerType.LIVE || mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) {
if (mLivePushDuration > MAX_SHIFT_TIME) {
seekTime = (int) (mLivePushDuration - MAX_SHIFT_TIME * (1 - percentage));
} else {
seekTime = (int) (mLivePushDuration * percentage);
}
}else {
seekTime = (int) (percentage * mDuration);
}
if (mControllerCallback != null) {
mControllerCallback.onSeekTo(seekTime);
}
mIsChangingSeekBarProgress = false;
}
}
if(event.getAction() == MotionEvent.ACTION_DOWN) {
removeCallbacks(mHideViewRunnable);
} else if(event.getAction() == MotionEvent.ACTION_UP) {
postDelayed(mHideViewRunnable, 7000);
}
return true;
}
/**
* 设置点击事件监听
*/
@Override
public void onClick(View view) {
int i = view.getId();
if (i == R.id.superplayer_iv_back || i == R.id.superplayer_tv_title) { //顶部标题栏
if (mControllerCallback != null) {
mControllerCallback.onBackPressed(SuperPlayerDef.PlayerMode.FULLSCREEN);
}
} else if (i == R.id.superplayer_iv_pause) { //暂停\播放按钮
togglePlayState();
} else if (i == R.id.superplayer_iv_danmuku) { //弹幕按钮
toggleBarrage();
} else if (i == R.id.superplayer_iv_snapshot) { //截屏按钮
if (mControllerCallback != null) {
mControllerCallback.onSnapshot();
}
} else if (i == R.id.superplayer_iv_more) { //更多设置按钮
showMoreView();
} else if (i == R.id.superplayer_tv_quality) { //画质按钮
showQualityView();
} else if (i == R.id.superplayer_iv_lock) { //锁屏按钮
toggleLockState();
} else if (i == R.id.superplayer_ll_replay) { //重播按钮
replay();
} else if (i == R.id.superplayer_tv_back_to_live) { //返回直播按钮
if (mControllerCallback != null) {
mControllerCallback.onResumeLive();
}
} else if (i == R.id.superplayer_large_tv_vtt_text) { //关键帧打点信息按钮
seekToKeyFramePos();
}
}
/**
* 开关弹幕
*/
private void toggleBarrage() {
mBarrageOn = !mBarrageOn;
if (mBarrageOn) {
mIvDanmu.setImageResource(R.drawable.superplayer_ic_danmuku_on);
} else {
mIvDanmu.setImageResource(R.drawable.superplayer_ic_danmuku_off);
}
if (mControllerCallback != null) {
mControllerCallback.onDanmuToggle(mBarrageOn);
}
}
/**
* 显示更多设置弹窗
*/
private void showMoreView() {
hide();
mVodMoreView.setVisibility(View.VISIBLE);
}
/**
* 显示画质列表弹窗
*/
private void showQualityView() {
if (mVideoQualityList == null || mVideoQualityList.size() == 0) {
return;
}
if(mVideoQualityList.size()==1 && (mVideoQualityList.get(0)==null || TextUtils.isEmpty(mVideoQualityList.get(0).title))){
return;
}
// 设置默认显示分辨率文字
mVodQualityView.setVisibility(View.VISIBLE);
if (!mFirstShowQuality && mDefaultVideoQuality != null) {
for (int i = 0 ; i < mVideoQualityList.size(); i++) {
VideoQuality quality = mVideoQualityList.get(i);
if (quality!=null && quality.title!=null &&quality.title.equals(mDefaultVideoQuality.title)) {
mVodQualityView.setDefaultSelectedQuality(i);
break;
}
}
mFirstShowQuality = true;
}
mVodQualityView.setVideoQualityList(mVideoQualityList);
}
/**
* 切换锁屏状态
*/
private void toggleLockState() {
mLockScreen = !mLockScreen;
mIvLock.setVisibility(VISIBLE);
if (mHideLockViewRunnable!=null) {
removeCallbacks(mHideLockViewRunnable);
postDelayed(mHideLockViewRunnable, 7000);
}
if (mLockScreen) {
mIvLock.setImageResource(R.drawable.superplayer_ic_player_lock);
hide();
mIvLock.setVisibility(VISIBLE);
} else {
mIvLock.setImageResource(R.drawable.superplayer_ic_player_unlock);
show();
}
}
/**
* 重播
*/
private void replay() {
toggleView(mLayoutReplay, false);
if (mControllerCallback != null) {
mControllerCallback.onResume();
}
}
/**
* 跳转至关键帧打点处
*/
private void seekToKeyFramePos() {
float time = mTXPlayKeyFrameDescInfoList != null ? mTXPlayKeyFrameDescInfoList.get(mSelectedPos).time : 0;
if (mControllerCallback != null) {
mControllerCallback.onSeekTo((int) time);
mControllerCallback.onResume();
}
mTvVttText.setVisibility(GONE);
toggleView(mLayoutReplay, false);
}
@Override
public void onProgressChanged(PointSeekBar seekBar, int progress, boolean isFromUser) {
if (mGestureVideoProgressLayout != null && isFromUser) {
mGestureVideoProgressLayout.show();
float percentage = ((float) progress) / seekBar.getMax();
float currentTime = (mDuration * percentage);
if (mPlayType == SuperPlayerDef.PlayerType.LIVE || mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) {
if (mLivePushDuration > MAX_SHIFT_TIME) {
currentTime = (int) (mLivePushDuration - MAX_SHIFT_TIME * (1 - percentage));
} else {
currentTime = mLivePushDuration * percentage;
}
mGestureVideoProgressLayout.setTimeText(formattedTime((long) currentTime));
} else {
mGestureVideoProgressLayout.setTimeText(formattedTime((long) currentTime) + " / " + formattedTime((long) mDuration));
}
mGestureVideoProgressLayout.setProgress(progress);
}
// 加载点播缩略图
if (isFromUser && mPlayType == SuperPlayerDef.PlayerType.VOD) {
setThumbnail(progress);
}
}
@Override
public void onStartTrackingTouch(PointSeekBar seekBar) {
removeCallbacks(mHideViewRunnable);
}
@Override
public void onStopTrackingTouch(PointSeekBar seekBar) {
int curProgress = seekBar.getProgress();
int maxProgress = seekBar.getMax();
switch (mPlayType) {
case VOD:
if (curProgress >= 0 && curProgress <= maxProgress) {
// 关闭重播按钮
toggleView(mLayoutReplay, false);
float percentage = ((float) curProgress) / maxProgress;
int position = (int) (mDuration * percentage);
if (mControllerCallback != null) {
mControllerCallback.onSeekTo(position);
mControllerCallback.onResume();
}
}
break;
case LIVE:
case LIVE_SHIFT:
toggleView(mPbLiveLoading, true);
int seekTime = (int) (mLivePushDuration * curProgress * 1.0f / maxProgress);
if (mLivePushDuration > MAX_SHIFT_TIME) {
seekTime = (int) (mLivePushDuration - MAX_SHIFT_TIME * (maxProgress - curProgress) * 1.0f / maxProgress);
}
if (mControllerCallback != null) {
mControllerCallback.onSeekTo(seekTime);
}
break;
}
postDelayed(mHideViewRunnable, 7000);
}
@Override
public void onSeekBarPointClick(final View view, final int pos) {
if (mHideLockViewRunnable!=null) {
removeCallbacks(mHideViewRunnable);
postDelayed(mHideViewRunnable, 7000);
}
if (mTXPlayKeyFrameDescInfoList != null) {
//ELK点击上报
LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_PLAYER_POINT, 0, 0);
mSelectedPos = pos;
view.post(new Runnable() {
@Override
public void run() {
int[] location = new int[2];
view.getLocationInWindow(location);
int viewX = location[0];
PlayKeyFrameDescInfo info = mTXPlayKeyFrameDescInfoList.get(pos);
String content = info.content;
mTvVttText.setText(formattedTime((long) info.time) + " " + content);
mTvVttText.setVisibility(VISIBLE);
adjustVttTextViewPos(viewX);
}
});
}
}
/**
* 设置播放进度所对应的缩略图
*
* @param progress 播放进度
*/
private void setThumbnail(int progress) {
float percentage = ((float) progress) / mSeekBarProgress.getMax();
float seekTime = (mDuration * percentage);
if (mTXImageSprite != null) {
Bitmap bitmap = mTXImageSprite.getThumbnail(seekTime);
if (bitmap != null) {
mGestureVideoProgressLayout.setThumbnail(bitmap);
}
}
}
/**
* 计算并设置关键帧打点信息文本显示的位置
*
* @param viewX 点击的打点view
*/
private void adjustVttTextViewPos(final int viewX) {
mTvVttText.post(new Runnable() {
@Override
public void run() {
int width = mTvVttText.getWidth();
int marginLeft = viewX - width / 2;
LayoutParams params = (LayoutParams) mTvVttText.getLayoutParams();
params.leftMargin = marginLeft;
if (marginLeft < 0) {
params.leftMargin = 0;
}
int screenWidth = getResources().getDisplayMetrics().widthPixels;
if (marginLeft + width > screenWidth) {
params.leftMargin = screenWidth - width;
}
mTvVttText.setLayoutParams(params);
}
});
}
@Override
public void onSpeedChange(float speedLevel) {
if (mControllerCallback != null) {
mControllerCallback.onSpeedChange(speedLevel);
}
}
@Override
public void onMirrorChange(boolean isMirror) {
if (mControllerCallback != null) {
mControllerCallback.onMirrorToggle(isMirror);
}
}
@Override
public void onHWAcceleration(boolean isAccelerate) {
if (mControllerCallback != null) {
mControllerCallback.onHWAccelerationToggle(isAccelerate);
}
}
@Override
public void onQualitySelect(VideoQuality quality) {
if (mControllerCallback != null) {
mControllerCallback.onQualityChange(quality);
}
mVodQualityView.setVisibility(View.GONE);
}
/**
* 隐藏锁屏按钮的runnable
*/
private static class HideLockViewRunnable implements Runnable{
private WeakReference<FullScreenPlayer> mWefControllerFullScreen;
public HideLockViewRunnable(FullScreenPlayer controller) {
mWefControllerFullScreen = new WeakReference<>(controller);
}
@Override
public void run() {
if (mWefControllerFullScreen!=null && mWefControllerFullScreen.get()!=null) {
mWefControllerFullScreen.get().mIvLock.setVisibility(GONE);
}
}
}
public void hideDanmu() {
mIvDanmu.setVisibility(View.GONE);
}
public void hideReplay() {
mLayoutReplay.setVisibility(View.GONE);
}
}
@@ -0,0 +1,223 @@
package com.tencent.liteav.demo.superplayer.ui.player;
import android.graphics.Bitmap;
import com.tencent.liteav.demo.superplayer.SuperPlayerDef;
import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo;
import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo;
import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality;
import java.util.List;
/**
* 播放控制接口
*/
public interface Player {
/**
* 设置回调
*
* @param callback 回调接口实现对象
*/
void setCallback(Callback callback);
/**
* 设置水印
*
* @param bmp 水印图
* @param x 水印的x坐标
* @param y 水印的y坐标
*/
void setWatermark(Bitmap bmp, float x, float y);
/**
* 显示控件
*/
void show();
/**
* 隐藏控件
*/
void hide();
/**
* 释放控件的内存
*/
void release();
/**
* 更新播放状态
*
* @param playState 正在播放{@link SuperPlayerDef.PlayerState#PLAYING}
* 正在加载{@link SuperPlayerDef.PlayerState#LOADING}
* 暂停 {@link SuperPlayerDef.PlayerState#PAUSE}
* 播放结束{@link SuperPlayerDef.PlayerState#END}
*/
void updatePlayState(SuperPlayerDef.PlayerState playState);
/**
* 设置视频画质信息
*
* @param list 画质列表
*/
void setVideoQualityList(List<VideoQuality> list);
/**
* 更新视频名称
*
* @param title 视频名称
*/
void updateTitle(String title);
/**
* 更新是屁播放进度
*
* @param current 当前进度(秒)
* @param duration 视频总时长(秒)
*/
void updateVideoProgress(long current, long duration);
/**
* 更新播放类型
*
* @param type 点播 {@link SuperPlayerDef.PlayerType#VOD}
* 点播 {@link SuperPlayerDef.PlayerType#LIVE}
* 直播回看 {@link SuperPlayerDef.PlayerType#LIVE_SHIFT}
*/
void updatePlayType(SuperPlayerDef.PlayerType type);
/**
* 设置背景
*
* @param bitmap 背景图
*/
void setBackground(final Bitmap bitmap);
/**
* 显示背景
*/
void showBackground();
/**
* 隐藏背景
*/
void hideBackground();
/**
* 更新视频播放画质
*
* @param videoQuality 画质
*/
void updateVideoQuality(VideoQuality videoQuality);
/**
* 更新雪碧图信息
*
* @param info 雪碧图信息
*/
void updateImageSpriteInfo(PlayImageSpriteInfo info);
/**
* 更新关键帧信息
*
* @param list 关键帧信息列表
*/
void updateKeyFrameDescInfo(List<PlayKeyFrameDescInfo> list);
/**
* 播放控制回调接口
*/
interface Callback {
/**
* 切换播放模式回调
*
* @param playMode 切换后的播放模式:
* 窗口模式 {@link SuperPlayerDef.PlayerMode#WINDOW }
* 全屏模式 {@link SuperPlayerDef.PlayerMode#FULLSCREEN }
* 悬浮窗模式 {@link SuperPlayerDef.PlayerMode#FLOAT }
*/
void onSwitchPlayMode(SuperPlayerDef.PlayerMode playMode);
/**
* 返回点击事件回调
*
* @param playMode 当前播放模式:
* 窗口模式 {@link SuperPlayerDef.PlayerMode#WINDOW }
* 全屏模式 {@link SuperPlayerDef.PlayerMode#FULLSCREEN }
* 悬浮窗模式 {@link SuperPlayerDef.PlayerMode#FLOAT }
*/
void onBackPressed(SuperPlayerDef.PlayerMode playMode);
/**
* 悬浮窗位置更新回调
*
* @param x 悬浮窗x坐标
* @param y 悬浮窗y坐标
*/
void onFloatPositionChange(int x, int y);
/**
* 播放暂停回调
*/
void onPause();
/**
* 播放继续回调
*/
void onResume();
/**
* 播放跳转回调
*
* @param position 跳转的位置(秒)
*/
void onSeekTo(int position);
/**
* 恢复直播回调
*/
void onResumeLive();
/**
* 弹幕开关回调
*
* @param isOpen 开启:true 关闭:false
*/
void onDanmuToggle(boolean isOpen);
/**
* 屏幕截图回调
*/
void onSnapshot();
/**
* 更新画质回调
*
* @param quality 画质
*/
void onQualityChange(VideoQuality quality);
/**
* 更新播放速度回调
*
* @param speedLevel 播放速度
*/
void onSpeedChange(float speedLevel);
/**
* 镜像开关回调
*
* @param isMirror 开启:true 关闭:close
*/
void onMirrorToggle(boolean isMirror);
/**
* 硬件加速开关回调
*
* @param isAccelerate 开启:true 关闭:false
*/
void onHWAccelerationToggle(boolean isAccelerate);
}
}
@@ -0,0 +1,644 @@
package com.tencent.liteav.demo.superplayer.ui.player;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.tencent.liteav.demo.superplayer.SuperPlayerDef;
import com.tencent.liteav.demo.superplayer.model.utils.VideoGestureDetector;
import com.tencent.liteav.demo.superplayer.ui.view.PointSeekBar;
import com.tencent.liteav.demo.superplayer.ui.view.VideoProgressLayout;
import com.tencent.liteav.demo.superplayer.ui.view.VolumeBrightnessProgressLayout;
import org.leanflutter.plugins.flutter_superplayer.R;
/**
* 窗口模式播放控件
*
* 除基本播放控制外,还有手势控制快进快退、手势调节亮度音量等
*
* 1、点击事件监听{@link #onClick(View)}
*
* 2、触摸事件监听{@link #onTouchEvent(MotionEvent)}
*
* 2、进度条事件监听{@link #onProgressChanged(PointSeekBar, int, boolean)}
* {@link #onStartTrackingTouch(PointSeekBar)}
* {@link #onStopTrackingTouch(PointSeekBar)}
*/
public class WindowPlayer extends AbsPlayer implements View.OnClickListener,
PointSeekBar.OnSeekBarChangeListener {
// UI控件
private LinearLayout mLayoutTop; // 顶部标题栏布局
private LinearLayout mLayoutBottom; // 底部进度条所在布局
private ImageView mIvPause; // 暂停播放按钮
private ImageView mIvFullScreen; // 全屏按钮
private TextView mTvTitle; // 视频名称文本
private TextView mTvBackToLive; // 返回直播文本
private ImageView mBackground; // 背景
private ImageView mIvWatermark; // 水印
private TextView mTvCurrent; // 当前进度文本
private TextView mTvDuration; // 总时长文本
private PointSeekBar mSeekBarProgress; // 播放进度条
private LinearLayout mLayoutReplay; // 重播按钮所在布局
private ProgressBar mPbLiveLoading; // 加载圈
private VolumeBrightnessProgressLayout mGestureVolumeBrightnessProgressLayout; // 音量亮度调节布局
private VideoProgressLayout mGestureVideoProgressLayout; // 手势快进提示布局
private GestureDetector mGestureDetector; // 手势检测监听器
private VideoGestureDetector mVideoGestureDetector; // 手势控制工具
private boolean isShowing; // 自身是否可见
private boolean mIsChangingSeekBarProgress; // 进度条是否正在拖动,避免SeekBar由于视频播放的update而跳动
private SuperPlayerDef.PlayerType mPlayType; // 当前播放视频类型
private SuperPlayerDef.PlayerState mCurrentPlayState = SuperPlayerDef.PlayerState.END; // 当前播放状态
private long mDuration; // 视频总时长
private long mLivePushDuration; // 直播推流总时长
private long mProgress; // 当前播放进度
private Bitmap mBackgroundBmp; // 背景图
private Bitmap mWaterMarkBmp; // 水印图
private float mWaterMarkBmpX; // 水印x坐标
private float mWaterMarkBmpY; // 水印y坐标
private long mLastClickTime; // 上次点击事件的时间
public WindowPlayer(Context context) {
super(context);
initialize(context);
}
public WindowPlayer(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context);
}
public WindowPlayer(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context);
}
/**
* 初始化控件、手势检测监听器、亮度/音量/播放进度的回调
*/
private void initialize(Context context) {
initView(context);
mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
togglePlayState();
show();
if (mHideViewRunnable != null) {
removeCallbacks(mHideViewRunnable);
postDelayed(mHideViewRunnable, 7000);
}
return true;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
toggle();
return true;
}
@Override
public boolean onScroll(MotionEvent downEvent, MotionEvent moveEvent, float distanceX, float distanceY) {
if (downEvent == null || moveEvent == null) {
return false;
}
if (mVideoGestureDetector != null && mGestureVolumeBrightnessProgressLayout != null) {
mVideoGestureDetector.check(mGestureVolumeBrightnessProgressLayout.getHeight(), downEvent, moveEvent, distanceX, distanceY);
}
return true;
}
@Override
public boolean onDown(MotionEvent e) {
if (mVideoGestureDetector != null) {
mVideoGestureDetector.reset(getWidth(), mSeekBarProgress.getProgress());
}
return true;
}
});
mGestureDetector.setIsLongpressEnabled(false);
mVideoGestureDetector = new VideoGestureDetector(getContext());
mVideoGestureDetector.setVideoGestureListener(new VideoGestureDetector.VideoGestureListener() {
@Override
public void onBrightnessGesture(float newBrightness) {
if (mGestureVolumeBrightnessProgressLayout != null) {
mGestureVolumeBrightnessProgressLayout.setProgress((int) (newBrightness * 100));
mGestureVolumeBrightnessProgressLayout.setImageResource(R.drawable.superplayer_ic_light_max);
mGestureVolumeBrightnessProgressLayout.show();
}
}
@Override
public void onVolumeGesture(float volumeProgress) {
if (mGestureVolumeBrightnessProgressLayout != null) {
mGestureVolumeBrightnessProgressLayout.setImageResource(R.drawable.superplayer_ic_volume_max);
mGestureVolumeBrightnessProgressLayout.setProgress((int) volumeProgress);
mGestureVolumeBrightnessProgressLayout.show();
}
}
@Override
public void onSeekGesture(int progress) {
mIsChangingSeekBarProgress = true;
if (mGestureVideoProgressLayout != null) {
if (progress > mSeekBarProgress.getMax()) {
progress = mSeekBarProgress.getMax();
}
if (progress < 0) {
progress = 0;
}
mGestureVideoProgressLayout.setProgress(progress);
mGestureVideoProgressLayout.show();
float percentage = ((float) progress) / mSeekBarProgress.getMax();
float currentTime = (mDuration * percentage);
if (mPlayType == SuperPlayerDef.PlayerType.LIVE || mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) {
if (mLivePushDuration > MAX_SHIFT_TIME) {
currentTime = (int) (mLivePushDuration - MAX_SHIFT_TIME * (1 - percentage));
} else {
currentTime = mLivePushDuration * percentage;
}
mGestureVideoProgressLayout.setTimeText(formattedTime((long) currentTime));
} else {
mGestureVideoProgressLayout.setTimeText(formattedTime((long) currentTime) + " / " + formattedTime((long) mDuration));
}
}
if (mSeekBarProgress!= null)
mSeekBarProgress.setProgress(progress);
}
});
}
/**
* 初始化view
*/
private void initView(Context context) {
LayoutInflater.from(context).inflate(R.layout.superplayer_vod_player_window, this);
mLayoutTop = (LinearLayout) findViewById(R.id.superplayer_rl_top);
mLayoutTop.setOnClickListener(this);
mLayoutBottom = (LinearLayout) findViewById(R.id.superplayer_ll_bottom);
mLayoutBottom.setOnClickListener(this);
mLayoutReplay = (LinearLayout) findViewById(R.id.superplayer_ll_replay);
mTvTitle = (TextView) findViewById(R.id.superplayer_tv_title);
mIvPause = (ImageView) findViewById(R.id.superplayer_iv_pause);
mTvCurrent = (TextView) findViewById(R.id.superplayer_tv_current);
mTvDuration = (TextView) findViewById(R.id.superplayer_tv_duration);
mSeekBarProgress = (PointSeekBar) findViewById(R.id.superplayer_seekbar_progress);
mSeekBarProgress.setProgress(0);
mSeekBarProgress.setMax(100);
mIvFullScreen = (ImageView) findViewById(R.id.superplayer_iv_fullscreen);
mTvBackToLive = (TextView) findViewById(R.id.superplayer_tv_back_to_live);
mPbLiveLoading = (ProgressBar) findViewById(R.id.superplayer_pb_live);
mTvBackToLive.setOnClickListener(this);
mIvPause.setOnClickListener(this);
mIvFullScreen.setOnClickListener(this);
mLayoutTop.setOnClickListener(this);
mLayoutReplay.setOnClickListener(this);
mSeekBarProgress.setOnSeekBarChangeListener(this);
mGestureVolumeBrightnessProgressLayout = (VolumeBrightnessProgressLayout)findViewById(R.id.superplayer_gesture_progress);
mGestureVideoProgressLayout = (VideoProgressLayout) findViewById(R.id.superplayer_video_progress_layout);
mBackground = (ImageView)findViewById(R.id.superplayer_small_iv_background);
setBackground(mBackgroundBmp);
mIvWatermark = (ImageView)findViewById(R.id.superplayer_small_iv_water_mark);
}
/**
* 切换播放状态
*
* 双击和点击播放/暂停按钮会触发此方法
*/
private void togglePlayState() {
switch (mCurrentPlayState) {
case PAUSE:
case END:
if (mControllerCallback != null) {
mControllerCallback.onResume();
}
break;
case PLAYING:
case LOADING:
if (mControllerCallback != null) {
mControllerCallback.onPause();
}
mLayoutReplay.setVisibility(View.GONE);
break;
}
show();
}
/**
* 切换自身的可见性
*/
private void toggle() {
if (isShowing) {
hide();
} else {
show();
if (mHideViewRunnable != null) {
removeCallbacks(mHideViewRunnable);
postDelayed(mHideViewRunnable, 7000);
}
}
}
/**
* 设置水印
*
* @param bmp 水印图
* @param x 水印的x坐标
* @param y 水印的y坐标
*/
@Override
public void setWatermark(final Bitmap bmp, float x, float y) {
mWaterMarkBmp = bmp;
mWaterMarkBmpX = x;
mWaterMarkBmpY = y;
if (bmp != null) {
this.post(new Runnable() {
@Override
public void run() {
int width = WindowPlayer.this.getWidth();
int height = WindowPlayer.this.getHeight();
int x = (int) (width * mWaterMarkBmpX) - bmp.getWidth() / 2;
int y = (int) (height * mWaterMarkBmpY) - bmp.getHeight() / 2;
mIvWatermark.setX(x);
mIvWatermark.setY(y);
mIvWatermark.setVisibility(VISIBLE);
setBitmap(mIvWatermark, bmp);
}
});
} else {
mIvWatermark.setVisibility(GONE);
}
}
/**
* 显示控件
*/
@Override
public void show() {
isShowing = true;
mLayoutTop.setVisibility(View.VISIBLE);
mLayoutBottom.setVisibility(View.VISIBLE);
if (mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) {
mTvBackToLive.setVisibility(View.VISIBLE);
}
}
/**
* 隐藏控件
*/
@Override
public void hide() {
isShowing = false;
mLayoutTop.setVisibility(View.GONE);
mLayoutBottom.setVisibility(View.GONE);
if (mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) {
mTvBackToLive.setVisibility(View.GONE);
}
}
@Override
public void updatePlayState(SuperPlayerDef.PlayerState playState) {
switch (playState) {
case PLAYING:
mIvPause.setImageResource(R.drawable.superplayer_ic_vod_pause_normal);
toggleView(mPbLiveLoading, false);
toggleView(mLayoutReplay, false);
break;
case LOADING:
mIvPause.setImageResource(R.drawable.superplayer_ic_vod_pause_normal);
toggleView(mPbLiveLoading, true);
toggleView(mLayoutReplay, false);
break;
case PAUSE:
mIvPause.setImageResource(R.drawable.superplayer_ic_vod_play_normal);
toggleView(mPbLiveLoading, false);
toggleView(mLayoutReplay, false);
break;
case END:
mIvPause.setImageResource(R.drawable.superplayer_ic_vod_play_normal);
toggleView(mPbLiveLoading, false);
toggleView(mLayoutReplay, true);
break;
}
mCurrentPlayState = playState;
}
/**
* 更新视频名称
*
* @param title 视频名称
*/
@Override
public void updateTitle(String title) {
mTvTitle.setText(title);
}
/**
* 更新视频播放进度
*
* @param current 当前进度(秒)
* @param duration 视频总时长(秒)
*/
@Override
public void updateVideoProgress(long current, long duration) {
mProgress = current < 0 ? 0 : current;
mDuration = duration < 0 ? 0 : duration;
mTvCurrent.setText(formattedTime(mProgress));
float percentage = mDuration > 0 ? ((float) mProgress / (float) mDuration) : 1.0f;
if (mProgress == 0) {
mLivePushDuration = 0;
percentage = 0;
}
if (mPlayType == SuperPlayerDef.PlayerType.LIVE || mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) {
mLivePushDuration = mLivePushDuration > mProgress ? mLivePushDuration : mProgress;
long leftTime = mDuration - mProgress;
mDuration = mDuration > MAX_SHIFT_TIME ? MAX_SHIFT_TIME : mDuration;
percentage = 1 - (float) leftTime / (float) mDuration;
}
if (percentage >= 0 && percentage <= 1) {
int progress = Math.round(percentage * mSeekBarProgress.getMax());
if (!mIsChangingSeekBarProgress) {
if (mPlayType == SuperPlayerDef.PlayerType.LIVE) {
mSeekBarProgress.setProgress(mSeekBarProgress.getMax());
} else {
mSeekBarProgress.setProgress(progress);
}
}
mTvDuration.setText(formattedTime(mDuration));
}
}
@Override
public void updatePlayType(SuperPlayerDef.PlayerType type) {
mPlayType = type;
switch (type) {
case VOD:
mTvBackToLive.setVisibility(View.GONE);
mTvDuration.setVisibility(View.VISIBLE);
break;
case LIVE:
mTvBackToLive.setVisibility(View.GONE);
mTvDuration.setVisibility(View.GONE);
mSeekBarProgress.setProgress(100);
break;
case LIVE_SHIFT:
if (mLayoutBottom.getVisibility() == VISIBLE)
mTvBackToLive.setVisibility(View.VISIBLE);
mTvDuration.setVisibility(View.GONE);
break;
}
}
/**
* 设置背景
*
* @param bitmap 背景图
*/
@Override
public void setBackground(final Bitmap bitmap) {
this.post(new Runnable() {
@Override
public void run() {
if (bitmap == null) return;
if (mBackground == null) {
mBackgroundBmp = bitmap;
} else {
setBitmap(mBackground, mBackgroundBmp);
}
}
});
}
/**
* 设置目标ImageView显示的图片
*/
private void setBitmap(ImageView view, Bitmap bitmap) {
if (view == null || bitmap == null) return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setBackground(new BitmapDrawable(getContext().getResources(), bitmap));
} else {
view.setBackgroundDrawable(new BitmapDrawable(getContext().getResources(), bitmap));
}
}
/**
* 显示背景
*/
@Override
public void showBackground() {
post(new Runnable() {
@Override
public void run() {
ValueAnimator alpha = ValueAnimator.ofFloat(0.0f, 1);
alpha.setDuration(500);
alpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue();
mBackground.setAlpha(value);
if (value == 1) {
mBackground.setVisibility(VISIBLE);
}
}
});
alpha.start();
}
});
}
/**
* 隐藏背景
*/
@Override
public void hideBackground() {
post(new Runnable() {
@Override
public void run() {
if (mBackground.getVisibility() != View.VISIBLE) return;
ValueAnimator alpha = ValueAnimator.ofFloat(1.0f, 0.0f);
alpha.setDuration(500);
alpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue();
mBackground.setAlpha(value);
if (value == 0) {
mBackground.setVisibility(GONE);
}
}
});
alpha.start();
}
});
}
/**
* 重写触摸事件监听,实现手势调节亮度、音量以及播放进度
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mGestureDetector != null)
mGestureDetector.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP && mVideoGestureDetector != null && mVideoGestureDetector.isVideoProgressModel()) {
int progress = mVideoGestureDetector.getVideoProgress();
if (progress > mSeekBarProgress.getMax()) {
progress = mSeekBarProgress.getMax();
}
if (progress < 0) {
progress = 0;
}
mSeekBarProgress.setProgress(progress);
int seekTime;
float percentage = progress * 1.0f / mSeekBarProgress.getMax();
if (mPlayType == SuperPlayerDef.PlayerType.LIVE || mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) {
if (mLivePushDuration > MAX_SHIFT_TIME) {
seekTime = (int) (mLivePushDuration - MAX_SHIFT_TIME * (1 - percentage));
} else {
seekTime = (int) (mLivePushDuration * percentage);
}
}else {
seekTime = (int) (percentage * mDuration);
}
if (mControllerCallback != null) {
mControllerCallback.onSeekTo(seekTime);
}
mIsChangingSeekBarProgress = false;
}
if(event.getAction() == MotionEvent.ACTION_DOWN) {
removeCallbacks(mHideViewRunnable);
} else if(event.getAction() == MotionEvent.ACTION_UP) {
postDelayed(mHideViewRunnable, 7000);
}
return true;
}
/**
* 设置点击事件监听
*/
@Override
public void onClick(View view) {
if (System.currentTimeMillis() - mLastClickTime < 300) { //限制点击频率
return;
}
mLastClickTime = System.currentTimeMillis();
int id = view.getId();
if (id == R.id.superplayer_rl_top) { //顶部标题栏
if (mControllerCallback != null) {
mControllerCallback.onBackPressed(SuperPlayerDef.PlayerMode.WINDOW);
}
} else if (id == R.id.superplayer_iv_pause) { //暂停\播放按钮
togglePlayState();
} else if (id == R.id.superplayer_iv_fullscreen) { //全屏按钮
if (mControllerCallback != null) {
mControllerCallback.onSwitchPlayMode(SuperPlayerDef.PlayerMode.FULLSCREEN);
}
} else if (id == R.id.superplayer_ll_replay) { //重播按钮
if (mControllerCallback != null) {
mControllerCallback.onResume();
}
} else if (id == R.id.superplayer_tv_back_to_live) { //返回直播按钮
if (mControllerCallback != null) {
mControllerCallback.onResumeLive();
}
}
}
@Override
public void onProgressChanged(PointSeekBar seekBar, int progress, boolean fromUser) {
if (mGestureVideoProgressLayout != null && fromUser) {
mGestureVideoProgressLayout.show();
float percentage = ((float) progress) / seekBar.getMax();
float currentTime = (mDuration * percentage);
if (mPlayType == SuperPlayerDef.PlayerType.LIVE || mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) {
if (mLivePushDuration > MAX_SHIFT_TIME) {
currentTime = (int) (mLivePushDuration - MAX_SHIFT_TIME * (1 - percentage));
} else {
currentTime = mLivePushDuration * percentage;
}
mGestureVideoProgressLayout.setTimeText(formattedTime((long) currentTime));
} else {
mGestureVideoProgressLayout.setTimeText(formattedTime((long) currentTime) + " / " + formattedTime((long) mDuration));
}
mGestureVideoProgressLayout.setProgress(progress);
}
}
@Override
public void onStartTrackingTouch(PointSeekBar seekBar) {
removeCallbacks(mHideViewRunnable);
}
@Override
public void onStopTrackingTouch(PointSeekBar seekBar) {
int curProgress = seekBar.getProgress();
int maxProgress = seekBar.getMax();
switch (mPlayType) {
case VOD:
if (curProgress >= 0 && curProgress <= maxProgress) {
// 关闭重播按钮
toggleView(mLayoutReplay, false);
float percentage = ((float) curProgress) / maxProgress;
int position = (int) (mDuration * percentage);
if (mControllerCallback != null) {
mControllerCallback.onSeekTo(position);
mControllerCallback.onResume();
}
}
break;
case LIVE:
case LIVE_SHIFT:
toggleView(mPbLiveLoading, true);
int seekTime = (int) (mLivePushDuration * curProgress * 1.0f / maxProgress);
if (mLivePushDuration > MAX_SHIFT_TIME) {
seekTime = (int) (mLivePushDuration - MAX_SHIFT_TIME * (maxProgress - curProgress) * 1.0f / maxProgress);
}
if (mControllerCallback != null) {
mControllerCallback.onSeekTo(seekTime);
}
break;
}
postDelayed(mHideViewRunnable, 7000);
}
public void hideReplay() {
mLayoutReplay.setVisibility(View.GONE);
}
}
@@ -0,0 +1,182 @@
package com.tencent.liteav.demo.superplayer.ui.view;
import android.content.Context;
import android.graphics.Color;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.AttributeSet;
import com.tencent.liteav.basic.log.TXCLog;
import java.util.Random;
import master.flame.danmaku.controller.DrawHandler;
import master.flame.danmaku.danmaku.model.BaseDanmaku;
import master.flame.danmaku.danmaku.model.DanmakuTimer;
import master.flame.danmaku.danmaku.model.IDanmakus;
import master.flame.danmaku.danmaku.model.android.DanmakuContext;
import master.flame.danmaku.danmaku.model.android.Danmakus;
import master.flame.danmaku.danmaku.parser.BaseDanmakuParser;
import master.flame.danmaku.ui.widget.DanmakuView;
/**
* Created by liyuejiao on 2018/1/29.
*
* 全功能播放器中的弹幕View
*
* 1、随机发送弹幕{@link #addDanmaku(String, boolean)}
*
* 2、弹幕操作所在线程的Handler{@link DanmuHandler}
*/
public class DanmuView extends DanmakuView {
private Context mContext;
private DanmakuContext mDanmakuContext;
private boolean mShowDanma; // 弹幕是否开启
private HandlerThread mHandlerThread; // 发送弹幕的线程
private DanmuHandler mDanmuHandler; // 弹幕线程handler
public DanmuView(Context context) {
super(context);
init(context);
}
public DanmuView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public DanmuView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
mContext = context;
enableDanmakuDrawingCache(true);
setCallback(new DrawHandler.Callback() {
@Override
public void prepared() {
mShowDanma = true;
start();
generateDanmaku();
}
@Override
public void updateTimer(DanmakuTimer timer) {
}
@Override
public void danmakuShown(BaseDanmaku danmaku) {
}
@Override
public void drawingFinished() {
}
});
mDanmakuContext = DanmakuContext.create();
prepare(mParser, mDanmakuContext);
}
@Override
public void release() {
super.release();
mShowDanma = false;
if (mDanmuHandler != null) {
mDanmuHandler.removeCallbacksAndMessages(null);
mDanmuHandler = null;
}
if (mHandlerThread != null) {
mHandlerThread.quit();
mHandlerThread = null;
}
}
private BaseDanmakuParser mParser = new BaseDanmakuParser() {
@Override
protected IDanmakus parse() {
return new Danmakus();
}
};
/**
* 随机生成一些弹幕内容以供测试
*/
private void generateDanmaku() {
mHandlerThread = new HandlerThread("Danmu");
mHandlerThread.start();
mDanmuHandler = new DanmuHandler(mHandlerThread.getLooper());
}
/**
* 向弹幕View中添加一条弹幕
*
* @param content 弹幕的具体内容
* @param withBorder 弹幕是否有边框
*/
private void addDanmaku(String content, boolean withBorder) {
BaseDanmaku danmaku = mDanmakuContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
if (danmaku != null) {
danmaku.text = content;
danmaku.padding = 5;
danmaku.textSize = sp2px(mContext, 20.0f);
danmaku.textColor = Color.WHITE;
danmaku.setTime(getCurrentTime());
if (withBorder) {
danmaku.borderColor = Color.GREEN;
}
addDanmaku(danmaku);
}
}
/**
* sp单位转px
*
* @param context
* @param spValue
* @return
*/
public int sp2px(Context context, float spValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (spValue * scale + 0.5f);
}
public void toggle(boolean on) {
TXCLog.i(TAG, "onToggleControllerView on:" + on);
if (on) {
mDanmuHandler.sendEmptyMessageAtTime(DanmuHandler.MSG_SEND_DANMU, 100);
} else {
mDanmuHandler.removeMessages(DanmuHandler.MSG_SEND_DANMU);
}
}
public class DanmuHandler extends Handler {
public static final int MSG_SEND_DANMU = 1001;
public DanmuHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SEND_DANMU:
sendDanmu();
int time = new Random().nextInt(1000);
mDanmuHandler.sendEmptyMessageDelayed(MSG_SEND_DANMU, time);
break;
}
}
private void sendDanmu() {
int time = new Random().nextInt(300);
String content = "弹幕" + time + time;
addDanmaku(content, false);
}
}
}
@@ -0,0 +1,553 @@
package com.tencent.liteav.demo.superplayer.ui.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
import org.leanflutter.plugins.flutter_superplayer.R;
import java.util.List;
/**
* 一个带有打点的,模仿seekbar的view
*
* 除seekbar基本功能外,还具备关键帧信息打点的功能
*
* 1、添加打点信息{@link #addPoint(PointParams, int)}
*
* 2、自定义thumb{@link TCThumbView}
*
* 3、打点view{@link TCPointView}
*
* 4、打点信息参数{@link PointParams}
*/
public class PointSeekBar extends RelativeLayout {
private int mWidth; // 自身宽度
private int mHeight; // 自身高度
private int mSeekBarLeft; // SeekBar的起点位置
private int mSeekBarRight; // SeekBar的终点位置
private int mBgTop; // 进度条距离父布局上边界的距离
private int mBgBottom; // 进度条距离父布局下边界的距离
private int mRoundSize; // 进度条圆角大小
private int mViewEnd; // 自身的右边界
private Paint mNormalPaint; // seekbar背景画笔
private Paint mProgressPaint; // seekbar进度条画笔
private Paint mPointerPaint; // 打点view画笔
private Drawable mThumbDrawable; // 拖动块图片
private int mHalfDrawableWidth; // Thumb图片宽度的一半
// Thumb距父布局中的位置
private float mThumbLeft; // thumb的marginLeft值
private float mThumbRight; // thumb的marginRight值
private float mThumbTop; // thumb的marginTop值
private float mThumbBottom; // thumb的marginBottom值
private boolean mIsOnDrag; // 是否处于拖动状态
private float mCurrentLeftOffset = 0; // thumb距离打点view的偏移量
private float mLastX; // 上一次点击事件的横坐标,用于计算偏移量
private int mCurrentProgress; // 当前seekbar的数值
private int mMaxProgress = 100; // seekbar最大数值
private float mBarHeightPx = 0; // seekbar的高度大小 px
private TCThumbView mThumbView; // 滑动ThumbView
private List<PointParams> mPointList; // 打点信息的列表
private OnSeekBarPointClickListener mPointClickListener; // 打点view点击回调
private boolean mIsChangePointViews; // 打点信息是否更新过
public PointSeekBar(Context context) {
super(context);
init(null);
}
public PointSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public PointSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
/**
* 设置seekbar进度值
*
* @param progress
*/
public void setProgress(int progress) {
if (progress < 0) {
progress = 0;
}
if (progress > mMaxProgress) {
progress = mMaxProgress;
}
if (!mIsOnDrag) {
mCurrentProgress = progress;
invalidate();
callbackProgressInternal(progress, false);
}
}
/**
* 设置seekbar最大值
*
* @param max
*/
public void setMax(int max) {
mMaxProgress = max;
}
/**
* 获取seekbar进度值
*
* @return
*/
public int getProgress() {
return mCurrentProgress;
}
/**
* 获取seekbar最大值
*
* @return
*/
public int getMax() {
return mMaxProgress;
}
private void init(AttributeSet attrs) {
setWillNotDraw(false);
int progressColor = getResources().getColor(R.color.superplayer_default_progress_color);
int backgroundColor = getResources().getColor(R.color.superplayer_default_progress_background_color);
if (attrs != null) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.SuperPlayerTCPointSeekBar);
mThumbDrawable = a.getDrawable(R.styleable.SuperPlayerTCPointSeekBar_psb_thumbBackground);
mHalfDrawableWidth = mThumbDrawable.getIntrinsicWidth() / 2;
progressColor = a.getColor(R.styleable.SuperPlayerTCPointSeekBar_psb_progressColor, progressColor);
backgroundColor = a.getColor(R.styleable.SuperPlayerTCPointSeekBar_psb_backgroundColor, backgroundColor);
mCurrentProgress = a.getInt(R.styleable.SuperPlayerTCPointSeekBar_psb_progress, 0);
mMaxProgress = a.getInt(R.styleable.SuperPlayerTCPointSeekBar_psb_max, 100);
mBarHeightPx = a.getDimension(R.styleable.SuperPlayerTCPointSeekBar_psb_progressHeight, 8);
a.recycle();
}
mNormalPaint = new Paint();
mNormalPaint.setColor(backgroundColor);
mPointerPaint = new Paint();
mPointerPaint.setColor(Color.RED);
mProgressPaint = new Paint();
mProgressPaint.setColor(progressColor);
this.post(new Runnable() {
@Override
public void run() {
addThumbView();
}
});
}
private void changeThumbPos() {
LayoutParams params = (LayoutParams) mThumbView.getLayoutParams();
params.leftMargin = (int) mThumbLeft;
params.topMargin = (int) mThumbTop;
mThumbView.setLayoutParams(params);
}
private void addThumbView() {
mThumbView = new TCThumbView(getContext(), mThumbDrawable);
LayoutParams thumbParams = new LayoutParams(mThumbDrawable.getIntrinsicHeight(), mThumbDrawable.getIntrinsicHeight());
mThumbView.setLayoutParams(thumbParams);
addView(mThumbView);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
mSeekBarLeft = mHalfDrawableWidth;
mSeekBarRight = mWidth - mHalfDrawableWidth;
float barPaddingTop = (mHeight - mBarHeightPx) / 2;
mBgTop = (int) barPaddingTop;
mBgBottom = (int) (mHeight - barPaddingTop);
mRoundSize = mHeight / 2;
mViewEnd = mWidth;
}
private void calProgressDis() {
float dis = (mSeekBarRight - mSeekBarLeft) * (mCurrentProgress * 1.0f / mMaxProgress);
mThumbLeft = dis;
mLastX = mThumbLeft;
mCurrentLeftOffset = 0;
calculatePointerRect();
}
private void addThumbAndPointViews() {
this.post(new Runnable() {
@Override
public void run() {
if (mIsChangePointViews) {
PointSeekBar.this.removeAllViews();
if (mPointList != null) {
for (int i = 0; i < mPointList.size(); i++) {
PointParams params = mPointList.get(i);
addPoint(params, i);
}
}
addThumbView();
mIsChangePointViews = false;
}
if(!mIsOnDrag) {
calProgressDis();
changeThumbPos();
}
}
});
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//draw bg
RectF rectF = new RectF();
rectF.left = mSeekBarLeft;
rectF.right = mSeekBarRight;
rectF.top = mBgTop;
rectF.bottom = mBgBottom;
canvas.drawRoundRect(rectF, mRoundSize, mRoundSize, mNormalPaint);
//draw progress
RectF pRecf = new RectF();
pRecf.left = mSeekBarLeft;
pRecf.top = mBgTop;
pRecf.right = mThumbRight - mHalfDrawableWidth;
pRecf.bottom = mBgBottom;
canvas.drawRoundRect(pRecf,
mRoundSize, mRoundSize, mProgressPaint);
addThumbAndPointViews();
}
/**
* 添加打点view
*
* @param pointParams
* @param index
*/
public void addPoint(PointParams pointParams, final int index) {
float percent = pointParams.progress * 1.0f / mMaxProgress;
int pointSize = mBgBottom - mBgTop;
float leftMargin = percent * (mSeekBarRight - mSeekBarLeft);
float rectLeft = (mThumbDrawable.getIntrinsicWidth() - pointSize) / 2;
float rectTop = mBgTop;
float rectBottom = mBgBottom;
float rectRight = rectLeft + pointSize;
final TCPointView view = new TCPointView(getContext());
LayoutParams params = new LayoutParams(mThumbDrawable.getIntrinsicWidth(), mThumbDrawable.getIntrinsicWidth());
params.leftMargin = (int) leftMargin;
view.setDrawRect(rectLeft, rectTop, rectBottom, rectRight);
view.setLayoutParams(params);
view.setColor(pointParams.color);
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mPointClickListener != null) {
mPointClickListener.onSeekBarPointClick(view, index);
}
}
});
addView(view);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) return false;
boolean isHandle = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isHandle = handleDownEvent(event);
break;
case MotionEvent.ACTION_MOVE:
isHandle = handleMoveEvent(event);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
isHandle = handleUpEvent(event);
break;
}
return isHandle;
}
private boolean handleUpEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
if (mIsOnDrag) {
mIsOnDrag = false;
if (mListener != null) {
mListener.onStopTrackingTouch(this);
}
return true;
}
return false;
}
private boolean handleMoveEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
if (mIsOnDrag) {
mCurrentLeftOffset = x - mLastX;
//计算出标尺的Rect
calculatePointerRect();
if (mThumbRight - mHalfDrawableWidth <= mSeekBarLeft) {
mThumbLeft = 0;
mThumbRight = mThumbLeft + mThumbDrawable.getIntrinsicWidth();
}
if (mThumbLeft + mHalfDrawableWidth >= mSeekBarRight) {
mThumbRight = mWidth;
mThumbLeft = mWidth - mThumbDrawable.getIntrinsicWidth();
}
changeThumbPos();
invalidate();
callbackProgress();
mLastX = x;
return true;
}
return false;
}
private void callbackProgress() {
if (mThumbLeft == 0) {
callbackProgressInternal(0, true);
} else if (mThumbRight == mWidth) {
callbackProgressInternal(mMaxProgress, true);
} else {
float pointerMiddle = mThumbLeft + mHalfDrawableWidth;
if (pointerMiddle >= mViewEnd) {
callbackProgressInternal(mMaxProgress, true);
} else {
float percent = pointerMiddle / mViewEnd * 1.0f;
int progress = (int) (percent * mMaxProgress);
if (progress > mMaxProgress) {
progress = mMaxProgress;
}
callbackProgressInternal(progress, true);
}
}
}
private void callbackProgressInternal(int progress, boolean isFromUser) {
mCurrentProgress = progress;
if (mListener != null) {
mListener.onProgressChanged(this, progress, isFromUser);
}
}
private boolean handleDownEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
if (x >= mThumbLeft - 100 && x <= mThumbRight + 100) {
if (mListener != null)
mListener.onStartTrackingTouch(this);
mIsOnDrag = true;
mLastX = x;
return true;
}
return false;
}
private void calculatePointerRect() {
//draw pointer
float pointerLeft = getPointerLeft(mCurrentLeftOffset);
float pointerRight = pointerLeft + mThumbDrawable.getIntrinsicWidth();
mThumbLeft = pointerLeft;
mThumbRight = pointerRight;
mThumbTop = 0;
mThumbBottom = mHeight;
}
private float getPointerLeft(float offset) {
return mThumbLeft + offset;
}
private OnSeekBarChangeListener mListener;
public void setOnSeekBarChangeListener(OnSeekBarChangeListener listener) {
mListener = listener;
}
public interface OnSeekBarChangeListener {
void onProgressChanged(PointSeekBar seekBar, int progress, boolean fromUser);
void onStartTrackingTouch(PointSeekBar seekBar);
void onStopTrackingTouch(PointSeekBar seekBar);
}
/**
* 设置监听
*
* @param listener
*/
public void setOnPointClickListener(OnSeekBarPointClickListener listener) {
mPointClickListener = listener;
}
/**
* 打点view点击回调
*/
public interface OnSeekBarPointClickListener {
void onSeekBarPointClick(View view, int pos);
}
/**
* 设置打点信息列表
*
* @param pointList
*/
public void setPointList(List<PointParams> pointList) {
mPointList = pointList;
mIsChangePointViews = true;
invalidate();
}
/**
* 打点信息
*/
public static class PointParams {
int progress = 0; // 视频进度值(秒)
int color = Color.RED; // 打点view的颜色
public PointParams(int progress, int color) {
this.progress = progress;
this.color = color;
}
}
/**
* 打点view
*/
private static class TCPointView extends View {
private int mColor = Color.WHITE; // view颜色
private Paint mPaint; // 画笔
private RectF mRectF; // 打点view的位置信息(矩形)
public TCPointView(Context context) {
super(context);
init();
}
public TCPointView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public TCPointView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(mColor);
mRectF = new RectF();
}
/**
* 设置打点颜色
*
* @param color
*/
public void setColor(int color) {
mColor = color;
mPaint.setColor(mColor);
}
/**
* 设置打点view的位置信息
*
* @param left
* @param top
* @param right
* @param bottom
*/
public void setDrawRect(float left, float top, float right, float bottom) {
mRectF.left = left;
mRectF.top = top;
mRectF.right = right;
mRectF.bottom = bottom;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(mRectF, mPaint);
}
}
/**
* 拖动块view
*/
private static class TCThumbView extends View {
private Paint mPaint; // 画笔
private Rect mRect; // 位置信息(矩形)
private Drawable mThumbDrawable;// thumb图片
public TCThumbView(Context context, Drawable drawable) {
super(context);
mThumbDrawable = drawable;
mPaint = new Paint();
mPaint.setAntiAlias(true);
mRect = new Rect();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mRect.left = 0;
mRect.top = 0;
mRect.right = w;
mRect.bottom = h;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mThumbDrawable.setBounds(mRect);
mThumbDrawable.draw(canvas);
}
}
}
@@ -0,0 +1,111 @@
package com.tencent.liteav.demo.superplayer.ui.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import org.leanflutter.plugins.flutter_superplayer.R;
/**
* 滑动手势控制播放进度时显示的进度提示view
*/
public class VideoProgressLayout extends RelativeLayout {
private ImageView mIvThumbnail; // 视频缩略图
private TextView mTvTime; // 视频进度文本
private ProgressBar mProgressBar; // 进度条
private HideRunnable mHideRunnable; // 隐藏自身的线程
private int duration = 1000; // 自身消失的延迟事件ms
public VideoProgressLayout(Context context) {
super(context);
init(context);
}
public VideoProgressLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
LayoutInflater.from(context).inflate(R.layout.superplayer_video_progress_layout, this);
mIvThumbnail = (ImageView) findViewById(R.id.superplayer_iv_progress_thumbnail);
mProgressBar = (ProgressBar) findViewById(R.id.superplayer_pb_progress_bar);
mTvTime = (TextView) findViewById(R.id.superplayer_tv_progress_time);
setVisibility(GONE);
mHideRunnable = new HideRunnable();
}
/**
* 显示view
*/
public void show() {
setVisibility(VISIBLE);
removeCallbacks(mHideRunnable);
postDelayed(mHideRunnable, duration);
}
/**
* 设置视频进度事件文本
*
* @param text
*/
public void setTimeText(String text) {
mTvTime.setText(text);
}
/**
* 设置progressbar的进度值
*
* @param progress
*/
public void setProgress(int progress) {
mProgressBar.setProgress(progress);
}
/**
* 设置view消失延迟的时间
*
* @param duration
*/
public void setDuration(int duration) {
this.duration = duration;
}
/**
* 设置缩略图图片
*
* @param bitmap
*/
public void setThumbnail(Bitmap bitmap) {
mIvThumbnail.setVisibility(VISIBLE);
mIvThumbnail.setImageBitmap(bitmap);
}
/**
* 设置progressbar的可见性
*
* @param enable
*/
public void setProgressVisibility(boolean enable) {
mProgressBar.setVisibility(enable ? VISIBLE : GONE);
}
/**
* 隐藏view的线程
*/
private class HideRunnable implements Runnable {
@Override
public void run() {
mIvThumbnail.setImageBitmap(null);
mIvThumbnail.setVisibility(GONE);
VideoProgressLayout.this.setVisibility(GONE);
}
}
}
@@ -0,0 +1,372 @@
package com.tencent.liteav.demo.superplayer.ui.view;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.CompoundButton;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.Switch;
import com.tencent.liteav.demo.superplayer.SuperPlayerDef;
import com.tencent.liteav.demo.superplayer.SuperPlayerGlobalConfig;
import org.leanflutter.plugins.flutter_superplayer.R;
/**
* Created by yuejiaoli on 2018/7/4.
*
* 更多选项弹框
*
* 1、声音调节seekBar回调{@link #mVolumeChangeListener}
*
* 2、亮度调节seekBar回调{@link #mLightChangeListener}
*
* 3、倍速选择回调{@link #onCheckedChanged(RadioGroup, int)}
*
* 4、镜像、硬件加速开关回调{@link #onCheckedChanged(CompoundButton, boolean)}
*/
public class VodMoreView extends RelativeLayout implements RadioGroup.OnCheckedChangeListener, CompoundButton.OnCheckedChangeListener {
private static final String VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION";
private static final String EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE";
private Context mContext;
private SeekBar mSeekBarVolume; // 音量seekBar
private SeekBar mSeekBarLight; // 亮度seekBar
private Switch mSwitchMirror; // 镜像开关
private Switch mSwitchAccelerate; // 硬解开关
private Callback mCallback; // 回调
private AudioManager mAudioManager; // 音频管理器
private RadioGroup mRadioGroup; // 倍速选择radioGroup
private RadioButton mRbSpeed1; // 1.0倍速按钮
private RadioButton mRbSpeed125; // 1.25倍速按钮
private RadioButton mRbSpeed15; // 1.5倍速按钮
private RadioButton mRbSpeed2; // 2.0倍速按钮
private LinearLayout mLayoutSpeed; // 倍速按钮所在布局
private LinearLayout mLayoutMirror; // 镜像按钮所在布局
private VolumeBroadcastReceiver mVolumeBroadcastReceiver;
public VodMoreView(Context context) {
super(context);
init(context);
}
public VodMoreView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public VodMoreView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mContext = context;
LayoutInflater.from(mContext).inflate(R.layout.superplayer_more_popup_view, this);
mLayoutSpeed = (LinearLayout) findViewById(R.id.superplayer_ll_speed);
mRadioGroup = (RadioGroup) findViewById(R.id.superplayer_rg);
mRbSpeed1 = (RadioButton) findViewById(R.id.superplayer_rb_speed1);
mRbSpeed125 = (RadioButton) findViewById(R.id.superplayer_rb_speed125);
mRbSpeed15 = (RadioButton) findViewById(R.id.superplayer_rb_speed15);
mRbSpeed2 = (RadioButton) findViewById(R.id.superplayer_rb_speed2);
mRadioGroup.setOnCheckedChangeListener(this);
mSeekBarVolume = (SeekBar) findViewById(R.id.superplayer_sb_audio);
mSeekBarLight = (SeekBar) findViewById(R.id.superplayer_sb_light);
mLayoutMirror = (LinearLayout) findViewById(R.id.superplayer_ll_mirror);
mSwitchMirror = (Switch) findViewById(R.id.superplayer_switch_mirror);
mSwitchAccelerate = (Switch) findViewById(R.id.superplayer_switch_accelerate);
SuperPlayerGlobalConfig config = SuperPlayerGlobalConfig.getInstance();
mSwitchAccelerate.setChecked(config.enableHWAcceleration);
mSeekBarVolume.setOnSeekBarChangeListener(mVolumeChangeListener);
mSeekBarLight.setOnSeekBarChangeListener(mLightChangeListener);
mSwitchMirror.setOnCheckedChangeListener(this);
mSwitchAccelerate.setOnCheckedChangeListener(this);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
updateCurrentVolume();
updateCurrentLight();
}
private void updateCurrentVolume() {
int curVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
float percentage = (float) curVolume / maxVolume;
final int progress = (int) (percentage * mSeekBarVolume.getMax());
mSeekBarVolume.setProgress(progress);
}
private void updateCurrentLight() {
Activity activity = (Activity) mContext;
Window window = activity.getWindow();
WindowManager.LayoutParams params = window.getAttributes();
params.screenBrightness = getActivityBrightness((Activity) mContext);
window.setAttributes(params);
if (params.screenBrightness == -1) {
mSeekBarLight.setProgress(100);
return;
}
mSeekBarLight.setProgress((int) (params.screenBrightness * 100));
}
/**
* 获取当前亮度
*
* @param activity
* @return
*/
public static float getActivityBrightness(Activity activity) {
Window localWindow = activity.getWindow();
WindowManager.LayoutParams params = localWindow.getAttributes();
return params.screenBrightness;
}
private SeekBar.OnSeekBarChangeListener mVolumeChangeListener = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
updateVolumeProgress(progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
};
private void updateVolumeProgress(int progress) {
float percentage = (float) progress / mSeekBarVolume.getMax();
if (percentage < 0 || percentage > 1)
return;
if (mAudioManager != null) {
int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
int newVolume = (int) (percentage * maxVolume);
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newVolume, 0);
}
}
private SeekBar.OnSeekBarChangeListener mLightChangeListener = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
updateBrightProgress(progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
};
private void updateBrightProgress(int progress) {
Activity activity = (Activity) mContext;
Window window = activity.getWindow();
WindowManager.LayoutParams params = window.getAttributes();
params.screenBrightness = progress * 1.0f / 100;
if (params.screenBrightness > 1.0f) {
params.screenBrightness = 1.0f;
}
if (params.screenBrightness <= 0.01f) {
params.screenBrightness = 0.01f;
}
window.setAttributes(params);
mSeekBarLight.setProgress(progress);
}
/**
* 镜像、硬解开关监听
*
* @param compoundButton
* @param isChecked
*/
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
if (compoundButton.getId() == R.id.superplayer_switch_mirror) {
if (mCallback != null) {
mCallback.onMirrorChange(isChecked);
}
} else if (compoundButton.getId() == R.id.superplayer_switch_accelerate) {
SuperPlayerGlobalConfig config = SuperPlayerGlobalConfig.getInstance();
config.enableHWAcceleration = !config.enableHWAcceleration;
mSwitchAccelerate.setChecked(config.enableHWAcceleration);
if (mCallback != null) {
mCallback.onHWAcceleration(config.enableHWAcceleration);
}
}
}
/**
* 设置回调
*
* @param callback
*/
public void setCallback(Callback callback) {
mCallback = callback;
}
/**
* 倍速选择监听
*
* @param radioGroup
* @param checkedId
*/
@Override
public void onCheckedChanged(RadioGroup radioGroup, int checkedId) {
if (checkedId == R.id.superplayer_rb_speed1) {
mRbSpeed1.setChecked(true);
if (mCallback != null) {
mCallback.onSpeedChange(1.0f);
}
} else if (checkedId == R.id.superplayer_rb_speed125) {
mRbSpeed125.setChecked(true);
if (mCallback != null) {
mCallback.onSpeedChange(1.25f);
}
} else if (checkedId == R.id.superplayer_rb_speed15) {
mRbSpeed15.setChecked(true);
if (mCallback != null) {
mCallback.onSpeedChange(1.5f);
}
} else if (checkedId == R.id.superplayer_rb_speed2) {
mRbSpeed2.setChecked(true);
if (mCallback != null) {
mCallback.onSpeedChange(2.0f);
}
}
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
if (visibility == View.VISIBLE) {
updateCurrentVolume();
updateCurrentLight();
registerReceiver();
}else {
unregisterReceiver();
}
}
public void setBrightProgress(int progress) {
updateBrightProgress(progress);
}
/**
* 更新播放视频类型
*
* @param playType
*/
public void updatePlayType(SuperPlayerDef.PlayerType playType) {
if (playType == SuperPlayerDef.PlayerType.VOD) {
mLayoutSpeed.setVisibility(View.VISIBLE);
mLayoutMirror.setVisibility(View.VISIBLE);
} else {
mLayoutSpeed.setVisibility(View.GONE);
mLayoutMirror.setVisibility(View.GONE);
}
}
private class VolumeBroadcastReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
//媒体音量改变才通知
if (VOLUME_CHANGED_ACTION.equals(intent.getAction())
&& (intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, -1) == AudioManager.STREAM_MUSIC)) {
updateCurrentVolume();
}
}
}
/**
* 注册音量广播接收器
* @return
*/
public void registerReceiver() {
mVolumeBroadcastReceiver = new VolumeBroadcastReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(VOLUME_CHANGED_ACTION);
mContext.registerReceiver(mVolumeBroadcastReceiver, filter);
}
/**
* 反注册音量广播监听器,需要与 registerReceiver 成对使用
*/
public void unregisterReceiver() {
try {
mContext.unregisterReceiver(mVolumeBroadcastReceiver);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 回调
*/
public interface Callback {
/**
* 播放速度更新回调
*
* @param speedLevel
*/
void onSpeedChange(float speedLevel);
/**
* 镜像开关回调
*
* @param isMirror
*/
void onMirrorChange(boolean isMirror);
/**
* 硬解开关回调
*
* @param isAccelerate
*/
void onHWAcceleration(boolean isAccelerate);
}
}
@@ -0,0 +1,203 @@
package com.tencent.liteav.demo.superplayer.ui.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality;
import org.leanflutter.plugins.flutter_superplayer.R;
import java.util.ArrayList;
import java.util.List;
/**
* Created by yuejiaoli on 2018/7/4.
*
* 视频画质选择弹框
*
* 1、设置画质列表{@link #setVideoQualityList(List)}
*
* 2、设置默认选中的画质{@link #setDefaultSelectedQuality(int)}
*/
public class VodQualityView extends RelativeLayout {
private Context mContext;
private Callback mCallback; // 回调
private ListView mListView; // 画质listView
private QualityAdapter mAdapter; // 画质列表适配器
private List<VideoQuality> mList; // 画质列表
private int mClickPos = -1; // 当前的画质下表
public VodQualityView(Context context) {
super(context);
init(context);
}
public VodQualityView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public VodQualityView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mContext = context;
mList = new ArrayList<VideoQuality>();
LayoutInflater.from(mContext).inflate(R.layout.superplayer_quality_popup_view, this);
mListView = (ListView) findViewById(R.id.superplayer_lv_quality);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (mCallback != null) {
if (mList != null && mList.size() > 0) {
VideoQuality quality = mList.get(position);
if (quality != null && position != mClickPos) {
mCallback.onQualitySelect(quality);
}
}
}
mClickPos = position;
mAdapter.notifyDataSetChanged();
}
});
mAdapter = new QualityAdapter();
mListView.setAdapter(mAdapter);
}
/**
* 设置回调
*
* @param callback
*/
public void setCallback(Callback callback) {
mCallback = callback;
}
/**
* 设置画质列表
*
* @param list
*/
public void setVideoQualityList(List<VideoQuality> list) {
mList.clear();
mList.addAll(list);
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
/**
* 设置默认选中的清晰度
*
* @param position
*/
public void setDefaultSelectedQuality(int position) {
if (position < 0) position = 0;
mClickPos = position;
mAdapter.notifyDataSetChanged();
}
class QualityAdapter extends BaseAdapter {
@Override
public int getCount() {
return mList.size();
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = new QualityItemView(mContext);
}
QualityItemView itemView = (QualityItemView) convertView;
itemView.setSelected(false);
VideoQuality quality = mList.get(position);
itemView.setQualityName(quality.title);
if (mClickPos == position) {
itemView.setSelected(true);
}
return itemView;
}
}
/**
* 画质item view
*/
class QualityItemView extends RelativeLayout {
private TextView mTvQuality;
public QualityItemView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
public QualityItemView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public QualityItemView(Context context) {
super(context);
init(context);
}
private void init(Context context) {
LayoutInflater.from(context).inflate(R.layout.superplayer_quality_item_view, this);
mTvQuality = (TextView) findViewById(R.id.superplayer_tv_quality);
}
/**
* 设置画质名称
*
* @param qualityName
*/
public void setQualityName(String qualityName) {
mTvQuality.setText(qualityName);
}
/**
* 设置画质item是否为选择状态
*
* @param isChecked
*/
public void setSelected(boolean isChecked) {
mTvQuality.setSelected(isChecked);
}
}
/**
* 回调
*/
public interface Callback {
/**
* 画质选择回调
*
* @param quality
*/
void onQualitySelect(VideoQuality quality);
}
}
@@ -0,0 +1,84 @@
package com.tencent.liteav.demo.superplayer.ui.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import org.leanflutter.plugins.flutter_superplayer.R;
/**
* 滑动手势设置音量、亮度时显示的提示view
*/
public class VolumeBrightnessProgressLayout extends RelativeLayout {
private ImageView mImageCenter; // 中心图片:亮度提示、音量提示
private ProgressBar mProgressBar; // 进度条
private HideRunnable mHideRunnable; // 隐藏view的runnable
private int mDuration = 1000; // view消失延迟时间(秒)
public VolumeBrightnessProgressLayout(Context context) {
super(context);
init(context);
}
public VolumeBrightnessProgressLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context){
LayoutInflater.from(context).inflate(R.layout.superplayer_video_volume_brightness_progress_layout,this);
mImageCenter = (ImageView) findViewById(R.id.superplayer_iv_center);
mProgressBar = (ProgressBar) findViewById(R.id.superplayer_pb_progress_bar);
mHideRunnable = new HideRunnable();
setVisibility(GONE);
}
/**
* 显示
*/
public void show(){
setVisibility(VISIBLE);
removeCallbacks(mHideRunnable);
postDelayed(mHideRunnable, mDuration);
}
/**
* 设置progressBar的进度值
*
* @param progress
*/
public void setProgress(int progress){
mProgressBar.setProgress(progress);
}
/**
* 设置view消失的延迟时间
*
* @param duration
*/
public void setDuration(int duration) {
this.mDuration = duration;
}
/**
* 设置显示的图片,亮度提示图片或者音量提示图片
*
* @param resource
*/
public void setImageResource(int resource){
mImageCenter.setImageResource(resource);
}
/**
* 隐藏view的runnable
*/
private class HideRunnable implements Runnable{
@Override
public void run() {
VolumeBrightnessProgressLayout.this.setVisibility(GONE);
}
}
}
@@ -0,0 +1,8 @@
package org.leanflutter.plugins.flutter_superplayer;
public class Constants {
public static final String CHANNEL_NAME = "flutter_superplayer";
public static final String SUPER_PLAYER_VIEW_TYPE = "leanflutter.org/superplayer_view";
public static final String SUPER_PLAYER_VIEW_CHANNEL_NAME = "leanflutter.org/superplayer_view/channel";
public static final String SUPER_PLAYER_VIEW_EVENT_CHANNEL_NAME = "leanflutter.org/superplayer_view/event_channel";
}
@@ -0,0 +1,318 @@
package org.leanflutter.plugins.flutter_superplayer;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import com.tencent.liteav.demo.superplayer.SuperPlayerDef;
import com.tencent.liteav.demo.superplayer.SuperPlayerGlobalConfig;
import com.tencent.liteav.demo.superplayer.SuperPlayerModel;
import com.tencent.liteav.demo.superplayer.SuperPlayerVideoId;
import com.tencent.liteav.demo.superplayer.SuperPlayerView;
import java.util.HashMap;
import java.util.Map;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.EventChannel.StreamHandler;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.platform.PlatformView;
import static org.leanflutter.plugins.flutter_superplayer.Constants.SUPER_PLAYER_VIEW_CHANNEL_NAME;
import static org.leanflutter.plugins.flutter_superplayer.Constants.SUPER_PLAYER_VIEW_EVENT_CHANNEL_NAME;
public class FlutterSuperPlayerView implements PlatformView, MethodCallHandler, StreamHandler, SuperPlayerView.OnSuperPlayerViewCallback {
private final MethodChannel methodChannel;
private final EventChannel eventChannel;
private final Handler platformThreadHandler = new Handler(Looper.getMainLooper());
private EventChannel.EventSink eventSink;
private Context context;
private FrameLayout containerView;
private SuperPlayerView superPlayerView;
private long playProgressCurrent = 0;
FlutterSuperPlayerView(
final Context context,
BinaryMessenger messenger,
int viewId,
Map<String, Object> params) {
this.context = context;
methodChannel = new MethodChannel(messenger, SUPER_PLAYER_VIEW_CHANNEL_NAME + "_" + viewId);
methodChannel.setMethodCallHandler(this);
eventChannel = new EventChannel(messenger, SUPER_PLAYER_VIEW_EVENT_CHANNEL_NAME + "_" + viewId);
eventChannel.setStreamHandler(this);
SuperPlayerGlobalConfig.getInstance().enableFloatWindow = false;
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT,
Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL
);
containerView = new FrameLayout(context);
containerView.setLayoutParams(layoutParams);
superPlayerView = new SuperPlayerView(context);
superPlayerView.setPlayerViewCallback(this);
containerView.addView(superPlayerView);
String controlViewType = (String) params.get("controlViewType");
setControlViewType(controlViewType);
}
@Override
public View getView() {
return containerView;
}
@Override
public void dispose() {
if (superPlayerView.getPlayerState() == SuperPlayerDef.PlayerState.PLAYING) {
superPlayerView.resetPlayer();
}
}
@Override
public void onListen(Object args, EventChannel.EventSink eventSink) {
this.eventSink = eventSink;
}
@Override
public void onCancel(Object args) {
this.eventSink = null;
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
if (call.method.equals("setControlViewType")) {
setControlViewType(call, result);
} else if (call.method.equals("getPlayMode")) {
getPlayMode(call, result);
} else if (call.method.equals("getPlayState")) {
getPlayState(call, result);
} else if (call.method.equals("getPlayRate")) {
getPlayRate(call, result);
} else if (call.method.equals("setPlayRate")) {
setPlayRate(call, result);
} else if (call.method.equals("resetPlayer")) {
resetPlayer(call, result);
} else if (call.method.equals("requestPlayMode")) {
requestPlayMode(call, result);
} else if (call.method.equals("playWithModel")) {
playWithModel(call, result);
} else if (call.method.equals("pause")) {
pause(call, result);
} else if (call.method.equals("resume")) {
resume(call, result);
} else if (call.method.equals("release")) {
release(call, result);
} else if (call.method.equals("seekTo")) {
seekTo(call, result);
} else if (call.method.equals("setLoop")) {
setLoop(call, result);
} else if (call.method.equals("uiHideDanmu")) {
uiHideDanmu(call, result);
} else if (call.method.equals("uiHideReplay")) {
uiHideReplay(call, result);
} else {
result.notImplemented();
}
}
void setControlViewType(String controlViewType) {
superPlayerView.setControlViewType(controlViewType);
}
void setControlViewType(@NonNull MethodCall call, @NonNull Result result) {
String controlViewType = (String) call.argument("controlViewType");
superPlayerView.setControlViewType(controlViewType);
}
void getPlayMode(@NonNull MethodCall call, @NonNull Result result) {
int playMode = superPlayerView.getPlayerMode().ordinal();
result.success(playMode);
}
void getPlayState(@NonNull MethodCall call, @NonNull Result result) {
SuperPlayerDef.PlayerState playerState = superPlayerView.getPlayerState();
result.success(playerState.intValue());
}
void getPlayRate(@NonNull MethodCall call, @NonNull Result result) {
float playRate = superPlayerView.getPlayerRate();
result.success(playRate);
}
void setPlayRate(@NonNull MethodCall call, @NonNull Result result) {
Number playRate = (Number) call.argument("playRate");
superPlayerView.getControllerCallback().onSpeedChange(playRate.floatValue());
}
void resetPlayer(@NonNull MethodCall call, @NonNull Result result) {
superPlayerView.resetPlayer();
}
void requestPlayMode(@NonNull MethodCall call, @NonNull Result result) {
int playMode = (int) call.argument("playMode");
superPlayerView.switchPlayMode(SuperPlayerDef.PlayerMode.values()[playMode]);
}
private void playWithModel(@NonNull MethodCall call, @NonNull Result result) {
SuperPlayerModel model = new SuperPlayerModel();
if (call.hasArgument("appId"))
model.appId = (int) call.argument("appId");
if (call.hasArgument("url"))
model.url = (String) call.argument("url");
if (call.hasArgument("title"))
model.title = (String) call.argument("title");
if (call.hasArgument("videoId")) {
HashMap<String, Object> videoIdJson = call.argument("videoId");
assert videoIdJson != null;
SuperPlayerVideoId videoId = new SuperPlayerVideoId();
if (videoIdJson.containsKey("fileId"))
videoId.fileId = (String) videoIdJson.get("fileId");
if (videoIdJson.containsKey("pSign"))
videoId.pSign = (String) videoIdJson.get("pSign");
model.videoId = videoId;
}
superPlayerView.playWithModel(model);
}
void pause(@NonNull MethodCall call, @NonNull Result result) {
superPlayerView.getControllerCallback().onPause();
}
void resume(@NonNull MethodCall call, @NonNull Result result) {
superPlayerView.getControllerCallback().onResume();
}
void release(@NonNull MethodCall call, @NonNull Result result) {
superPlayerView.release();
}
void seekTo(@NonNull MethodCall call, @NonNull Result result) {
int time = (int) call.argument("time");
superPlayerView.getControllerCallback().onSeekTo(time);
}
void setLoop(@NonNull MethodCall call, @NonNull Result result) {
boolean isLoop = (boolean) call.argument("isLoop");
superPlayerView.getSuperPlayer().setLoop(isLoop);
}
void uiHideDanmu(@NonNull MethodCall call, @NonNull Result result) {
superPlayerView.uiHideDanmu();
}
void uiHideReplay(@NonNull MethodCall call, @NonNull Result result) {
superPlayerView.uiHideReplay();
}
@Override
public void onStartFullScreenPlay() {
final Map<String, Object> dataMap = new HashMap<>();
dataMap.put("isFullScreen", true);
final Map<String, Object> eventData = new HashMap<>();
eventData.put("listener", "SuperPlayerListener");
eventData.put("method", "onFullScreenChange");
eventData.put("data", dataMap);
eventSink.success(eventData);
}
@Override
public void onStopFullScreenPlay() {
final Map<String, Object> dataMap = new HashMap<>();
dataMap.put("isFullScreen", false);
final Map<String, Object> eventData = new HashMap<>();
eventData.put("listener", "SuperPlayerListener");
eventData.put("method", "onFullScreenChange");
eventData.put("data", dataMap);
eventSink.success(eventData);
}
@Override
public void onClickFloatCloseBtn() {
final Map<String, Object> eventData = new HashMap<>();
eventData.put("listener", "SuperPlayerListener");
eventData.put("method", "onClickFloatCloseBtn");
eventSink.success(eventData);
}
@Override
public void onClickSmallReturnBtn() {
final Map<String, Object> eventData = new HashMap<>();
eventData.put("listener", "SuperPlayerListener");
eventData.put("method", "onClickSmallReturnBtn");
eventSink.success(eventData);
}
@Override
public void onStartFloatWindowPlay() {
final Map<String, Object> eventData = new HashMap<>();
eventData.put("listener", "SuperPlayerListener");
eventData.put("method", "onStartFloatWindowPlay");
eventSink.success(eventData);
}
@Override
public void onPlayStateChange(SuperPlayerDef.PlayerState playState) {
final Map<String, Object> dataMap = new HashMap<>();
dataMap.put("playState", playState.intValue());
final Map<String, Object> eventData = new HashMap<>();
eventData.put("listener", "SuperPlayerListener");
eventData.put("method", "onPlayStateChange");
eventData.put("data", dataMap);
eventSink.success(eventData);
}
@Override
public void onPlayProgressChange(long current, long duration) {
boolean isProgressChange = playProgressCurrent == current;
playProgressCurrent = current;
if (isProgressChange) return;
final Map<String, Object> dataMap = new HashMap<>();
dataMap.put("current", current);
dataMap.put("duration", duration);
final Map<String, Object> eventData = new HashMap<>();
eventData.put("listener", "SuperPlayerListener");
eventData.put("method", "onPlayProgressChange");
eventData.put("data", dataMap);
eventSink.success(eventData);
}
}
@@ -0,0 +1,101 @@
package org.leanflutter.plugins.flutter_superplayer;
import androidx.annotation.NonNull;
import com.tencent.rtmp.TXLiveBase;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import io.flutter.plugin.platform.PlatformViewFactory;
import io.flutter.plugin.platform.PlatformViewRegistry;
import static org.leanflutter.plugins.flutter_superplayer.Constants.CHANNEL_NAME;
import static org.leanflutter.plugins.flutter_superplayer.Constants.SUPER_PLAYER_VIEW_TYPE;
/**
* FlutterSuperplayerPlugin
*/
public class FlutterSuperplayerPlugin implements FlutterPlugin, ActivityAware, MethodCallHandler {
private FlutterPluginBinding pluginBinding;
/// The MethodChannel that will the communication between Flutter and native Android
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private MethodChannel channel;
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), CHANNEL_NAME);
channel.setMethodCallHandler(this);
pluginBinding = flutterPluginBinding;
}
// This static function is optional and equivalent to onAttachedToEngine. It supports the old
// pre-Flutter-1.12 Android projects. You are encouraged to continue supporting
// plugin registration via this function while apps migrate to use the new Android APIs
// post-flutter-1.12 via https://flutter.dev/go/android-project-migration.
//
// It is encouraged to share logic between onAttachedToEngine and registerWith to keep
// them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called
// depending on the user's project. onAttachedToEngine or registerWith must both be defined
// in the same class.
public static void registerWith(Registrar registrar) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME);
channel.setMethodCallHandler(new FlutterSuperplayerPlugin());
PlatformViewFactory superPlayerViewFactory = new SuperPlayerViewFactory(registrar.activity(), registrar.messenger());
PlatformViewRegistry platformViewRegistry = registrar.platformViewRegistry();
platformViewRegistry.registerViewFactory(SUPER_PLAYER_VIEW_TYPE, superPlayerViewFactory);
registrar.addViewDestroyListener(view -> {
// skip
return false; // We are not interested in assuming ownership of the NativeView.
});
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
pluginBinding = null;
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
if (call.method.equals("getSDKVersion")) {
result.success(TXLiveBase.getSDKVersionStr());
} else {
result.notImplemented();
}
}
@Override
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
PlatformViewFactory superPlayerViewFactory = new SuperPlayerViewFactory(binding.getActivity(), pluginBinding.getBinaryMessenger());
PlatformViewRegistry platformViewRegistry = pluginBinding.getFlutterEngine().getPlatformViewsController().getRegistry();
platformViewRegistry.registerViewFactory(SUPER_PLAYER_VIEW_TYPE, superPlayerViewFactory);
}
@Override
public void onDetachedFromActivityForConfigChanges() {
}
@Override
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
}
@Override
public void onDetachedFromActivity() {
}
}
@@ -0,0 +1,30 @@
package org.leanflutter.plugins.flutter_superplayer;
import android.app.Activity;
import android.content.Context;
import java.util.Map;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.plugin.platform.PlatformView;
import io.flutter.plugin.platform.PlatformViewFactory;
public final class SuperPlayerViewFactory extends PlatformViewFactory {
private final Activity activity;
private final BinaryMessenger messenger;
public SuperPlayerViewFactory(Activity activity, BinaryMessenger messenger) {
super(StandardMessageCodec.INSTANCE);
this.activity = activity;
this.messenger = messenger;
}
@Override
public PlatformView create(Context context, int viewId, Object args) {
Map<String, Object> params = (Map<String, Object>) args;
return new FlutterSuperPlayerView(activity, messenger, viewId, params);
}
}