myplayer:史上最好用的Android音视频播放器-ExoPlayer的使用及自定义UI 2024-04-26 13:14:36 0 0 ExoPlayer是运行在YouTube app Android版本上的视频播放器。不仅功能强大,而且使用简单,可定制性强。ExoPlayer也是Google官方推荐的Android媒体播放器,可以在Android官方文档的音频和视频目录中找到。 一,优点和缺点 优点: 1,支持DASH和SmoothStreaming这两种数据格式的资源,而MediaPlayer对这两种数据格式都不支持。它还支持其它格式的数据资源,比如MP4, M4A, FMP4, WebM, MKV, MP3, Ogg, WAV, MPEG-TS, MPEG-PS, FLV and ADTS (AAC)等 2,支持高级的HLS特性,比如能正确的处理#EXT-X-DISCONTINUITY标签 3,无缝连接,合并和循环播放多媒体的能力 4,和应用一起更新播放器(ExoPlayer),因为ExoPlayer是一个集成到应用APK里面的库,你可以决定你所想使用的ExoPlayer版本,并且可以随着应用的更新把ExoPlayer更新到一个最新的版本。 5,较少的关于设备的特殊问题,并且在不同的Android版本和设备上很少会有不同的表现。 6,在Android4.4(API level 19)以及更高的版本上支持Widevine通用加密 7,为了符合你的开发需求,播放器支持自定义和扩展。其实ExoPlayer为此专门做了设计,并且允许很多组件可以被自定义的实现类替换。 8,使用官方的扩展功能可以很快的集成一些第三方的库,比如IMA扩展功能通过使用互动媒体广告SDK可以很容易地将视频内容货币化(变现) 缺点: 1,在某些设备上播放音频,ExoPlayer可能会比MediaPlayer消耗更多的电量。 二,使用 1,添加依赖implementation 'com.google.android.exoplayer:exoplayer:2.X.X' 目前最新版本是2.10.5,最新的版本可以查看github地址。 上面是省事的方式,依赖了整个ExoPlayer库。也可以根据自己的需求选择性添加依赖,如核心库和UI库,这两个可以满足基本上的视频播放需求:implementation 'com.google.android.exoplayer:exoplayer-core:2.X.X'implementation 'com.google.android.exoplayer:exoplayer-ui:2.X.X' 整个ExoPlayer库包含5个字库,依赖5个子库和依赖整个库效果是一样的。exoplayer-core:核心功能 (必要)exoplayer-dash:支持DASH内容exoplayer-hls:支持HLS内容exoplayer-smoothstreaming:支持SmoothStreaming内容exoplayer-ui:用于ExoPlayer的UI组件和相关的资源。 2,添加Java8的支持android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 }} 3,布局中引入<com.google.android.exoplayer2.ui.PlayerView android:id="@+id/video_view" android:layout_width="match_parent" android:layout_height="match_parent"/> 4,activity中使用val player = ExoPlayerFactory.newSimpleInstance(this,DefaultTrackSelector(),DefaultLoadControl())player.playWhenReady = truevideo_view.player = playerval uri = Uri.parse("https://cdn.xxxxxx.com/new-sing/66c3d05eaa177e07d57465f948f0d8b934b7a7ba.mp4")val dataSourceFactory = DefaultHttpDataSourceFactory("user-agent")val videoSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(uri)// 播放player.prepare(mediaSource) 此时已经可以播放了,如下: 5,释放资源 适当时候释放资源player.release() 6,添加监听 Player.EventListener中有很多监听方法,但都是default类型的,可以根据需求添加实现。player.addListener(object:Player.EventListener{ override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { Log.e("ExoPlayer","playWhenReady: $playWhenReady +$playbackState") when (playbackState){ Player.STATE_BUFFERING-> Toast.makeText(this@MainActivity,"加载中",Toast.LENGTH_LONG).show() Player.STATE_READY-> Toast.makeText(this@MainActivity,"播放中",Toast.LENGTH_LONG).show() Player.STATE_ENDED-> Toast.makeText(this@MainActivity,"播放完成",Toast.LENGTH_LONG).show() } } override fun onPlayerError(error: ExoPlaybackException?) { Log.e("ExoPlayer","ExoPlaybackException: $error") } ...}) 三,其他功能 1,Clipping a video(视频裁剪) 播放指定的范围:MediaSource videoSource = new ProgressiveMediaSource.Factory(...).createMediaSource(videoUri);// Clip to start at 5 seconds and end at 10 seconds.ClippingMediaSource clippingSource = new ClippingMediaSource( videoSource, /* startPositionUs= */ 5_000_000, /* endPositionUs= */ 10_000_000); 2,Side-loading a subtitle file(加载台词)// Build the video MediaSource.MediaSource videoSource = new ProgressiveMediaSource.Factory(...).createMediaSource(videoUri);// Build the subtitle MediaSource.Format subtitleFormat = Format.createTextSampleFormat( id, // An identifier for the track. May be null. MimeTypes.APPLICATION_SUBRIP, // The mime type. Must be set correctly. selectionFlags, // Selection flags for the track. language); // The subtitle language. May be null.MediaSource subtitleSource = new SingleSampleMediaSource.Factory(...) .createMediaSource(subtitleUri, subtitleFormat, C.TIME_UNSET);// Plays the video with the sideloaded subtitle.MergingMediaSource mergedSource = new MergingMediaSource(videoSource, subtitleSource); 3,Advanced composition(视频合并) 下面两种方式实现第一个视频播放两次,第二个播放一次 方法一:MediaSource firstSource = new ProgressiveMediaSource.Factory(...).createMediaSource(firstVideoUri);MediaSource secondSource = new ProgressiveMediaSource.Factory(...).createMediaSource(secondVideoUri);// Plays the first video twice.LoopingMediaSource firstSourceTwice = new LoopingMediaSource(firstSource, 2);// Plays the first video twice, then the second video.ConcatenatingMediaSource concatenatedSource = new ConcatenatingMediaSource(firstSourceTwice, secondSource); 方法二:MediaSource firstSource = new ProgressiveMediaSource.Factory(...).createMediaSource(firstVideoUri);MediaSource secondSource = new ProgressiveMediaSource.Factory(...).createMediaSource(secondVideoUri);// Plays the first video twice, then the second video.ConcatenatingMediaSource concatenatedSource = new ConcatenatingMediaSource(firstSource, firstSource, secondSource); 还有其他功能,可以参看官方文档:Hello world! - ExoPlayer 四,自定义UI 1,简单自定义 这种方式是通过修改ExoPlayer预留的布局文件来实现定制化,但是这种方式只能修改特定的一些UI。 在源代码中可以找到int controllerLayoutId = R.layout.exo_player_control_view; 也就是说ExoPlayer默认使用的是这个布局,我们可以在代码中新建一个名为:exo_player_control_view 的layout,或者在xml中添加<com.google.android.exoplayer2.ui.PlayerView android:id="@+id/video_view" android:layout_width="match_parent" android:layout_height="match_parent" app:controller_layout_id="@layout/xxx_view"/> 指定我们的layout来实现覆盖的目的。如下:<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:background="@drawable/player_bottom_bg"> <TextView android:id="@+id/exo_position" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="10dp" android:layout_marginEnd="10dp" android:layout_marginTop="10dp" android:textColor="@android:color/white" app:layout_constraintLeft_toLeftOf="parent" /> <TextView android:id="@+id/exo_duration" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="10dp" android:layout_marginStart="10dp" android:layout_marginTop="10dp" android:textColor="@android:color/white" app:layout_constraintRight_toRightOf="parent" /> <com.google.android.exoplayer2.ui.DefaultTimeBar android:id="@id/exo_progress" android:layout_width="0dp" android:layout_height="26dp" android:layout_weight="1" app:layout_constraintBottom_toBottomOf="@id/exo_position" app:layout_constraintLeft_toRightOf="@id/exo_position" app:layout_constraintRight_toLeftOf="@id/exo_duration" app:layout_constraintTop_toTopOf="@id/exo_position" app:played_color="#FFDE81" app:unplayed_color="@android:color/black" app:buffered_color="@android:color/darker_gray"/> <ImageView android:id="@+id/exo_play" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/play_btn" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/exo_progress" /> <ImageView android:id="@+id/exo_pause" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/pause_btn" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/exo_progress" /> <android.support.constraint.Guideline android:id="@+id/gl" android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="vertical" app:layout_constraintGuide_percent="0.5" /> <ImageView android:id="@+id/exo_rew" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/pre_btn" android:layout_marginEnd="8dp" app:layout_constraintRight_toLeftOf="@id/gl" app:layout_constraintTop_toTopOf="@id/exo_play" /> <ImageView android:id="@+id/exo_ffwd" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/next_btn" android:layout_marginStart="8dp" app:layout_constraintLeft_toRightOf="@id/gl" app:layout_constraintTop_toTopOf="@id/exo_play" /> </android.support.constraint.ConstraintLayout></FrameLayout> 效果如下: 缺点: 这种方式控件的id不能随便起,要与exoPlayer原来PlaybackControlView的布局控件id,名称一致,不然就人家的代码中是获取不到你的id的。 有这些控件id可以使用: exo_play –>播放 exo_pause –>暂停 exo_rew –>后退 exo_ffwd –>前进 exo_prev –>上一个 exo_next –>下一个 exo_repeat_toggle –>重复模式开关 exo_duration –>视频总时长 exo_position –>当前播放位置 exo_progress –>播放进度 2,高级自定义 高级自定义可以实现任意的效果,如下: 1,首先需要重写: com.google.android.exoplayer2.ui.PlayerView。这里的“重写”是指新建一个类比如MyPlayerView,然后复制PlayerView中的代码到MyPlayerView。使用时引用MyPlayerView。 2,引入自定义的布局,如:senior_diy_player_control_view.xml,通过controller_layout_id属性设置布局,如下:<com.example.exoplayerdemo.custom.MyPlayerView android:id="@+id/video_view" android:layout_width="match_parent" android:layout_height="match_parent" app:show_buffering="always" app:controller_layout_id="@layout/senior_diy_player_control_view" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"/> 3,改变了布局以后需要添加自定义的事件,需要重写com.google.android.exoplayer2.ui.PlayerControlView,跟上面一样新建一个MyPlayerControlView,然后把代码复制进去,同时需要把MyPlayerView中所有的PlayerControlView的引用改为MyPlayerControlView的引用,此时MyPlayerControlView就生效了,在这里可以找到原布局中的播放、暂停、快进、快退按钮:private final View playButton;private final View pauseButton;private final View fastForwardButton;private final View rewindButton; 以及对应的点击事件:playButton = findViewById(R.id.exo_play);if (playButton != null) { playButton.setOnClickListener(componentListener);}pauseButton = findViewById(R.id.exo_pause);if (pauseButton != null) { pauseButton.setOnClickListener(componentListener);} 以及具体的事件方法:@Overridepublic void onClick(View view) { ... if (playButton == view) { ... } else if (pauseButton == view) { ... } ...} 接下来怎么做就不用多说了。 4,此时还不能改变进度条的样式,进度条通过com.google.android.exoplayer2.ui.DefaultTimeBar实现的,如果要改变进度条样式就需要重写DefaultTimeBar,和上面的一样,新建一个MyTimeBar,把代码复制进去,修改MyPlayerControlView中对DefaultTimeBar的引用,DefaultTimeBar继承自View,修改样式也就是自定义View的操作,比如我这里把原来的进度改成圆头的代码:private void drawTimeBar(Canvas canvas) { .. if (duration <= 0) {// canvas.drawRect(progressBar.left, barTop, progressBar.right, barBottom, unplayedPaint); // 改为圆角 canvas.drawRoundRect(progressBar.left, barTop, progressBar.right, barBottom, barHeight/2,barHeight/2, unplayedPaint); return; } ... if (progressLeft < progressBar.right) {// canvas.drawRect(progressLeft, barTop, progressBar.right, barBottom, unplayedPaint); // 改为圆角 canvas.drawRoundRect(scrubberBar.left, barTop, progressBar.right, barBottom, barHeight/2,barHeight/2,unplayedPaint); } ... if (bufferedRight > bufferedLeft) {// canvas.drawRect(bufferedLeft, barTop, bufferedRight, barBottom, bufferedPaint); // 改为圆角 canvas.drawRoundRect(bufferedLeft, barTop, bufferedRight, barBottom, barHeight/2,barHeight/2,bufferedPaint); } if (scrubberBar.width() > 0) {// canvas.drawRect(scrubberBar.left, barTop, scrubberBar.right, barBottom, playedPaint); // 改为圆角 canvas.drawRoundRect(scrubberBar.left, barTop, scrubberBar.right, barBottom, barHeight/2,barHeight/2, playedPaint); } ...} 这里就不多说了。 代码地址:https://gitee.com/HappyAndroid666/ExoPlayerDemo,感觉有用的帮忙star一下 收藏(0)