#Android ExoPlayer的学习与应用
ExoPlayer是Google自家推出的一个适用于Android的可扩展媒体播放器的库,该库功能非常强大,且Api实现简单,做到了几行代码就能做到加载,缓存等一系列操作,按照谷歌官方的说法,该库有以下几点优势和劣势:
ExoPlayer与MediaPlayer内置的Android相比具有许多优势:
- 支持HTTP动态自适应流媒体(DASH)和SmoothStreaming,这两者都不受MediaPlayer支持。许多其他格式也被支持。
- 支持高级HLS功能,如正确处理 #EXT-X-DISCONTINUITY标签。
- 无缝合并,连接和循环媒体的能力。
- 随着您的应用程序一起更新播放器的能力。因为ExoPlayer是一个包含在应用程序apk中的库,所以您可以控制使用哪个版本,并且可以轻松地将其更新为新版本,作为常规应用程序更新的一部分。
- 更少的设备特定问题以及不同设备和Android版本的行为差异更小。
- 在Android 4.4(API级别19)及更高版本上支持Widevine通用加密。
- 能够自定义和扩展播放器以适应您的使用情况。ExoPlayer是专门为此设计的,允许用自定义实现来替换许多组件。
- 使用官方扩展快速集成多个附加库的功能。例如,IMA扩展可以让您轻松通过互动式媒体广告SDK获利内容。
重要的是要注意,还有一些缺点:
- ExoPlayer的标准音频和视频组件依赖 MediaCodec于Android4.1(API级别16)发布的API。因此,它们不适用于早期版本的Android。Widevine通用加密适用于Android 4.4(API级别19)及更高版本。
##第一步:集成
该版本是2.7.2,最新版本去Github上查看,地址:https://github.com/google/ExoPlayer
implementation ’ com.google.android.exoplayer:exoplayer:2.7.2 ’
##第二步:布局中添加PlayerView
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
##第三步:创建播放器
// 得到默认合适的带宽
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
// 创建跟踪的工厂
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
// 创建跟踪器
TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
// 创建player
SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(this, trackSelector);
// 绑定player
playerView.setPlayer(player);
##第四步:准备player
- 代码中的Util.getUserAgent()方法是根据给定的应用程序返回一个用户代理字符串名称和版本库,参数中的第一个参数是上下文,第二个参数是应用程序的名称(可以自定义)。
- MediaSource是要播放的资源,通过createMediaSource方法传入uri实现,这个uri可以是:
- 网络路径(这里给一个可以加载播放的地址:http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4)
- 存放在Android Studio里raw或者assets文件夹中的媒体文件
- 手机本地的媒体文件
// 生成通过其加载媒体数据的DataSource实例
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this,
Util.getUserAgent(MediaActivity.this, "ExoPlayer"), bandwidthMeter);
// 创建要播放的媒体的MediaSource
MediaSource mediaSource = new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
// 准备播放器的MediaSource
player.prepare(mediaSource);
// 当准备完毕后直接播放
player.setPlayWhenReady(true);
##第五步:释放资源(player)
一般在onDestroy()中执行
if (player != null) {
player.release();
}
##第六步:拓展(不想看的可以忽略)
以上,基本的播放已经可以实现了,当然,如果是获取网络视频,别忘记在清单文件里授权网络权限,不然,你懂的…
只有这些,我肯定是不满足,这就需要拓展一下了,至于怎么拓展,拓展哪些,我就介绍一下我自己拓展的一些东西。
- 横屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
- 全屏(隐藏状态栏并且使用沉浸式导航栏)
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
// 隐藏导航栏
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
// 全屏(隐藏状态栏)
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_FULLSCREEN
// 使用沉浸式必须加这个flag
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
);
}
}
- 添加ToolBar
有些人要说了,添加Toolbar有什么好说的,但是你知道,怎么让ToolBar在视频控制器消失的时候,也一同消失吗?不知道的话,就继续往下看…
playerView.setControllerVisibilityListener(new PlayerControlView.VisibilityListener() {
@Override
public void onVisibilityChange(int visibility) {
Log.e(TAG, "onVisibilityChange: " + visibility);
if (View.GONE == visibility) {
toolbar.setVisibility(View.GONE);
} else if (View.VISIBLE == visibility) {
toolbar.setVisibility(View.VISIBLE);
}
}
});
注:playerView就是xml中添加的PlayerView。
- 返回桌面暂停播放,没退出应用的情况下,点进去继续上次的进度,上次的状态
@Override
protected void onPause() {
super.onPause();
// 暂停播放
if (player != null) {
player.stop();
}
}
@Override
protected void onResume() {
super.onResume();
// 继续播放
if (player != null) {
player.prepare(mediaSource, false, false);
}
}
注:player.prepare()方法中需要三个参数,第一个是mediaSource为准备Player中的MediaSource,可以把这个mediaSource设置为全局后传入,第二个为是否重置播放位置,第三个为是否重置播放状态,在继续播放的情况下,二三两个参数都应该传false。
- 添加左右两侧调节亮度、音量功能
上下滑动调节功能,同时用seekBar显示调节的值,原理其实很简单,就是根据上滑的距离、总体的长度、亮度或者声音的最大值,计算出调节后的值,再相应地设置改变就行了。首先在onCreate方法中获取一些系统的参数,代码如下:
// 获取屏幕大小
screenWidth = getResources().getDisplayMetrics().widthPixels;
screenHeight = getResources().getDisplayMetrics().heightPixels;
// 获取系统音量
audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
if (audioManager != null) {
// AudioManager.STREAM_MUSIC为媒体音量
maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
}
// 获取当前屏幕亮度
try {
currentBrightness = Settings.System.getInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS);
} catch (Settings.SettingNotFoundException e) {
e.printStackTrace();
currentBrightness = 255;
}
其次,就是处理onTouchListener事件,首先,在手指按下的时候,获取按下的点的x轴、y轴坐标,x轴坐标主要是用来判断是在屏幕哪一侧(这里就是区分是调节亮度还是音量),y轴数据主要是记录按下时的数据,稍候滑动的时候需要用到,代码如下:(代码中的MAX_SCREEN_BRIGHTNESS参数值为255,官方给的系统屏幕的最大亮度为255)
case MotionEvent.ACTION_DOWN:
startX = event.getX();
startY = event.getY();
// 获取当前系统声音
currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
if (startX > screenWidth / 2) {
// 根据系统音量的最大值来设置seekBar的最大值
seekBar.setMax(maxVolume);
} else {
// 根据系统亮度的最大值来设置seekBar的最大值
seekBar.setMax(MAX_SCREEN_BRIGHTNESS);
}
break;
然后,在滑动过程中还要根据滑动后的y轴的值与手指按下时的y轴值的差值来判断是上滑还是下滑,上滑增加,下滑减小,滑动的距离占整个长度的百分比乘以亮度或者音量的最大值加上初始的值(由于滑动的距离很短,占的百分比也会很小,这里需要乘以一个数,来控制调节的幅度,我这里取SLIDE_MULTIPLE的值为5),就可以得到调节的值,代码如下:
case MotionEvent.ACTION_MOVE:
moveY = event.getY();
distanceY = startY - moveY;
Log.d(TAG, "distanceY: " + distanceY);
// 显示弹框
llDetail.setVisibility(View.VISIBLE);
if (startX > screenWidth / 2) {
// 在屏幕右边,调节声音
updateVoice((int) Math.min(
Math.max(distanceY * SLIDE_MULTIPLE / screenHeight * maxVolume + currentVolume, 0),
maxVolume));
} else {
// 在屏幕左边,调节亮度
setBrightness(Math.min(
Math.max(distanceY * SLIDE_MULTIPLE / screenHeight * MAX_SCREEN_BRIGHTNESS + currentBrightness, 0),
MAX_SCREEN_BRIGHTNESS));
}
break;
调节音量,调节的是媒体音量,调节的同时会改变系统媒体音量的大小
private void updateVoice(int index) {
if (audioManager != null) {
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, index, 0);
tvDetail.setText("媒体音量");
seekBar.setProgress(index);
}
}
调节屏幕亮度,只是针对这个界面的亮度,并不会改变系统屏幕亮度的大小
public void setBrightness(float brightness) {
WindowManager.LayoutParams lp = getWindow().getAttributes();
if (lp.screenBrightness > 1) {
lp.screenBrightness = 1;
} else {
lp.screenBrightness = brightness / MAX_SCREEN_BRIGHTNESS;
}
Log.e(TAG, "screenBrightness: " + lp.screenBrightness);
getWindow().setAttributes(lp);
tvDetail.setText("屏幕亮度");
seekBar.setProgress((int) brightness);
}
上文说了,调节亮度不会改变系统屏幕亮度的值,所以获取最后一次亮度改变的值就不能像音量一样直接获取系统的值,而是需要在手指抬起时记录最后一次改变的值,代码如下:
case MotionEvent.ACTION_UP:
llDetail.setVisibility(View.GONE);
// 得到最后一次改变亮度的值
currentBrightness = (int) (distanceY * SLIDE_MULTIPLE / screenHeight * MAX_SCREEN_BRIGHTNESS + currentBrightness);
break;
- 至于添加字幕,playerView绑定监听,设置自定义的事件等我这都没有用到,需要用到的可以去Github上查看,地址之前已经给出。