如何在Android Exoplayer中实现质量控制功能



我想在Android应用程序的外部层中播放HLS视频。以下是代码-

DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
//DefaultTrackSelector chooses tracks in the media item
DefaultTrackSelector trackSelector = new DefaultTrackSelector(this);
trackSelector.setParameters(trackSelector.buildUponParameters().setMaxVideoSizeSd());
mPlayer = new SimpleExoPlayer.Builder(this).setTrackSelector(trackSelector).build();
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this, Util.getUserAgent(this, "exoplayerapp"), bandwidthMeter);
MediaSource mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse(url));
mPlayer.prepare(mediaSource);
playerView.setKeepScreenOn(true);
playerView.requestFocus();
playerView.setPlayer(mPlayer);
mPlayer.setPlayWhenReady(true);

它运行良好,但我无法添加质量控制。HSL流有不同的质量格式,如249p、360p、480p,但我无法选择曲目。我应该在哪里更改代码?

我做了一个repo,在那里你可以找到我做的质量选择器,它可以帮助你处理HLS流。我正试着用系外层的版本来更新它。

https://github.com/yoobi/exoplayer-kotlin/tree/master/qualityselector


exo_layer_control_view.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout 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="wrap_content"
android:layout_gravity="bottom"
android:layoutDirection="ltr"
android:background="#CC000000"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="4dp"
android:orientation="horizontal">
<ImageButton android:id="@id/exo_rew"
style="@style/ExoMediaButton.Rewind"/>
<ImageButton android:id="@id/exo_play"
style="@style/ExoMediaButton.Play"/>
<ImageButton android:id="@id/exo_pause"
style="@style/ExoMediaButton.Pause"/>
<ImageButton android:id="@id/exo_ffwd"
style="@style/ExoMediaButton.FastForward"/>
<LinearLayout
android:id="@+id/exo_quality_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageButton
android:id="@+id/exo_quality"
app:srcCompat="@drawable/ic_settings"
style="@style/ExoMediaButton"
android:visibility="gone"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView android:id="@id/exo_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textStyle="bold"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:includeFontPadding="false"
android:textColor="#FFBEBEBE"/>
<com.google.android.exoplayer2.ui.DefaultTimeBar
android:id="@id/exo_progress"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="26dp"/>
<TextView android:id="@id/exo_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textStyle="bold"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:includeFontPadding="false"
android:textColor="#FFBEBEBE"/>
</LinearLayout>
</LinearLayout>

主要活动

