Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Playback Stops After Scrolling through a Few Videos #244

Closed
1 task done
kiptoomm opened this issue Jun 15, 2016 · 10 comments
Closed
1 task done

Playback Stops After Scrolling through a Few Videos #244

kiptoomm opened this issue Jun 15, 2016 · 10 comments
Labels

Comments

@kiptoomm
Copy link

kiptoomm commented Jun 15, 2016

  • I have verified there are no duplicate active or recent bugs, questions, or requests
Include the following:
  • ExoMedia version: 2.5.6
  • Device OS version: 5.1.1 and 6.0
  • Devide Manufacturer: Android TV: various manufactures e.g Google Nexus, Razer Forge
  • Device Name: Android TV emulator, Nexus Player, Razer Forge TV
Reproduction Steps
Expected Result
  • Video playback begins soon after a card in the top row is selected after any number of scrolls through the carousel.
Actual Result
  • After 5th or 6th video, the video view shows a black screen and the "sending message to a Handler on a dead thread" warning is output. Often the reported line is EMVideoView.setVideoUri() and stopPlayback()
More info

I have read about some possibly related issues on the ExoPlayer project, like:
google/ExoPlayer#426 and google/ExoPlayer#590 but the ExoPlayer devs seem to insist that it's a harmless warning. Could it possibly related to memory problems? I'm stumped because from Memory Monitor usage seems pretty normal and the device doesn't throw any OOMs

@kiptoomm
Copy link
Author

Sample stacktrace:
W/MessageQueue: Handler (android.os.Handler) {5a4e1a0} sending message to a Handler on a dead thread java.lang.IllegalStateException: Handler (android.os.Handler) {5a4e1a0} sending message to a Handler on a dead thread at android.os.MessageQueue.enqueueMessage(MessageQueue.java:543) at android.os.Handler.enqueueMessage(Handler.java:631) at android.os.Handler.sendMessageAtTime(Handler.java:600) at android.os.Handler.sendMessageDelayed(Handler.java:570) at android.os.Handler.sendMessage(Handler.java:507) at android.os.Message.sendToTarget(Message.java:416) at com.google.android.exoplayer.ExoPlayerImplInternal.seekTo(ExoPlayerImplInternal.java:146) at com.google.android.exoplayer.ExoPlayerImpl.seekTo(ExoPlayerImpl.java:146) at com.devbrackets.android.exomedia.exoplayer.EMExoPlayer.seekTo(EMExoPlayer.java:260) at com.devbrackets.android.exomedia.EMVideoView.setVideoURI(EMVideoView.java:834) at com.devbrackets.android.exomedia.EMVideoView.setVideoURI(EMVideoView.java:810) at com.devbrackets.android.exomedia.EMVideoView.setVideoURI(EMVideoView.java:795) at net.kiptoo.android.tv.demo.VideoCardView.createVideoView(VideoCardView.java:45) at net.kiptoo.android.tv.demo.VideoCardPresenter$1.setSelected(VideoCardPresenter.java:31) at android.view.ViewGroup.dispatchSetSelected(ViewGroup.java:3705) at android.view.View.setSelected(View.java:17966) at android.support.v17.leanback.widget.FocusHighlightHelper$BrowseItemFocusHighlight.onItemFocused(FocusHighlightHelper.java:161) at android.support.v17.leanback.widget.ItemBridgeAdapter$OnFocusChangeListener.onFocusChange(ItemBridgeAdapter.java:76) at android.view.View.onFocusChanged(View.java:5717) at android.view.View.handleFocusGainInternal(View.java:5472) at android.view.ViewGroup.handleFocusGainInternal(ViewGroup.java:714) at android.view.View.requestFocusNoSearch(View.java:8470) at android.view.View.requestFocus(View.java:8449) at android.view.ViewGroup.requestFocus(ViewGroup.java:2747) at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:4203) at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4089) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3642) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3695) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3661) at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3787) at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3669) at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:3844) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3642) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3695) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3661) at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3669) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3642) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3695) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3661) at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3820) at android.view.ViewRootImpl$ImeInputStage.onFinishedInputEvent(ViewRootImpl.java:3981) at android.view.inputmethod.InputMethodManager$PendingEvent.run(InputMethodManager.java:2253) at android.view.inputmethod.InputMethodManager.invokeFinishedInputEventCallback(InputMethodManager.java:1874) at android.view.inputmethod.InputMethodManager.finishedInputEvent(InputMethodManager.java:1865) at android.view.inputmethod.InputMethodManager$ImeInputEventSender.onInputEventFinished(InputMethodManager.java:2230) at android.view.InputEventSender.dispatchInputEventFinished(InputEventSender.java:141) at android.os.MessageQueue.nativePollOnce(Native Method) at android.os.MessageQueue.next(MessageQueue.java:323) at android.os.Looper.loop(Looper.java:135) at android.app.ActivityThread.main(ActivityThread.java:5417) at java.lang.reflect.Method.invoke(Native Me

@brianwernick
Copy link
Owner

The IllegalStateException occurs when the ExoPlayer is being destroyed and after looking in to it about a year ago I came to the same conclusion the ExoPlayer devs have, it is inconsequential and doesn't represent another issue (It would be quite a bit of work to change the architecture around the issue so it hasn't been done because it is low priority).

