android - 如何对 Android Exoplayer 中的功能进行质量控制?
问题描述
我想在我的 Android 应用程序的 exoplayer 中播放 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 流。我正在尝试使用 exoplayer 版本更新它。
https://github.com/yoobi/exoplayer-kotlin/tree/master/qualityselector
exo_player_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
}
}
推荐阅读
- python - 代码中是否缺少无法填充其他信息的内容
- javascript - 如何让 React Redux 异步操作返回一个承诺?
- reactjs - Material-UI, React Grid Component 响应式布局问题
- thingsboard - 如果 devicec 处于非活动状态,则转换部分遥测数据
- node.js - 为什么我的任何 npm 命令都不起作用?安装 npm install 时总是发出 ERR 抱怨?
- angular - 如何在Angular的多个选择字段中默认选择第一项?
- node.js - Botfront 未在根 URL 上打开
- azure-pipelines-yaml - YAML 管道中 if 语句中的测试路径
- oracle - Liquibase 无法执行在 Oracle 中作为 SQL 脚本工作的 sql 语句
- javascript - getState() 在几秒钟后更改已调度操作的值