import android.app.Dialog
import android.os.Bundle
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.trackselection.MappingTrackSelector
import com.google.android.exoplayer2.ui.PlayerView
import com.google.android.exoplayer2.ui.TrackSelectionDialogBuilder
import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.MimeTypes
import com.google.android.exoplayer2.util.Util
const val HLS_STATIC_URL = "https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8"
const val STATE_RESUME_WINDOW = "resumeWindow"
const val STATE_RESUME_POSITION = "resumePosition"
const val STATE_PLAYER_FULLSCREEN = "playerFullscreen"
const val STATE_PLAYER_PLAYING = "playerOnPlay"
const val MAX_HEIGHT = 539
const val MAX_WIDTH = 959
class MainActivity : AppCompatActivity() {
private lateinit var exoPlayer: SimpleExoPlayer
private lateinit var dataSourceFactory: DataSource.Factory
private lateinit var trackSelector: DefaultTrackSelector
private lateinit var playerView: PlayerView
private lateinit var exoQuality: ImageButton
private var currentWindow = 0
private var playbackPosition: Long = 0
private var isFullscreen = false
private var isPlayerPlaying = true
private var trackDialog: Dialog? = null
private val mediaItem = MediaItem.Builder()
.setUri(HLS_STATIC_URL)
.setMimeType(MimeTypes.APPLICATION_M3U8)
.build()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
playerView = findViewById(R.id.player_view)
exoQuality = playerView.findViewById(R.id.exo_quality)
dataSourceFactory = DefaultDataSourceFactory(this,
Util.getUserAgent(this, "testapp"))
exoQuality.setOnClickListener{
if(trackDialog == null){
initPopupQuality()
}
trackDialog?.show()
}
if (savedInstanceState != null) {
currentWindow = savedInstanceState.getInt(STATE_RESUME_WINDOW)
playbackPosition = savedInstanceState.getLong(STATE_RESUME_POSITION)
isFullscreen = savedInstanceState.getBoolean(STATE_PLAYER_FULLSCREEN)
isPlayerPlaying = savedInstanceState.getBoolean(STATE_PLAYER_PLAYING)
}
}
private fun initPlayer(){
trackSelector = DefaultTrackSelector(this)
// When player is initialized it'll be played with a quality of MaxVideoSize to prevent loading in 1080p from the start
trackSelector.setParameters(trackSelector.buildUponParameters().setMaxVideoSize(MAX_WIDTH,MAX_HEIGHT))
exoPlayer = SimpleExoPlayer.Builder(this).setTrackSelector(trackSelector).build().apply {
playWhenReady = isPlayerPlaying
seekTo(currentWindow, playbackPosition)
setMediaItem(mediaItem)
prepare()
}
playerView.player = exoPlayer
//Listener on player
exoPlayer.addListener(object: Player.Listener{
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
if(playbackState == Player.STATE_READY){
exoQuality.visibility = View.VISIBLE
}
}
})
}
private fun releasePlayer(){
isPlayerPlaying = exoPlayer.playWhenReady
playbackPosition = exoPlayer.currentPosition
currentWindow = exoPlayer.currentWindowIndex
exoPlayer.release()
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putInt(STATE_RESUME_WINDOW, exoPlayer.currentWindowIndex)
outState.putLong(STATE_RESUME_POSITION, exoPlayer.currentPosition)
outState.putBoolean(STATE_PLAYER_FULLSCREEN, isFullscreen)
outState.putBoolean(STATE_PLAYER_PLAYING, isPlayerPlaying)
super.onSaveInstanceState(outState)
}
override fun onStart() {
super.onStart()
if (Util.SDK_INT > 23) {
initPlayer()
playerView.onResume()
}
}
override fun onResume() {
super.onResume()
if (Util.SDK_INT <= 23) {
initPlayer()
playerView.onResume()
}
}
override fun onPause() {
super.onPause()
if (Util.SDK_INT <= 23) {
playerView.onPause()
releasePlayer()
}
}
override fun onStop() {
super.onStop()
if (Util.SDK_INT > 23) {
playerView.onPause()
releasePlayer()
}
}
// QUALITY SELECTOR
private fun initPopupQuality() {
val mappedTrackInfo = trackSelector.currentMappedTrackInfo
var videoRenderer : Int? = null
if(mappedTrackInfo == null) return else exoQuality.visibility = View.VISIBLE
for(i in 0 until mappedTrackInfo.rendererCount){
if(isVideoRenderer(mappedTrackInfo, i)){
videoRenderer = i
}
}
if(videoRenderer == null){
exoQuality.visibility = View.GONE
return
}
val trackSelectionDialogBuilder = TrackSelectionDialogBuilder(this, getString(R.string.qualitySelector), trackSelector, videoRenderer)
trackSelectionDialogBuilder.setTrackNameProvider{
// Override function getTrackName
getString(R.string.exo_track_resolution_pixel, it.height)
}
trackDialog = trackSelectionDialogBuilder.build()
}
private fun isVideoRenderer(mappedTrackInfo: MappingTrackSelector.MappedTrackInfo, rendererIndex: Int): Boolean {
val trackGroupArray = mappedTrackInfo.getTrackGroups(rendererIndex)
if (trackGroupArray.length == 0) {
return false
}
val trackType = mappedTrackInfo.getRendererType(rendererIndex)
return C.TRACK_TYPE_VIDEO == trackType
}
}

最新更新