fix: crash when opening a new video on media tunneling devices (ex: FireTV) - #85
Open
lucarosellini wants to merge 1 commit into
Open
Conversation
SurfaceHolderCallback handed the player a PlaceholderSurface of its own on surfaceDestroyed. MediaCodecVideoRenderer refuses to swap a surface onto a tunneled codec and releases it instead, so ours made the swap fail and the detach timed out. Pass null and let the renderer decide.
Owner
|
Thanks, LGTM. I'll merge this later since we'll release 5.2.5 tomorrow and it's better not introduce new modifications at this time. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On a device using media tunneling, playing a video and then opening another one
crashes the app. Reproduced 100% of the time on a Fire TV Stick 4K (
AFTMM/mantis, MediaTek MT8695, Fire OS 6 / API 25) playing tunneled VP9. Phones donot reproduce it, because they rarely get a tunneled decoder for these streams.
Root cause
SurfaceHolderCallback.surfaceDestroyed()created aPlaceholderSurfaceof itsown and handed it to the player. That dates from an ExoPlayer 2.x workaround
(google/ExoPlayer#2703, 2017), written when the player did not manage placeholder
surfaces itself.
The player does that itself now, and it checks first whether swapping the
surface is even possible:
The
!tunnelingterm is the one that matters here.MediaCodec.setOutputSurface()cannot change the surface of a tunneled codec, and ACodec rejects the attempt.
When
setOutput()finds no usable surface it takes the other branchand calls
releaseCodec(); maybeInitCodecOrBypass();instead, which works.Passing our own placeholder made that check unreachable.
displaySurfacewasnever null, so the renderer always took the swap path:
The exception escapes
handleMessage, so thePlayerMessageis never markeddelivered.
ExoPlayerImpl.setVideoOutputInternal()blocks the main thread inblockUntilDelivered()until the detach timeout expires (two seconds), thenraises the timeout as a playback error:
The error is delivered synchronously, which puts
onPlayerErrorinside theViewGroup.removeView()whose detach pass calledsurfaceDestroyedin the firstplace.
VideoDetailFragment.onPlayerError()then callshideMainPlayerOnLoadingNewStream()again, re-enteringremoveViewFromParent()while the outer removal is still running.
ViewGroup.removeView()resolves the child index up front and only writes through it later inremoveFromArray(), so the nested removal shrinks the array underneath it andthe outer call dereferences an emptied slot.
The crash comes from that nested removal. On API 25 it can also surface one frame
earlier, in
SurfaceView.updateWindow(), which dereferences itsIWindowSessionwith no null check before API 26 rewrote the class.Fix
Pass
nullso the renderer makes the decision itself. It substitutes its ownplaceholder when that is safe, and releases the codec when it is not.
Behaviour is unchanged for non-tunneled codecs: the renderer allocates the same
PlaceholderSurfacewe used to allocate here. The rest of the diff is the nowdead plumbing coming out (the field, its lazy init,
release()and its callsite, two imports), plus a Javadoc line that no longer described the code.
Testing
Fire TV Stick 4K, API 25, tunneled VP9. Four consecutive videos, four clean
codec cycles:
Tunneling stays enabled, so no hardware A/V sync is lost.
cannot change tunneled surface,Detaching surface timed out,onPlayerError,NullPointerException,IllegalStateExceptionandFATALare all absent fromthe log. The two second main thread stall at each transition is gone as well:
before the fix it landed squarely on the video switch, and now the only
Choreographer: Skipped framesevents are at stream start.Phone (arm64, non-tunneled) checked for regressions in the paths the original
workaround existed for: rotation during playback, background and return, and
fullscreen toggle. No change in behaviour, and no
Unrecoverable player error occurred.Notes
This is not specific to Fire TV. Any device that ends up with a tunneled decoder
is affected, which on Android TV is common. Users hitting it today can work
around it with the existing "Disable media tunneling" preference, at the cost of
tunneled playback.