hyzp_ybqx-Commit001:代码刚转换好,编译通过
This commit is contained in:
+11
@@ -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;
|
||||
}
|
||||
+37
@@ -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 // 竖屏
|
||||
}
|
||||
}
|
||||
+67
@@ -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() {
|
||||
}
|
||||
}
|
||||
}
|
||||
+73
@@ -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 = ""; // 该清晰度对应的地址
|
||||
|
||||
}
|
||||
}
|
||||
+19
@@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
+913
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+148
@@ -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);
|
||||
}
|
||||
+914
@@ -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;
|
||||
}
|
||||
}
|
||||
+57
@@ -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) {}
|
||||
}
|
||||
+21
@@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
+20
@@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
+67
@@ -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;
|
||||
}
|
||||
}
|
||||
+18
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
+22
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
+22
@@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
+40
@@ -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;
|
||||
}
|
||||
}
|
||||
+25
@@ -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;
|
||||
}
|
||||
}
|
||||
+144
@@ -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();
|
||||
}
|
||||
}
|
||||
+102
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+83
@@ -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();
|
||||
|
||||
}
|
||||
+102
@@ -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();
|
||||
}
|
||||
+23
@@ -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);
|
||||
}
|
||||
+20
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -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() : "") + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
+455
@@ -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 "";
|
||||
}
|
||||
}
|
||||
+228
@@ -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;
|
||||
}
|
||||
}
|
||||
+275
@@ -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() : "";
|
||||
}
|
||||
}
|
||||
+291
@@ -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() : "";
|
||||
}
|
||||
}
|
||||
+133
@@ -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();
|
||||
}
|
||||
}
|
||||
+212
@@ -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);
|
||||
}
|
||||
}
|
||||
+156
@@ -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;
|
||||
}
|
||||
}
|
||||
+162
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+150
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+930
@@ -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);
|
||||
}
|
||||
}
|
||||
+223
@@ -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);
|
||||
}
|
||||
}
|
||||
+644
@@ -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);
|
||||
}
|
||||
}
|
||||
+182
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+553
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+111
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+372
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+203
@@ -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);
|
||||
}
|
||||
}
|
||||
+84
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -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";
|
||||
}
|
||||
+318
@@ -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);
|
||||
}
|
||||
}
|
||||
+101
@@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
+30
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user