I'm using the Exoplayer library to load videos in my application, but in certain videos I want to avoid that the user can't go forward in the video progress bar, only backward, until now I had achieved something by adding a listener to the DefaultTimeBar in this way:
var currentPosition: Long = 0
val exoPlayerProgressBar: DefaultTimeBar? = localPlayerView?.findViewById(
R.id.exo_progress
) as? DefaultTimeBar
exoPlayerProgressBar?.addListener(object: TimeBar.OnScrubListener {
override fun onScrubStart(timeBar: TimeBar, position: Long) {
currentPosition = position
}
override fun onScrubMove(timeBar: TimeBar, position: Long) {
if (currentPosition < position) {
timeBar.setPosition(position)
timeBar.setEnabled(false)
}
}
override fun onScrubStop(timeBar: TimeBar, position: Long, canceled: Boolean) {
timeBar.setEnabled(true)
}
})
But it doesn't work well at all, I think the listener takes a long time to be called and the position of the onScrubStart method is not exact and besides, although I manage to disable the user to advance, I haven't managed to do it if he clicks on a place on the bar. Is there another way that I don't know?
If you are using custom view for your xml then setting this in xml will work for androidx.media3.ui.DefaultTimeBar
app:touch_target_height="0dp"
I also got this answer from one of stackoverflow answer only but not having link as of now.
You should use ExoPlayer.Listener and override onPlaybackStateChanged() to detect state of playing video progress (Player.STATE_IDLE, Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED), use Player.STATE_READY to hide the progress bar and Player.STATE_BUFFERING to show the progress bar.
override fun onPlaybackStateChanged(playbackState: Int) {
when (playbackState) {
Player.STATE_ENDED -> {}
Player.STATE_READY -> hideProgressBar() //*Hide progressbar!
Player.STATE_BUFFERING -> showProgressBar() //*Show progressbar!
Player.STATE_IDLE -> {}
}
}
Listening to playback events
Related
What I am trying to do is retrieve data from the server when I click a button. When I click the button, I want to show my "Loading..." TextView for 2 seconds, and only then show the data I got from the server. How can I do this?
For now my animation is working, but the data is showing almost instantly. I want to delay that. Using Thread.sleep(2000) causes both the data and Loading to be delayed.
val loadingAnimation :TextView = findViewById<TextView>(R.id.loadingAnimationTextView)
val alphaAnim = AlphaAnimation(1.0f, 0.0f)
alphaAnim.startOffset = 0
alphaAnim.duration = 2000
alphaAnim.setAnimationListener(object : AnimationListener {
override fun onAnimationRepeat(animation: Animation?) {
//not sure what code to put here
}
override fun onAnimationEnd(animation: Animation) {
// make invisible when animation completes, you could also remove the view from the layout
loadingAnimation.setVisibility(View.INVISIBLE)
}
override fun onAnimationStart(animation: Animation?) {
loadingAnimation.setVisibility(View.VISIBLE)
}
})
loadingAnimation.setAnimation(alphaAnim)
Thread.sleep(2000)
You can use the handler for this task.
Handler(Looper.getMainLooper()).postDelayed({
// Show you data here
loadingAnimation.setVisibility(View.INVISIBLE)
}, 2000)
Here, 2000 = 2 seconds
It's probably easier to use the ViewPropertyAnimator stuff:
loadingAnimation.animate()
.alpha(0)
.duration(2000)
.withEndAction {
// data displaying code goes here
}.start()
but honestly I don't think there's anything wrong with populating an invisible list, and just making it visible when you want to display it. But that up there is a way to chain runnable code and animations, one after the other
Working with playlist on the ExoPlayer, I managed the setting of the playlist by following the documentation.
val firstSource = ProgressiveMediaSource.Factory(...).createMediaSource(firstVideoUri);
val secondSource = ProgressiveMediaSource.Factory(...).createMediaSource(secondVideoUri);
val playlist = ConcatenatingMediaSource(firstSource, secondSource);
player.prepare(playlist)
But I noticed that the transition between video is instantaneous. However, I would like to add a waiting stage between each video, exactly like youtube does (let's say 5 sec). Something like:
Is it already handled by the Exoplayer itself or not? If not what is a clean way to solve the problem ?
You can use following code.
object : CountDownTimer(1500, 500) {
override fun onTick(millisUntilFinished: Long) {
//Update your Ui.
}
override fun onFinish() {
//Thats it 5 seconds gone.
}
}.start()
For more details on CountDownTimer
I have tried the new BottomSheetBehaviour with design library 23.0.2 but i think it too limited. When I change state with setState() method, the bottomsheet use ad animation to move to the new state.
How can I change state immediately, without animation? I don't see a public method to do that.
Unfortunately it looks like you can't. Invocation of BottomSheetBehavior's setState ends with synchronous or asynchronous call of startSettlingAnimation(child, state). And there is no way to override these methods behavior cause setState is final and startSettlingAnimation has package visible modifier. Check the sources for more information.
I have problems with the same, but in a bit different way - my UI state changes setHideable to false before that settling animation invokes, so I'm getting IllegalStateException there. I will consider usage of BottomSheetCallback to manage this properly.
If you want to remove the show/close animation you can use dialog.window?.setWindowAnimations(-1). For instance:
class MyDialog(): BottomSheetDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.window?.setDimAmount(0f) // for removing the dimm
dialog.window?.setWindowAnimations(-1) // for removing the animation
return dialog
}
}
If you really need it, then you can resort to reflection:
fun BottomSheetBehavior.getViewDragHelper(): ViewDragHelper? = BottomSheetBehavior::class.java
.getDeclaredField("viewDragHelper")
.apply { isAccessible = true }
.let { field -> field.get(this) as? ViewDragHelper? }
fun ViewDragHelper.getScroller(): OverScroller? = ViewDragHelper::class.java
.getDeclaredField("mScroller")
.apply { isAccessible = true }
.let { field -> field.get(this) as? OverScroller? }
Then you can use these extension methods when the state changes:
bottomSheetBehavior.setBottomSheetCallback(object : BottomSheetCallback() {
override fun onSlide(view: View, offset: Float) {}
override fun onStateChanged(view: View, state: Int) {
if (state == STATE_SETTLING) {
try {
bottomSheetBehavior.getViewDragHelper()?.getScroller()?.abortAnimation()
} catch(e: Throwable) {}
}
}
})
I will add that the code is not perfect, getting fields every time the state changes is not efficient, and this is done for the sake of simplicity.
I have managed to create a line chart using MP Android Chart, And want to create a draggable line to set a limit. So if the value crosses the line value, then the user gets an alert. My use case is similar to the Android system Data usage limit.
I came across LimitLines - https://github.com/PhilJay/MPAndroidChart/wiki/The-Axis and also dragging using touch event callbacks https://github.com/PhilJay/MPAndroidChart/wiki/Interaction-with-the-Chart
My question is whether I can add the limit line dynamically on the response to a translate (onChartTranslate) event so I can simulate the limit setting? Is this a better approach than trying to overload the MarkerView ?
I managed to create a draggable (horizontal) LimitLine by using OnChartGestureListener to listen for begin and end of drag events:
chart.onChartGestureListener = object: OnChartGestureListener {
override fun onChartGestureEnd(me: MotionEvent?, lastPerformedGesture: ChartTouchListener.ChartGesture?) {
if (lastPerformedGesture == ChartTouchListener.ChartGesture.DRAG && limitLineDragEnabled) {
me?.let {
displayLimitLine(me)
}
limitLineDragEnabled = false
}
}
override fun onChartLongPressed(me: MotionEvent?) {
if (binding.chart.isFullyZoomedOut) {
limitLineDragEnabled = true
me?.let {
displayLimitLine(me)
}
}
}
}
and a ChartTouchListener to catch MotionEvents between drag begin and end (sadly, OnChartGestureListener wasn't able to help me there):
chart.onTouchListener = object: BarLineChartTouchListener(chart, chart.viewPortHandler.matrixTouch, 3f) {
#SuppressLint("ClickableViewAccessibility")
override fun onTouch(v: View?, me: MotionEvent?): Boolean {
if (limitLineDragEnabled) {
me?.let {
displayLimitLine(me)
}
}
return super.onTouch(v, me)
}
}
I am using ActionBar.Tabs with ViewPager, and it looks like
I implemented the ActionBar.TabListener and ViewPager.OnPageChangeListener to support that when user swipe pages in ViewPager, the tab indicator will change accordingly (the blue indicator will move to the corresponding tab).
Now I realized that the blue indicator changes without any animation, and it doesn't look good. (when I swipe from tab1 to tab2, the blue indicator disappear under tab1 and appear under tab2). Is there a way to change it so that when I switch tabs, the blue tab indicator moves smoothly between tabs?
Use the TabLayout class from the design support library:
https://developer.android.com/reference/android/support/design/widget/TabLayout.html
It has a setupWithViewPager to easily connect it with a ViewPager, and it has a sliding indicator bar.
In my case it appeared because of TabSelectedListener. In tab_layout.addOnTabSelectedListener() I overrode a onTabSelected method, where I opened another activity without a delay.
view.tab_layout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabReselected(tab: TabLayout.Tab?) {
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabSelected(tab: TabLayout.Tab?) {
if (tab?.position == 1) {
MapsActivity.showScreen(this#ThisFragment)
}
}
})
I rewrote that method with a delay:
override fun onTabSelected(tab: TabLayout.Tab?) {
if (tab?.position == 1) {
// Show tab animation and open an activity.
view.tab_layout.postDelayed({
// This check is optional.
if (isAdded && view.tab_layout.selectedTabPosition == 1) {
MapsActivity.showScreen(this#YourFragment)
}
}, 250)
}
}
Also I tried to change a duration value, but don't know how (didn't help):
view.tab_layout.animation = object: Animation() {}
view.tab_layout.animation.duration = 1000
view.tab_layout.layoutAnimation = LayoutAnimationController(object: Animation() {})
view.tab_layout.layoutAnimation.delay = 1000f
view.tab_layout.layoutAnimation.animation.duration = 1000