自从上次做完视频播放器调研以后,心里就知道,肯定以后这块东西都是我做,果不其然,公司对视频播放这块不断的优化。我就悲催的无限填坑,话说英语差,看国外文档真的很吃力。
简单讲一下项目中遇到的问题。
- 创建和基本使用
这个不多讲,最简单使用就是布局里
接下来创建轨道,播放器等,如果你播放的时候发现有时候黑屏。这是可能你的VideoCache不是单例模式。 下面是我自己写的Manger和VideoCache
public class ExoPlayerManger {
private static final String TAG = "ExoPlayerManger";
private Context mContext;
private BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
// 创建轨道选择工厂
private TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
// 创建轨道选择器实例
private TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
private SimpleExoPlayer simpleExoPlayer;
private DataSource.Factory dataSourceFactory;
private String mVideoUrl;
private SimpleCache simpleCache;
private Uri playVideoUri;
private ExtractorMediaSource mediaSource;
/**
* @param context 传入context
*/
public void setBuilderContext(Context context) {
mContext = context;
dataSourceFactory = new DefaultDataSourceFactory(mContext, "seyed");
}
/**
* @param videoUrl 传入视频路径
*/
public void setVideoUrl(String videoUrl) {
this.mVideoUrl = videoUrl;
simpleCache = VideoCache.getInstance(mContext);
playVideoUri = Uri.parse(mVideoUrl);
}
/**
* @return 返回exoPlayer对象
*/
public SimpleExoPlayer create() {
try {
simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(mContext, trackSelector);
dataSourceFactory = new CacheDataSourceFactory(simpleCache, dataSourceFactory);
mediaSource = new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(playVideoUri);
simpleExoPlayer.prepare(mediaSource);
} catch (Exception e) {
}
return simpleExoPlayer;
}
}
/**
* @author :leo on 2018/12/17 17:58
*
* 方法用途 :视频缓存单例模式
*/
public class VideoCache {
private static SimpleCache sDownloadCache;
/**
* @param context
* @return
*/
public static SimpleCache getInstance(Context context) {
if (sDownloadCache == null) {
sDownloadCache = new SimpleCache(new File(getMediaCacheFile(context), "StoryCache"), new LeastRecentlyUsedCacheEvictor(512 * 1024 *1024));
}
return sDownloadCache;
}
public static File getMediaCacheFile(Context context) {
String directoryPath = "";
String childPath = "exoPlayer";
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
// 外部储存可用
directoryPath = File.separator + context.getExternalFilesDir(childPath).getAbsolutePath();
} else {
directoryPath = File.separator + context.getFilesDir().getAbsolutePath() + File.separator + childPath;
}
File file = new File(directoryPath);
//判断文件目录是否存在
if (!file.exists()) {
file.mkdirs();
}
return file;
}
}
紧接着 只需要
ExoPlayerManger exoPlayerManger = new ExoPlayerManger();
exoPlayerManger.setBuilderContext(mContext);
exoPlayerManger.setVideoUrl(playVideoUrl);
simpleExoPlayer = exoPlayerManger.create();
//设置音量 测试期间设置为0
simpleExoPlayer.setVolume(10);
videoView.setPlayer(simpleExoPlayer);
//赋值给显示时间
simpleExoPlayer.addListener(this);
//开启播放
simpleExoPlayer.setPlayWhenReady(true);
播放器有个监听 Player.EventListener ,这就讲几个状态
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (isFullScreen) {
fullScreenDialog.setPlayState(playbackState);
}
switch (playbackState) {
//缓冲状态
case 2:
break;
//播放状态
case 3:
break;
//播放完成
case 4:
default:
break;
}
- 如何播放raw下文件
RawResourceDataSource.buildRawResourceUri(R.raw.login_bg_video);
ExoPlayerManger exoPlayerManger = new ExoPlayerManger();
exoPlayerManger.setBuilderContext(getContext());
//设置从raw下读取的文件路径
exoPlayerManger.setVideoUrl(RawResourceDataSource.buildRawResourceUri(R.raw.login_bg_video).toString());
simpleExoPlayer = exoPlayerManger.create();
simpleExoPlayer.setVolume(0);
simpleExoPlayer.setRepeatMode(1);
playerView.setPlayer(simpleExoPlayer);
// simpleExoPlayer.prepare(audioSource);
simpleExoPlayer.setPlayWhenReady(true);
- 列表中点击切换到全屏
这里有很多方法,我选择的方法不是最好的。科学上网看了很多老外写的例子,大部分都是弹出一个Dialog,将item中的播放PlayerView,remove出来,放到Dialog里面。然后更改PlayerView的布局大小就可以了。
但是这里会有一个卡顿问题,老外同学们基本没讲,如果你按照直接removeView然后addView to Dialog, 我测试基本会卡1-5s。这时候可以做一个骚操作,就是向前或者向后seekto一下,可以基本做到秒开。当你切换窗口的时候,可以将PlayerView 还回去就可以了,记得设置原来的宽高和大小。
//从当前布局移除播放view
ViewGroup parent = (ViewGroup) videoView.getParent();
if (parent != null) {
parent.removeView(videoView);
}
//加入到Dialog
ViewGroup.LayoutParams layoutParams = videoView.getLayoutParams();
layoutParams.width = RelativeLayout.LayoutParams.MATCH_PARENT;
layoutParams.height = RelativeLayout.LayoutParams.MATCH_PARENT;
videoView.setLayoutParams(layoutParams);
rlShow.addView(playerView);
其实也可以尝试使用,simpleExoPlayer.setVideoTextureView();切换画布,但是我在测试的时候,会有卡顿,有时间的同学可以试试,希望你有别的办法可以给我留言,谢谢。还有很多细节的东西,等我想起来再补上,连续加班,今天才有时间写点东西,就先到这里 。
最近公司大佬提出建议,说来回切换全屏的时候有卡顿经过一番百度谷姐,终于找到解决办法。
使用TextureView 作为ExoPlayer播放画布。
TextureView 在切换状态的时候会经历销毁重建过程。
那么之前TextureView中播放的SurfaceTexture也会销毁。
建议在从列表到全屏之前调用TextureView.getSurfaceTexture()
保存当前状态,在设置完新的宽高后,将保存的SurfaceTexture重新设置进去。
这里会有一个坑,就是设置进去也会卡在那里。
在调用TextureView.getSurfaceTexture()之前,给TextureView设置setSurfaceTextureListener 并在onSurfaceTextureDestroyed方法中返回false
在onSurfaceTextureAvailable中重新调用textureView.setSurfaceTexture(mSurfaceTexture);
建议try一下,因为可能会报SurfaceTexture IsReleased 异常。
简单代码如下
windowTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
});
//获取当前缓存的帧
SurfaceTexture surfaceTexture = windowTextureView.getSurfaceTexture();
//跳转全屏
fullScreenDialog.setFullScreenRes(windowTextureView, surfaceTexture);
//在全屏页面中
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
if (mSurfaceTexture == null) {
mSurfaceTexture = surface;
}
try {
textureView.setSurfaceTexture(mSurfaceTexture);
} catch (Exception e) {
}
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
});
经过测试基本可以实现无卡顿切换。