|
@@ -4,8 +4,10 @@
|
|
|
- 02.播放器内核架构图
|
|
|
- 03.如何兼容不同内核播放器
|
|
|
- 04.看一下ijk的内核实现类
|
|
|
-- 05.如何创建不同内核播放器
|
|
|
-- 06.看一下工厂类实现代码
|
|
|
+- 05.看一下exo的内核实现类
|
|
|
+- 06.如何创建不同内核播放器
|
|
|
+- 07.看一下工厂类实现代码
|
|
|
+- 08.后期如何添加新的内核
|
|
|
|
|
|
|
|
|
|
|
@@ -35,7 +37,7 @@
|
|
|
|
|
|
|
|
|
### 04.看一下ijk的内核实现类
|
|
|
-- 代码如下所示
|
|
|
+- ijk的内核实现类代码如下所示
|
|
|
```java
|
|
|
public class IjkVideoPlayer extends AbstractVideoPlayer {
|
|
|
|
|
@@ -430,7 +432,406 @@
|
|
|
```
|
|
|
|
|
|
|
|
|
-### 05.如何创建不同内核播放器
|
|
|
+### 05.看一下exo的内核实现类
|
|
|
+- exo的内核实现类代码如下所示,和ijk的api有些区别
|
|
|
+ ```java
|
|
|
+ public class ExoMediaPlayer extends AbstractVideoPlayer implements VideoListener, Player.EventListener {
|
|
|
+
|
|
|
+ protected Context mAppContext;
|
|
|
+ protected SimpleExoPlayer mInternalPlayer;
|
|
|
+ protected MediaSource mMediaSource;
|
|
|
+ protected ExoMediaSourceHelper mMediaSourceHelper;
|
|
|
+ private PlaybackParameters mSpeedPlaybackParameters;
|
|
|
+ private int mLastReportedPlaybackState = Player.STATE_IDLE;
|
|
|
+ private boolean mLastReportedPlayWhenReady = false;
|
|
|
+ private boolean mIsPreparing;
|
|
|
+ private boolean mIsBuffering;
|
|
|
+
|
|
|
+ private LoadControl mLoadControl;
|
|
|
+ private RenderersFactory mRenderersFactory;
|
|
|
+ private TrackSelector mTrackSelector;
|
|
|
+
|
|
|
+ public ExoMediaPlayer(Context context) {
|
|
|
+ if (context instanceof Application){
|
|
|
+ mAppContext = context;
|
|
|
+ } else {
|
|
|
+ mAppContext = context.getApplicationContext();
|
|
|
+ }
|
|
|
+ mMediaSourceHelper = ExoMediaSourceHelper.getInstance(context);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void initPlayer() {
|
|
|
+ //创建exo播放器
|
|
|
+ mInternalPlayer = new SimpleExoPlayer.Builder(
|
|
|
+ mAppContext,
|
|
|
+ mRenderersFactory == null ? mRenderersFactory = new DefaultRenderersFactory(mAppContext) : mRenderersFactory,
|
|
|
+ mTrackSelector == null ? mTrackSelector = new DefaultTrackSelector(mAppContext) : mTrackSelector,
|
|
|
+ mLoadControl == null ? mLoadControl = new DefaultLoadControl() : mLoadControl,
|
|
|
+ DefaultBandwidthMeter.getSingletonInstance(mAppContext),
|
|
|
+ Util.getLooper(),
|
|
|
+ new AnalyticsCollector(Clock.DEFAULT),
|
|
|
+ /* useLazyPreparation= */ true,
|
|
|
+ Clock.DEFAULT)
|
|
|
+ .build();
|
|
|
+ setOptions();
|
|
|
+
|
|
|
+ //播放器日志
|
|
|
+ if (VideoLogUtils.isIsLog() && mTrackSelector instanceof MappingTrackSelector) {
|
|
|
+ mInternalPlayer.addAnalyticsListener(new EventLogger((MappingTrackSelector) mTrackSelector, "ExoPlayer"));
|
|
|
+ }
|
|
|
+ initListener();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * exo视频播放器监听listener
|
|
|
+ */
|
|
|
+ private void initListener() {
|
|
|
+ mInternalPlayer.addListener(this);
|
|
|
+ mInternalPlayer.addVideoListener(this);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setTrackSelector(TrackSelector trackSelector) {
|
|
|
+ mTrackSelector = trackSelector;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setRenderersFactory(RenderersFactory renderersFactory) {
|
|
|
+ mRenderersFactory = renderersFactory;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setLoadControl(LoadControl loadControl) {
|
|
|
+ mLoadControl = loadControl;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置播放地址
|
|
|
+ *
|
|
|
+ * @param path 播放地址
|
|
|
+ * @param headers 播放地址请求头
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void setDataSource(String path, Map<String, String> headers) {
|
|
|
+ // 设置dataSource
|
|
|
+ if(path==null || path.length()==0){
|
|
|
+ if (mPlayerEventListener!=null){
|
|
|
+ mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_URL_NULL, 0);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ mMediaSource = mMediaSourceHelper.getMediaSource(path, headers);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void setDataSource(AssetFileDescriptor fd) {
|
|
|
+ //no support
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 准备开始播放(异步)
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void prepareAsync() {
|
|
|
+ if (mInternalPlayer == null){
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (mMediaSource == null){
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (mSpeedPlaybackParameters != null) {
|
|
|
+ mInternalPlayer.setPlaybackParameters(mSpeedPlaybackParameters);
|
|
|
+ }
|
|
|
+ mIsPreparing = true;
|
|
|
+ mMediaSource.addEventListener(new Handler(), mMediaSourceEventListener);
|
|
|
+ //准备播放
|
|
|
+ mInternalPlayer.prepare(mMediaSource);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 播放
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void start() {
|
|
|
+ if (mInternalPlayer == null){
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ mInternalPlayer.setPlayWhenReady(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 暂停
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void pause() {
|
|
|
+ if (mInternalPlayer == null){
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ mInternalPlayer.setPlayWhenReady(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 停止
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void stop() {
|
|
|
+ if (mInternalPlayer == null){
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ mInternalPlayer.stop();
|
|
|
+ }
|
|
|
+
|
|
|
+ private MediaSourceEventListener mMediaSourceEventListener = new MediaSourceEventListener() {
|
|
|
+ @Override
|
|
|
+ public void onReadingStarted(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {
|
|
|
+ if (mPlayerEventListener != null && mIsPreparing) {
|
|
|
+ mPlayerEventListener.onPrepared();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重置播放器
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void reset() {
|
|
|
+ if (mInternalPlayer != null) {
|
|
|
+ mInternalPlayer.stop(true);
|
|
|
+ mInternalPlayer.setVideoSurface(null);
|
|
|
+ mIsPreparing = false;
|
|
|
+ mIsBuffering = false;
|
|
|
+ mLastReportedPlaybackState = Player.STATE_IDLE;
|
|
|
+ mLastReportedPlayWhenReady = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否正在播放
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public boolean isPlaying() {
|
|
|
+ if (mInternalPlayer == null){
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ int state = mInternalPlayer.getPlaybackState();
|
|
|
+ switch (state) {
|
|
|
+ case Player.STATE_BUFFERING:
|
|
|
+ case Player.STATE_READY:
|
|
|
+ return mInternalPlayer.getPlayWhenReady();
|
|
|
+ case Player.STATE_IDLE:
|
|
|
+ case Player.STATE_ENDED:
|
|
|
+ default:
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 调整进度
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void seekTo(long time) {
|
|
|
+ if (mInternalPlayer == null){
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ mInternalPlayer.seekTo(time);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 释放播放器
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void release() {
|
|
|
+ if (mInternalPlayer != null) {
|
|
|
+ mInternalPlayer.removeListener(this);
|
|
|
+ mInternalPlayer.removeVideoListener(this);
|
|
|
+ final SimpleExoPlayer player = mInternalPlayer;
|
|
|
+ mInternalPlayer = null;
|
|
|
+ new Thread() {
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ //异步释放,防止卡顿
|
|
|
+ player.release();
|
|
|
+ }
|
|
|
+ }.start();
|
|
|
+ }
|
|
|
+
|
|
|
+ mIsPreparing = false;
|
|
|
+ mIsBuffering = false;
|
|
|
+ mLastReportedPlaybackState = Player.STATE_IDLE;
|
|
|
+ mLastReportedPlayWhenReady = false;
|
|
|
+ mSpeedPlaybackParameters = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取当前播放的位置
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public long getCurrentPosition() {
|
|
|
+ if (mInternalPlayer == null){
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ return mInternalPlayer.getCurrentPosition();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取视频总时长
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public long getDuration() {
|
|
|
+ if (mInternalPlayer == null){
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ return mInternalPlayer.getDuration();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取缓冲百分比
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public int getBufferedPercentage() {
|
|
|
+ return mInternalPlayer == null ? 0 : mInternalPlayer.getBufferedPercentage();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置渲染视频的View,主要用于SurfaceView
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void setSurface(Surface surface) {
|
|
|
+ if (mInternalPlayer != null) {
|
|
|
+ mInternalPlayer.setVideoSurface(surface);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void setDisplay(SurfaceHolder holder) {
|
|
|
+ if (holder == null){
|
|
|
+ setSurface(null);
|
|
|
+ } else{
|
|
|
+ setSurface(holder.getSurface());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置音量
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void setVolume(float leftVolume, float rightVolume) {
|
|
|
+ if (mInternalPlayer != null){
|
|
|
+ mInternalPlayer.setVolume((leftVolume + rightVolume) / 2);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置是否循环播放
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void setLooping(boolean isLooping) {
|
|
|
+ if (mInternalPlayer != null){
|
|
|
+ mInternalPlayer.setRepeatMode(isLooping ? Player.REPEAT_MODE_ALL : Player.REPEAT_MODE_OFF);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void setOptions() {
|
|
|
+ //准备好就开始播放
|
|
|
+ mInternalPlayer.setPlayWhenReady(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置播放速度
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void setSpeed(float speed) {
|
|
|
+ PlaybackParameters playbackParameters = new PlaybackParameters(speed);
|
|
|
+ mSpeedPlaybackParameters = playbackParameters;
|
|
|
+ if (mInternalPlayer != null) {
|
|
|
+ mInternalPlayer.setPlaybackParameters(playbackParameters);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取播放速度
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public float getSpeed() {
|
|
|
+ if (mSpeedPlaybackParameters != null) {
|
|
|
+ return mSpeedPlaybackParameters.speed;
|
|
|
+ }
|
|
|
+ return 1f;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取当前缓冲的网速
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public long getTcpSpeed() {
|
|
|
+ // no support
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
|
|
+ if (mPlayerEventListener == null){
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (mIsPreparing){
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (mLastReportedPlayWhenReady != playWhenReady || mLastReportedPlaybackState != playbackState) {
|
|
|
+ switch (playbackState) {
|
|
|
+ //最开始调用的状态
|
|
|
+ case Player.STATE_IDLE:
|
|
|
+ break;
|
|
|
+ //开始缓充
|
|
|
+ case Player.STATE_BUFFERING:
|
|
|
+ mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_BUFFERING_START, getBufferedPercentage());
|
|
|
+ mIsBuffering = true;
|
|
|
+ break;
|
|
|
+ //开始播放
|
|
|
+ case Player.STATE_READY:
|
|
|
+ if (mIsBuffering) {
|
|
|
+ mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_BUFFERING_END, getBufferedPercentage());
|
|
|
+ mIsBuffering = false;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ //播放器已经播放完了媒体
|
|
|
+ case Player.STATE_ENDED:
|
|
|
+ mPlayerEventListener.onCompletion();
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ mLastReportedPlaybackState = playbackState;
|
|
|
+ mLastReportedPlayWhenReady = playWhenReady;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onPlayerError(ExoPlaybackException error) {
|
|
|
+ if (mPlayerEventListener != null) {
|
|
|
+ mPlayerEventListener.onError();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
|
|
|
+ if (mPlayerEventListener != null) {
|
|
|
+ mPlayerEventListener.onVideoSizeChanged(width, height);
|
|
|
+ if (unappliedRotationDegrees > 0) {
|
|
|
+ mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_VIDEO_ROTATION_CHANGED, unappliedRotationDegrees);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onRenderedFirstFrame() {
|
|
|
+ if (mPlayerEventListener != null && mIsPreparing) {
|
|
|
+ mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_VIDEO_RENDERING_START, 0);
|
|
|
+ mIsPreparing = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ```
|
|
|
+
|
|
|
+### 06.如何创建不同内核播放器
|
|
|
- 先来看一下创建不同内核播放器的代码,只需要开发者传入一个类型参数,即可创建不同类的实例对象。代码如下所示
|
|
|
```java
|
|
|
/**
|
|
@@ -468,7 +869,7 @@
|
|
|
- 简而言之,创建对象的时候只需要传递类型type,而不需要对应的工厂,即可创建具体的产品对象
|
|
|
|
|
|
|
|
|
-### 06.看一下工厂类实现代码
|
|
|
+### 07.看一下工厂类实现代码
|
|
|
- 抽象工厂类,代码如下所示
|
|
|
```java
|
|
|
public abstract class PlayerFactory<T extends AbstractVideoPlayer> {
|
|
@@ -495,9 +896,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
+### 08.后期如何添加新的内核
|
|
|
+- 比如后期想要添加一个腾讯视频内核的播放器。代码如下所示,这个是简化的
|
|
|
+ ```java
|
|
|
+ public class TxPlayerFactory extends PlayerFactory<TxMediaPlayer> {
|
|
|
+
|
|
|
+ public static TxPlayerFactory create() {
|
|
|
+ return new TxPlayerFactory();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public TxMediaPlayer createPlayer(Context context) {
|
|
|
+ return new TxMediaPlayer(context);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public class TxMediaPlayer extends AbstractVideoPlayer {
|
|
|
+ //省略接口的实现方法代码
|
|
|
+ }
|
|
|
+ ```
|
|
|
|
|
|
|
|
|
|