In my application, I had an infinite animation. Actually, it's a TextSwitcher animation running from left to right. However, whenever I tried to prepare the player, I got a jitter issue with the text's animation. Although it's just a few milliseconds, it caused my texts was like jumping from left to right. Here's the prepare video method:
private fun prepareVideo(uri: Uri): SimpleExoPlayer? {
val simpleExoPlayer = SimpleExoPlayer
.Builder(this) // .setLoadControl(defaultLoadControl)
.build()
val mediaItem = MediaItem.fromUri(uri)
simpleExoPlayer.setMediaItem(mediaItem)
val eventListener: Player.EventListener = object : Player.EventListener {
override fun onPlaybackStateChanged(state: Int) {
if (state == ExoPlayer.STATE_READY) {
playVideo(simpleExoPlayer)
}
}
}
simpleExoPlayer.addListener(eventListener)
simpleExoPlayer.playWhenReady = false
simpleExoPlayer.prepare()
showToast("Prepare video")
return simpleExoPlayer
}
Do you guys have any idea to fix it?
If you thinks problem in this part of code i can recommend you to try initialize your SimpleExoPlayer object in application class and use it's instance all across application.
Try with this code
var exoPlayer: SimpleExoPlayer? = null
private fun setupVideo(uriString: String) {
val appName = R.string.app_name
val trackSelector: TrackSelector = DefaultTrackSelector()
val userAgent = context.let { Util.getUserAgent(it, it.getString(appName)) }
val sourceFactory = DefaultDataSourceFactory(requireContext(), userAgent)
val uri = Uri.parse(uriString)
val mediaSource = ProgressiveMediaSource.Factory(sourceFactory).createMediaSource(uri)
exoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector)
exoPlayer?.prepare(mediaSource)
exoPlayer?.playWhenReady = false
pvPlayer.player = exoPlayer
}
After quite some time investigating, I found the issue is creating an ExoPlayer instance.
val simpleExoPlayer = SimpleExoPlayer
.Builder(this) // .setLoadControl(defaultLoadControl)
.build()
Just this simple blook took about 0.1 seconds to finish. As a result, it froze the animation for a short period of time. I decided to create it on splash screen and reused it whenever possible.
Related
Im migrating a Video screen from XML to Compose., I need to display Ads with the IMA plugin.
Right now, I'm using this dependencies:
implementation "androidx.media3:media3-ui:1.0.0-beta02"
implementation 'androidx.media3:media3-exoplayer:1.0.0-beta02'
implementation "androidx.media3:media3-exoplayer-ima:1.0.0-beta02"
And im implementing the player as follows:
#Composable
fun VideoPlayer(videoUri: String) {
val context = LocalContext.current
val exoPlayer = remember {
ExoPlayer.Builder(context)
.build()
.apply {
val defaultDataSourceFactory = DefaultDataSource.Factory(context)
val dataSourceFactory: DataSource.Factory = DefaultDataSource.Factory(
context,
defaultDataSourceFactory
)
val source = ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(MediaItem.fromUri(videoUri))
setMediaSource(source)
prepare()
}
}
DisposableEffect(
AndroidView(factory = {
PlayerView(context).apply {
hideController()
player = exoPlayer
}
})
) {
onDispose { exoPlayer.release() }
}
}
But, when I run my app and navigate to the ExoPlayer screen, I get this error:
java.lang.ClassCastException: com.google.android.exoplayer2.ui.DefaultTimeBar cannot be cast to androidx.media3.ui.TimeBar
I find this odd, but perhaps in ExoPlayer3 you can customize the TimeBar and choose what class to use. Has someone has faced this before?
I implement exoplayer2 in my app for playing HLS Videos, but sometimes it plays just sound, and doesn't work correctly. What am I supposed to do? I couldn't find why this happens sometimes.
this is the code for initializing player :
fun initPalyer(){
val mainHandler = Handler()
val bandwidthMeter: BandwidthMeter = DefaultBandwidthMeter.Builder(context).build()
bandwidthMeter.addEventListener(mainHandler!!, this)
val trackSelectionFactory: TrackSelection.Factory = AdaptiveTrackSelection.Factory()
trackSelector = DefaultTrackSelector(context, trackSelectionFactory)
val builder = ParametersBuilder(context)
trackSelectorParameters = builder.build()
trackSelector!!.parameters = trackSelectorParameters
var rendersFactory: RenderersFactory = app.buildRenderersFactory(false)
player = SimpleExoPlayer.Builder(
context, renderersFactory
)
.setTrackSelector(trackSelector!!)
.setBandwidthMeter(bandwidthMeter)
//.setLoadControl(loadControl)
.build()
player!!.addListener(this)
loadState()
playerView.player = player!!
}
the code for preparing player :
private fun preparePlayer(uri: Uri) {
val mediaSource = MediaSourceBuilder().build(uri)
durationSet = false
player?.prepare(mediaSource, true, false)
}
and the code for creating mediaSources :
class MediaSourceBuilder {
//Build various MediaSource depending upon the type of Media for a given video/audio uri
fun build(uri: Uri): MediaSource {
val userAgent = PlayerConstants.USER_AGENT
val lastPath = uri.lastPathSegment?:""
val defaultHttpDataSourceFactory = DefaultHttpDataSourceFactory(userAgent)
if(lastPath.contains(PlayerConstants.FORMAT_MP3) || lastPath.contains(PlayerConstants.FORMAT_MP4)){
return ExtractorMediaSource.Factory(defaultHttpDataSourceFactory)
.createMediaSource(uri)
}else if(lastPath.contains(PlayerConstants.FORMAT_M3U8)){
return HlsMediaSource.Factory(defaultHttpDataSourceFactory)
.setAllowChunklessPreparation(true)
.createMediaSource(uri)
}else{
val dashChunkSourceFactory = DefaultDashChunkSource.Factory(defaultHttpDataSourceFactory)
return DashMediaSource.Factory(dashChunkSourceFactory, defaultHttpDataSourceFactory)
.createMediaSource(uri)
}
}
You might wanna change your DataSourceFactory, in case your URL are in HTTPS you might end up with an error, try using DefaultDataSourceFactory instead of DefaultHttpDataSourceFactory
This is my code that I wrote but when I click button to initialize ExpoPlayer it doesn't work.
private fun initializeExoplayer(){
simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(
DefaultRenderersFactory(context),
DefaultTrackSelector(adaptiveTrackSelectionFactory)
)
simpleExoPlayer.prepare(buildMediaSource())
itemView.simpleExoPlayerView.player = simpleExoPlayer
simpleExoPlayer.seekTo(playbackPosition)
simpleExoPlayer.playWhenReady = true
simpleExoPlayer.addListener(this)
}
private fun buildMediaSource(): MediaSource {
var uri: Uri = Uri.parse("https://player.vimeo.com/external/357507131.m3u8?s=17a1326569611e13080cbe99344549fcc6b5613d")
val dataSourceFactory: DataSource.Factory = DefaultHttpDataSourceFactory(Util.getUserAgent(context,"App Name"))
return HlsMediaSource.Factory(dataSourceFactory).createMediaSource(uri)
}
I've tried with above code but it does't work anyone can help me for this issue???
I am using exoplayer in my application. I am using it to play video from a url. What i am trying to do is that i have three different urls for high,medium and low quality of same the video, and i would like to let the user to be able to change the video quality manually.
{
"lowQualityUrl":"string url",
"mediumQualityUrl":"string url",
"highQualityUrl":"string url"
}
In JWplayer there is an option to add different sources/url for different qualities. Is there something similar that can be done in exoplayer...?
Edit : I don't want to play videos one after another. I just want to switch to a different quality of the same video, like in youtube. But instead of using a single url for the source, what i have are 3 different urls for 3 qualities(low,medium,high) of the same video.
I found a solution or rather a workaround for the issue. I am using the exoplayer inside a recylcerview. This code might need some optimization.
I got this idea from another answer which was under this question,which had this github link, but i think the author deleted it.
What i did was create a class for keeping all the urls for a particular video. Then showing a Spinner above the exoplayer, and when user selects a particular quality then i prepare the exoplayer with the new URL, and then seekto to the previously playing position. You can ignore the StringUtils methods.
VideoPlayerConfig.kt
object VideoPlayerConfig {
//Minimum Video you want to buffer while Playing
val MIN_BUFFER_DURATION = 3000
//Max Video you want to buffer during PlayBack
val MAX_BUFFER_DURATION = 5000
//Min Video you want to buffer before start Playing it
val MIN_PLAYBACK_START_BUFFER = 1500
//Min video You want to buffer when user resumes video
val MIN_PLAYBACK_RESUME_BUFFER = 5000
}
VideoQuality.kt
You can change this class according to your need. I need to store exactly 3 urls for low,medium and high quality urls. And i needed to show them in that order as well in the spinner.
class VideoQuality {
private val videoQualityUrls = HashMap<String, String>()
companion object {
val LOW = getStringResource(R.string.low)
val MEDIUM = getStringResource(R.string.medium)
val HIGH = getStringResource(R.string.high)
}
val qualityArray
get() = arrayListOf<String>().apply {
if (hasQuality(LOW)) add(LOW)
if (hasQuality(MEDIUM)) add(MEDIUM)
if (hasQuality(HIGH)) add(HIGH)
}
var defaultVideoQuality: String? = HIGH
var lowQuality: String?
set(value) {
setVideoQualityUrl(LOW, value)
}
get() = videoQualityUrls[LOW] ?: ""
var mediumQuality: String?
set(value) {
setVideoQualityUrl(MEDIUM, value)
}
get() = videoQualityUrls[MEDIUM] ?: ""
var highQuality: String?
set(value) {
setVideoQualityUrl(HIGH, value)
}
get() = videoQualityUrls[HIGH] ?: ""
private fun setVideoQualityUrl(quality: String?, url: String?) {
if (url != null && quality != null) {
videoQualityUrls[quality] = url
}
}
private fun hasQuality(quality: String?): Boolean {
if (videoQualityUrls[quality] == null) {
return false
}
return true
}
fun getVideoQualityUrl(quality: String?): String? {
return videoQualityUrls[quality]
}
}
Methods to implement for the exoplayer
private fun initializePlayer() {
if (exoPlayer == null) {
val loadControl = DefaultLoadControl.Builder()
.setBufferDurationsMs(2 * VideoPlayerConfig.MIN_BUFFER_DURATION, 2 * VideoPlayerConfig.MAX_BUFFER_DURATION, VideoPlayerConfig.MIN_PLAYBACK_START_BUFFER, VideoPlayerConfig.MIN_PLAYBACK_RESUME_BUFFER)
.createDefaultLoadControl()
//Create a default TrackSelector
val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory()
val trackSelector = DefaultTrackSelector(videoTrackSelectionFactory)
exoPlayer = ExoPlayerFactory.newSimpleInstance(itemView.context, DefaultRenderersFactory(itemView.context), trackSelector, loadControl)
exoPlayer!!.addListener(PlayEventListener())
val videoQualityInfo:VideoQuality = videoListVideoDataHolderData!!.videoQualityUrls //Just an object that i created and stored in a dataHolder for this view.
val url = videoQualityInfo.getVideoQualityUrl(videoQualityInfo.defaultVideoQuality) ?: ""
preparePlayer(url)
}
}
private fun preparePlayer(url: String) {
if (url.isNotEmpty()) {
val mediaSource = buildMediaSource(StringUtils.makeHttpUrl(url))
exoPlayer?.prepare(mediaSource)
videoView.player = exoPlayer
} else {
Log.d(APPTAG, "NO DEFAULT URL")
}
}
private fun buildMediaSource(url: String): ProgressiveMediaSource {
val mUri: Uri = Uri.parse(url)
val dataSourceFactory = DefaultDataSourceFactory(
itemView.context,
Util.getUserAgent(itemView.context, getStringResource(R.string.app_name))
)
val videoSource = ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(mUri)
return videoSource
}
And then in the Spinner/QualitySelector's OnItemSelectedListener
videoQualitySpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
val currentTime = exoPlayer?.currentPosition
val isReadyToPlay = exoPlayer?.playWhenReady
val urlToBuild = when (videoQualityUrls.qualityArray[position]) {
VideoQuality.LOW -> videoQualityUrls.lowQuality
VideoQuality.MEDIUM -> videoQualityUrls.mediumQuality
else -> videoQualityUrls.highQuality
}
Log.d(APPTAG, "VIDEO DETAILS :::: ${currentTime} ${isReadyToPlay} ${urlToBuild}")
if (!urlToBuild.isNullOrEmpty()) {
val mediaSource = buildMediaSource(StringUtils.makeHttpUrl(urlToBuild))
exoPlayer?.prepare(mediaSource)
exoPlayer?.playWhenReady = isReadyToPlay ?: false
exoPlayer?.seekTo(currentTime ?: 0)
}
}
}
I have a class activity_player layout in which I have exoplayer2.ui.PlayerView and I created exo_player_control_view so that it overrides default controls in ExoPlayer. So I wanted to use Databinding in newly created custom control view but don't know how to do it. Any advice?
It is actually an open issue over here, but yet to be solved. So is there anyone who had a workaround to make exo_player_control_view Databinding friendly?
Use Like this >>>>>
private val binding by lazy {
ActivityPipVideoPlayerBinding.inflate(layoutInflater)}
private val exoPLayerBinding by lazy {
VdoExoControlViewBinding.inflate(LayoutInflater.from(this), binding.root, true)
}
You can use binding variable inside fragment/activity to access the playerView inside fragment/activity and
val uri: Uri? = if (url is String) Uri.parse(url as String?) else url as Uri?
val trackSelector =
DefaultTrackSelector(AdaptiveTrackSelection.Factory(DefaultBandwidthMeter()))
val player: SimpleExoPlayer = ExoPlayerFactory.newSimpleInstance(view.context, trackSelector)
val dataSourceFactory = DefaultDataSourceFactory(view.context, "ua")
val mediaSource =
ExtractorMediaSource(uri, dataSourceFactory, DefaultExtractorsFactory(), null, null)
player.prepare(mediaSource)
player.apply {
volume = 0f
repeatMode = Player.REPEAT_MODE_ONE
playWhenReady = true
videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT
}
binding.playerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FILL)
binding.playerView.player = player