383 lines
14 KiB
Java
383 lines
14 KiB
Java
package com.yunbao.live.utils;
|
||
|
||
import android.content.Context;
|
||
import android.os.Handler;
|
||
import android.os.Looper;
|
||
import android.util.Log;
|
||
|
||
import androidx.annotation.NonNull;
|
||
|
||
import com.google.android.exoplayer2.C;
|
||
import com.google.android.exoplayer2.DefaultLoadControl;
|
||
import com.google.android.exoplayer2.ExoPlayer;
|
||
import com.google.android.exoplayer2.MediaItem;
|
||
import com.google.android.exoplayer2.PlaybackException;
|
||
import com.google.android.exoplayer2.Player;
|
||
import com.google.android.exoplayer2.analytics.AnalyticsListener;
|
||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||
import com.google.android.exoplayer2.ui.StyledPlayerView;
|
||
import com.google.android.exoplayer2.video.VideoSize;
|
||
|
||
/**
|
||
* 直播间播放器管理器
|
||
* 通过预加载子播放器来实现无缝切换分辨率功能
|
||
*/
|
||
public class LiveExoPlayerManager {
|
||
private final int MODEL_PLAY1 = 0;//当前主播放器
|
||
private final int MODEL_PLAY2 = 1;//当前子播放器
|
||
private final ExoPlayer player1;
|
||
private final ExoPlayer player2;
|
||
private StyledPlayerView mainView;//渲染视图
|
||
private int status = MODEL_PLAY1;
|
||
private Player.Listener listener;
|
||
private boolean isSwitchUrl = false;//是否为主动切换播放器
|
||
private final String TAG = "播放器";
|
||
private int playBufferIndex = 0;//卡顿计数器
|
||
private final Handler handler;
|
||
private static double log_buffer_time = 0, log_buffer_max_time;
|
||
private String url = "";
|
||
|
||
public LiveExoPlayerManager(Context mContext) {
|
||
DefaultLoadControl control = new DefaultLoadControl.Builder()
|
||
.setPrioritizeTimeOverSizeThresholds(false)
|
||
.setBackBuffer(10_000, true)
|
||
.setBufferDurationsMs(500,
|
||
5_000,
|
||
150,
|
||
200)
|
||
.build();
|
||
player1 = new ExoPlayer.Builder(mContext).setLoadControl(control).build();
|
||
player2 = new ExoPlayer.Builder(mContext).setLoadControl(control).build();
|
||
player1.setVideoScalingMode(C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING);
|
||
player2.setVideoScalingMode(C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING);
|
||
|
||
handler = new Handler(Looper.getMainLooper());
|
||
setListener();
|
||
setAnalyticsListener();
|
||
}
|
||
|
||
|
||
public void setListener(Player.Listener listener) {
|
||
this.listener = listener;
|
||
}
|
||
|
||
public void setMainView(StyledPlayerView mainView) {
|
||
this.mainView = mainView;
|
||
this.mainView.setKeepContentOnPlayerReset(true);
|
||
}
|
||
|
||
public void setViewResizeMode(boolean isPhone) {
|
||
Log.i(TAG, "setViewResizeMode: " + isPhone);
|
||
mainView.setResizeMode(isPhone ? AspectRatioFrameLayout.RESIZE_MODE_ZOOM : AspectRatioFrameLayout.RESIZE_MODE_FIT);
|
||
mainView.requestLayout();
|
||
}
|
||
|
||
public boolean isViewResizeModeToPhone() {
|
||
return mainView.getResizeMode() == AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
|
||
}
|
||
|
||
private void setAnalyticsListener() {
|
||
player1.addAnalyticsListener(new AnalyticsListener() {
|
||
@Override
|
||
public void onPlaybackStateChanged(
|
||
@NonNull EventTime eventTime, int state) {
|
||
Log.d(TAG, "onPlaybackStateChanged(1) called with: eventTime = [" + eventTime + "], state = [" + state + "]");
|
||
}
|
||
|
||
@Override
|
||
public void onDroppedVideoFrames(
|
||
@NonNull EventTime eventTime, int droppedFrames, long elapsedMs) {
|
||
Log.d(TAG, "onDroppedVideoFrames(1) called with: eventTime = [" + eventTime + "], droppedFrames = [" + droppedFrames + "], elapsedMs = [" + elapsedMs + "]");
|
||
}
|
||
});
|
||
|
||
player2.addAnalyticsListener(new AnalyticsListener() {
|
||
@Override
|
||
public void onPlaybackStateChanged(
|
||
@NonNull EventTime eventTime, int state) {
|
||
Log.d(TAG, "onPlaybackStateChanged(2) called with: eventTime = [" + eventTime.totalBufferedDurationMs + "], state = [" + state + "]");
|
||
}
|
||
|
||
@Override
|
||
public void onDroppedVideoFrames(
|
||
@NonNull EventTime eventTime, int droppedFrames, long elapsedMs) {
|
||
Log.d(TAG, "onDroppedVideoFrames(2) called with: eventTime = [" + eventTime + "], droppedFrames = [" + droppedFrames + "], elapsedMs = [" + elapsedMs + "]");
|
||
}
|
||
});
|
||
|
||
}
|
||
|
||
/**
|
||
* 延迟1秒还未恢复播放则认为卡顿了,可以切分辨率了
|
||
*/
|
||
private final Runnable buffRunnable = new Runnable() {
|
||
@Override
|
||
public void run() {
|
||
playBufferIndex = 0;
|
||
listener.onPlaybackStateChanged(Player.STATE_BUFFERING);
|
||
replay();
|
||
}
|
||
};
|
||
|
||
private void setListener() {
|
||
player1.addListener(new Player.Listener() {
|
||
@Override
|
||
public void onPlaybackStateChanged(int playbackState) {
|
||
Player.Listener.super.onPlaybackStateChanged(playbackState);
|
||
Log.i(TAG, "onPlaybackStateChanged 1: " + playbackState);
|
||
if (playbackState == Player.STATE_READY && !player1.isPlaying()) {
|
||
player2.stop();
|
||
player2.clearVideoSurface();
|
||
player1.play();
|
||
Log.i(TAG, "切换播放器1");
|
||
handler.removeCallbacks(buffRunnable);
|
||
playBufferIndex = 0;
|
||
} else if (playbackState == Player.STATE_BUFFERING && status == MODEL_PLAY1 && !isSwitchUrl) {
|
||
if (listener != null) {
|
||
if (playBufferIndex++ == 0) {
|
||
handler.postDelayed(buffRunnable, 2000);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void onIsPlayingChanged(boolean isPlaying) {
|
||
Player.Listener.super.onIsPlayingChanged(isPlaying);
|
||
if (isPlaying) {
|
||
Log.i(TAG, "onIsPlayingChanged1: 播放了");
|
||
//player1.setVideoSurfaceView(mainView);
|
||
if (log_buffer_time != -1) {
|
||
double tmp = (System.currentTimeMillis() - log_buffer_time) / 1000;
|
||
if (tmp > log_buffer_max_time) {
|
||
log_buffer_max_time = tmp;
|
||
}
|
||
//ToastUtil.show(String.format(Locale.CHINA, "从加载到播放 = %.3f,最大耗时 = %.3f", tmp, log_buffer_max_time));
|
||
log_buffer_time = -1;
|
||
}
|
||
mainView.setPlayer(player1);
|
||
status = MODEL_PLAY1;
|
||
isSwitchUrl = false;
|
||
handler.removeCallbacks(buffRunnable);
|
||
if (getNextPlayer().isPlaying()) {
|
||
getNextPlayer().stop();
|
||
}
|
||
if (listener != null) {
|
||
listener.onIsPlayingChanged(true);
|
||
}
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void onVideoSizeChanged(@NonNull VideoSize videoSize) {
|
||
Player.Listener.super.onVideoSizeChanged(videoSize);
|
||
setViewResizeMode(videoSize.height > videoSize.width);
|
||
Log.i(TAG, "onVideoSizeChanged: width = " + videoSize.width + " height = " + videoSize.height);
|
||
if (listener != null) {
|
||
listener.onVideoSizeChanged(videoSize);
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void onIsLoadingChanged(boolean isLoading) {
|
||
Player.Listener.super.onIsLoadingChanged(isLoading);
|
||
Log.i(TAG, "onIsLoadingChanged: 1 " + isLoading);
|
||
}
|
||
|
||
@Override
|
||
public void onPlayerError(@NonNull PlaybackException error) {
|
||
Player.Listener.super.onPlayerError(error);
|
||
error.printStackTrace();
|
||
replay();
|
||
}
|
||
});
|
||
|
||
player2.addListener(new Player.Listener() {
|
||
@Override
|
||
public void onPlaybackStateChanged(int playbackState) {
|
||
Player.Listener.super.onPlaybackStateChanged(playbackState);
|
||
Log.i(TAG, "onPlaybackStateChanged 2: " + playbackState);
|
||
if (playbackState == Player.STATE_READY && !player2.isPlaying()) {
|
||
player1.stop();
|
||
player1.clearVideoSurface();
|
||
player2.play();
|
||
Log.i(TAG, "切换播放器2 " + player2.isPlaying());
|
||
handler.removeCallbacks(buffRunnable);
|
||
playBufferIndex = 0;
|
||
} else if (playbackState == Player.STATE_BUFFERING && status == MODEL_PLAY2 && !isSwitchUrl) {
|
||
if (listener != null) {
|
||
if (playBufferIndex++ == 0) {
|
||
handler.postDelayed(buffRunnable, 2000);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void onIsPlayingChanged(boolean isPlaying) {
|
||
Player.Listener.super.onIsPlayingChanged(isPlaying);
|
||
if (isPlaying) {
|
||
Log.i(TAG, "onIsPlayingChanged2: 播放了");
|
||
//player2.setVideoSurfaceView(mainView);
|
||
mainView.setPlayer(player2);
|
||
double tmp = (System.currentTimeMillis() - log_buffer_time) / 1000;
|
||
if (tmp > log_buffer_max_time) {
|
||
log_buffer_max_time = tmp;
|
||
}
|
||
//ToastUtil.show(String.format(Locale.CHINA, "从加载到播放 = %.3f,最大耗时 = %.3f", tmp, log_buffer_max_time));
|
||
log_buffer_time = -1;
|
||
status = MODEL_PLAY2;
|
||
if (getNextPlayer().isPlaying()) {
|
||
getNextPlayer().stop();
|
||
}
|
||
handler.removeCallbacks(buffRunnable);
|
||
isSwitchUrl = false;
|
||
if (listener != null) {
|
||
listener.onIsPlayingChanged(true);
|
||
}
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void onVideoSizeChanged(@NonNull VideoSize videoSize) {
|
||
Player.Listener.super.onVideoSizeChanged(videoSize);
|
||
setViewResizeMode(videoSize.height > videoSize.width);
|
||
if (listener != null) {
|
||
listener.onVideoSizeChanged(videoSize);
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void onIsLoadingChanged(boolean isLoading) {
|
||
Player.Listener.super.onIsLoadingChanged(isLoading);
|
||
Log.i(TAG, "onIsLoadingChanged: 2 " + isLoading);
|
||
}
|
||
|
||
@Override
|
||
public void onPlayerError(@NonNull PlaybackException error) {
|
||
Player.Listener.super.onPlayerError(error);
|
||
error.printStackTrace();
|
||
replay();
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 开始播放
|
||
*
|
||
* @param url 地址
|
||
*/
|
||
public void startUrl(String url) {
|
||
if (url != null && url.equals(this.url)) return;
|
||
Log.i(TAG, "startUrl: " + url + " > " + mainView.getResizeMode());
|
||
handler.removeCallbacks(buffRunnable);
|
||
this.url = url;
|
||
isSwitchUrl = true;
|
||
playBufferIndex = 0;
|
||
log_buffer_time = System.currentTimeMillis();
|
||
//getNowPlayer().setVideoSurfaceView(mainView);
|
||
mainView.setKeepContentOnPlayerReset(false);
|
||
mainView.setPlayer(getNowPlayer());
|
||
mainView.setKeepContentOnPlayerReset(true);
|
||
getNowPlayer().setMediaItem(createMediaItem(url));
|
||
getNowPlayer().prepare();
|
||
getNowPlayer().play();
|
||
handler.postDelayed(buffRunnable, 10000);
|
||
}
|
||
|
||
/**
|
||
* 无缝切换
|
||
*
|
||
* @param url 地址
|
||
*/
|
||
public void switchUrl(String url) {
|
||
if (url != null && url.equals(this.url)) return;
|
||
Log.i(TAG, "switchUrl: " + url + " src : " + this.url);
|
||
this.url = url;
|
||
playBufferIndex = 0;
|
||
isSwitchUrl = true;
|
||
log_buffer_time = System.currentTimeMillis();
|
||
mainView.setKeepContentOnPlayerReset(true);
|
||
getNextPlayer().setMediaItem(createMediaItem(url));
|
||
getNextPlayer().prepare();
|
||
}
|
||
|
||
private MediaItem createMediaItem(String url) {
|
||
return MediaItem.fromUri(url);
|
||
}
|
||
|
||
/**
|
||
* 获取当前播放器
|
||
*/
|
||
public ExoPlayer getNowPlayer() {
|
||
return status == MODEL_PLAY1 ? player1 : player2;
|
||
}
|
||
|
||
/**
|
||
* 获取下一个播放器。
|
||
*/
|
||
private ExoPlayer getNextPlayer() {
|
||
return status == MODEL_PLAY1 ? player2 : player1;
|
||
}
|
||
|
||
/**
|
||
* 是否正在播放
|
||
*/
|
||
public boolean isPlaying() {
|
||
return getNowPlayer().isPlaying();
|
||
}
|
||
|
||
/**
|
||
* 停止播放
|
||
*/
|
||
public void stop() {
|
||
getNowPlayer().stop();
|
||
clearUrl();
|
||
}
|
||
|
||
/**
|
||
* 开始播放
|
||
*/
|
||
public void play() {
|
||
getNowPlayer().play();
|
||
}
|
||
|
||
public void replay() {
|
||
Log.i(TAG, "replay: 重载播放");
|
||
getNowPlayer().stop();
|
||
getNextPlayer().stop();
|
||
String tmp = url;
|
||
url = null;
|
||
startUrl(tmp);
|
||
}
|
||
|
||
public void clearFrame() {
|
||
mainView.setKeepContentOnPlayerReset(false);
|
||
if (mainView.getVideoSurfaceView() != null) {
|
||
mainView.setPlayer(null);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 释放播放器
|
||
*/
|
||
public void release() {
|
||
Log.i(TAG, "release: 释放播放器");
|
||
player1.release();
|
||
player2.release();
|
||
handler.removeCallbacks(buffRunnable);
|
||
}
|
||
|
||
public void clearUrl() {
|
||
url = "";
|
||
handler.removeCallbacks(buffRunnable);
|
||
}
|
||
|
||
public String getUrl() {
|
||
if (url == null) {
|
||
url = "";
|
||
}
|
||
return url;
|
||
}
|
||
}
|