You should also look at #214 and #128 as they might shed some light on your issue

@brianwernick
Copy link
Owner

In short you need to specify mVideoView.setReleaseOnDetachFromWindow(false); and release all when the presenter is detached (#128 should have the description). You also need to not null out the mVideoView in the card views, these will be cleaned up when the holders are destroyed.

As an extra; the EMVideoView has a preview image view that you can use instead of a custom one that will automatically hide/show when appropriate

@kiptoomm
Copy link
Author

Thanks for your feedback Brian. Great to know about the preview image, will use it. Earlier I tried to perform a manual release with mVideoView.release() in VideoCardView's onDetachFromWindow() but it didn't change the behavior. I made sure onDetach() was being called.

On #128 I suppose this is the relevant description you're talking about:

calling .release() when populating new views won't perform the cleanup when the activity (or fragment) is destroyed; so you will leak contexts which may cause the issue.

In this case I would have the RecyclerView.Adapter keep a list of all the EMVideoViews and in the onDetachFromWindow() method call .release() on all of them. Just keep in mind that the onDetachFromWindow() isn't called unless you call recyclerView.setAdapter(null) in the Activities or Fragments onDestroy() method.

In the context of creating Rows in Leanback, in general I haven't seen where RecyclerView.Adapter is explicitly referenced. How/where would I obtain the adapter? In other words, I don't quite see where to interact with the adapter since Leanback uses MVP. Also, I'm a bit confused by your suggestion to "keep a list of all the EMVideoViews and in the onDetachFromWindow() method call .release() on all of them" >> Isn't each EMVideoView instantiated and destroyed independently when the presenter is active/selected and detached?

@brianwernick
Copy link
Owner

Normally that Video View will clean itself up, which is why you are facing issues when a card view is reused. Calling mVideoView.setReleaseOnDetachFromWindow(false); essentially says "I will take control of manually releasing the video views" and is why the list is now needed.

The Leanback framework doesn't use the RecyclerView, nor did they do a good job with setting up MVP (but that is another discussion). In your case you need to do two things

  1. In VideoCardView#createVideoView you need to call mVideoView.setReleaseOnDetachFromWindow(false);
  2. In VideoCardPresenter#onUnbindViewHolder you need to call ((VideoCardView) viewHolder.view).stopPlayback();

NOTE: I'm assuming that the onUnbindViewHolder gets called when the Presenter is being destroyed, if not then you will need to figure out a way to handle that interaction. If onUnbindViewHolder works then you won't need to store a list of Video Views.

@kiptoomm
Copy link
Author

My intention is to only create the video view when its card is selected in VideoCardPresenter#onCreateViewHolder and calling VideoCardView.stopPlayback() immediately when we select away from the card for memory performance reasons. I do not want multiple instances of VideoView at a given time, only the currently selected card. As such, I don't rely on onUnbindViewHolder() being called (in fact, I cannot predict when it gets called, it's rather erratic). I have pushed the suggested changes just a while ago but unfortunately OOMs are back up again

@brianwernick
Copy link
Owner

Yeah, I typed that wrong.

You should still be calling stopPlayback() when the selection is lost (though without nulling the mVideoView as it will get reused). Instead for 2 you should be calling ((VideoCardView) viewHolder.view).getVideoView().release();

@kiptoomm
Copy link
Author

I see. Unfortunately, that doesn't seem to fix it either. Not surprising though, because release() would have been eventually called if we had let it be automatically done right?

BTW, > without nulling the mVideoView as it will get reused
as you may notice in createVideoView, I recreate a new VideoView each time so it probably won't get reused. I thought this was necessary to do as having multiple instances of the view raises memory usage, but if you know a better way to manage it please let me know.

Sorry this is taking long :( but thanks for your patience

@brianwernick
Copy link
Owner

Your createVideoView doesn't actually create the EMVideoView it is retrieving the reference to it (the parent view holds the reference to it). If you actually want to dynamically add and remove the EMVideoView to your VideoCardView you will need to perform the addition and removal via code (not adding it to the layout file). Now because the video view is not really being removed, the automatic calling of release(); is what is causing the problem (thus turning it off and manually calling it when appropriate). If you are truly dynamically adding/removing the view then you can leave the automatic cleanup as it is.

@kiptoomm
Copy link
Author

Dynamic creation and removal seems to fix it, playback always works now!

Now I have to figure out how apply layout attributes in code and also try to reduce the [expected] ever increasing memory usage. Thanks Brian!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants