66
77import android .content .Context ;
88import android .os .Build ;
9+ import android .view .Surface ;
910import android .view .SurfaceHolder ;
1011import android .view .SurfaceView ;
1112import android .view .View ;
@@ -30,23 +31,14 @@ public final class PlatformVideoView implements PlatformView {
3031 */
3132 @ OptIn (markerClass = UnstableApi .class )
3233 public PlatformVideoView (@ NonNull Context context , @ NonNull ExoPlayer exoPlayer ) {
33- surfaceView = new SurfaceView (context );
34-
35- if (Build .VERSION .SDK_INT == Build .VERSION_CODES .P ) {
36- // Workaround for rendering issues on Android 9 (API 28).
37- // On Android 9, using setVideoSurfaceView seems to lead to issues where the first frame is
38- // not displayed if the video is paused initially.
39- // To ensure the first frame is visible, the surface is directly set using holder.getSurface()
40- // when the surface is created, and ExoPlayer seeks to a position to force rendering of the
41- // first frame.
42- setupSurfaceWithCallback (exoPlayer );
43- } else {
44- if (Build .VERSION .SDK_INT <= Build .VERSION_CODES .N_MR1 ) {
45- // Avoid blank space instead of a video on Android versions below 8 by adjusting video's
46- // z-layer within the Android view hierarchy:
47- surfaceView .setZOrderMediaOverlay (true );
48- }
49- exoPlayer .setVideoSurfaceView (surfaceView );
34+ this .surfaceView = new VideoSurfaceView (context , exoPlayer );
35+
36+ setupSurfaceWithCallback (exoPlayer );
37+
38+ if (Build .VERSION .SDK_INT <= Build .VERSION_CODES .N_MR1 ) {
39+ // Avoid blank space instead of a video on Android versions below 8 by adjusting video's
40+ // z-layer within the Android view hierarchy:
41+ surfaceView .setZOrderMediaOverlay (true );
5042 }
5143 }
5244
@@ -57,24 +49,63 @@ private void setupSurfaceWithCallback(@NonNull ExoPlayer exoPlayer) {
5749 new SurfaceHolder .Callback () {
5850 @ Override
5951 public void surfaceCreated (@ NonNull SurfaceHolder holder ) {
60- exoPlayer .setVideoSurface (holder .getSurface ());
61- // Force first frame rendering:
62- exoPlayer .seekTo (1 );
52+ bindPlayerToSurface (exoPlayer , holder .getSurface ());
6353 }
6454
6555 @ Override
6656 public void surfaceChanged (
67- @ NonNull SurfaceHolder holder , int format , int width , int height ) {
68- // No implementation needed.
69- }
57+ @ NonNull SurfaceHolder holder , int format , int width , int height ) {}
7058
7159 @ Override
7260 public void surfaceDestroyed (@ NonNull SurfaceHolder holder ) {
73- exoPlayer .setVideoSurface (null );
61+ // Use clearVideoSurface to ensure we only unbind if this surface is currently active.
62+ exoPlayer .clearVideoSurface (holder .getSurface ());
7463 }
7564 });
7665 }
7766
67+ /**
68+ * Binds the ExoPlayer to the provided surface and performs a seek operation to ensure the video
69+ * frame is rendered. Includes special handling for Android 9.
70+ */
71+ private static void bindPlayerToSurface (@ NonNull ExoPlayer exoPlayer , @ NonNull Surface surface ) {
72+ if (surface .isValid ()) {
73+ exoPlayer .setVideoSurface (surface );
74+
75+ // Workaround for a rendering bug on Android 9 (API 28) where the decoder does not
76+ // flush its output buffer when a new surface is attached while the player is paused,
77+ // resulting in a black frame. A seek forces the codec to produce and display a frame.
78+ long current = exoPlayer .getCurrentPosition ();
79+ if (current == 0 && Build .VERSION .SDK_INT == Build .VERSION_CODES .P ) {
80+ exoPlayer .seekTo (1 );
81+ } else {
82+ exoPlayer .seekTo (current );
83+ }
84+ }
85+ }
86+
87+ /**
88+ * A custom SurfaceView that handles visibility changes to ensure video rendering is restored
89+ * during route transitions (e.g., returning from a full-screen view).
90+ */
91+ private static class VideoSurfaceView extends SurfaceView {
92+ private final ExoPlayer exoPlayer ;
93+
94+ public VideoSurfaceView (Context context , ExoPlayer exoPlayer ) {
95+ super (context );
96+ this .exoPlayer = exoPlayer ;
97+ }
98+
99+ @ Override
100+ protected void onVisibilityChanged (@ NonNull View changedView , int visibility ) {
101+ super .onVisibilityChanged (changedView , visibility );
102+ // When the view becomes visible again, ensure the ExoPlayer is re-attached to the surface.
103+ if (visibility == View .VISIBLE ) {
104+ bindPlayerToSurface (exoPlayer , getHolder ().getSurface ());
105+ }
106+ }
107+ }
108+
78109 /**
79110 * Returns the view associated with this PlatformView.
80111 *
0 commit comments