CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-androidx-media3--media3-exoplayer

ExoPlayer module that provides the core ExoPlayer implementation for local media playback on Android, supporting various media formats and streaming protocols

Pending
Overview
Eval results
Files

video-rendering.mddocs/

Video Rendering

Video rendering in ExoPlayer handles decoding, processing, and displaying video content. The video pipeline supports hardware acceleration, various surface outputs, and advanced features like spherical video and video effects.

MediaCodecVideoRenderer

The primary video renderer that uses MediaCodec for hardware-accelerated video decoding.

public class MediaCodecVideoRenderer extends MediaCodecRenderer {
    /**
     * Creates a MediaCodecVideoRenderer.
     * 
     * @param context The context
     * @param mediaCodecSelector The media codec selector
     * @param allowedJoiningTimeMs The maximum time to block for joining
     * @param eventHandler The event handler
     * @param eventListener The event listener
     * @param maxDroppedFramesToCountAsSkipped Max dropped frames to count as skipped
     */
    public MediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector, 
                                 long allowedJoiningTimeMs, @Nullable Handler eventHandler,
                                 @Nullable VideoRendererEventListener eventListener, 
                                 int maxDroppedFramesToCountAsSkipped);
    
    /**
     * Creates a MediaCodecVideoRenderer with a DrmSessionManager.
     * 
     * @param context The context
     * @param mediaCodecSelector The media codec selector
     * @param allowedJoiningTimeMs The maximum time to block for joining
     * @param drmSessionManager The DRM session manager
     * @param playClearSamplesWithoutKeys Whether to play clear samples without keys
     * @param eventHandler The event handler
     * @param eventListener The event listener
     * @param maxDroppedFramesToCountAsSkipped Max dropped frames to count as skipped
     */
    public MediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector,
                                 long allowedJoiningTimeMs, @Nullable DrmSessionManager drmSessionManager,
                                 boolean playClearSamplesWithoutKeys, @Nullable Handler eventHandler,
                                 @Nullable VideoRendererEventListener eventListener,
                                 int maxDroppedFramesToCountAsSkipped);
    
    /**
     * Sets a video frame metadata listener.
     * 
     * @param listener The listener
     */
    public void setVideoFrameMetadataListener(VideoFrameMetadataListener listener);
    
    /**
     * Clears the video frame metadata listener.
     */
    public void clearVideoFrameMetadataListener();
    
    /**
     * Sets the video surface.
     * 
     * @param surface The surface
     */
    public void setOutputSurface(@Nullable Surface surface);
    
    /**
     * Clears the video surface.
     */
    public void clearOutputSurface();
}

VideoSink Interface

Interface for consuming and rendering video frames.

public interface VideoSink {
    /**
     * Exception thrown by video sink operations.
     */
    final class VideoSinkException extends Exception {
        public final Format format;
        
        public VideoSinkException(Throwable cause, Format format);
    }
    
    /**
     * Called when the input stream changes.
     * 
     * @param inputType The input track type
     * @param format The input format
     */
    void onInputStreamChanged(@C.TrackType int inputType, Format format);
    
    /**
     * Registers an input stream.
     * 
     * @param inputType The input track type
     * @param effects The list of effects to apply
     */
    void registerInputStream(@C.TrackType int inputType, List<Effect> effects);
    
    /**
     * Sets the output surface information.
     * 
     * @param outputSurface The output surface
     * @param outputResolution The output resolution
     */
    void setOutputSurfaceInfo(@Nullable Surface outputSurface, @Nullable Size outputResolution);
    
    /**
     * Renders a video frame.
     * 
     * @param presentationTimeUs The presentation time in microseconds
     * @param releaseTimeNs The release time in nanoseconds
     * @return Whether the frame was rendered successfully
     * @throws VideoSinkException If rendering fails
     */
    boolean render(long presentationTimeUs, long releaseTimeNs) throws VideoSinkException;
    
    /**
     * Joins the video sink, blocking until all queued frames are rendered.
     */
    void join();
    
    /**
     * Releases the video sink.
     */
    void release();
}

VideoRendererEventListener

Listener for video renderer events.

public interface VideoRendererEventListener {
    /**
     * Called when the video renderer is enabled.
     * 
     * @param counters The decoder counters
     */
    default void onVideoEnabled(DecoderCounters counters) {}
    
    /**
     * Called when the video decoder is initialized.
     * 
     * @param decoderName The decoder name
     * @param initializedTimestampMs The initialization timestamp
     * @param initializationDurationMs The initialization duration
     */
    default void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs, long initializationDurationMs) {}
    
    /**
     * Called when the video input format changes.
     * 
     * @param format The new format
     * @param decoderReuseEvaluation The decoder reuse evaluation
     */
    default void onVideoInputFormatChanged(Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {}
    
    /**
     * Called when frames are dropped.
     * 
     * @param count The number of dropped frames
     * @param elapsed The elapsed time
     */
    default void onDroppedVideoFrames(int count, long elapsed) {}
    
    /**
     * Called when the video size changes.
     * 
     * @param videoSize The new video size
     */
    default void onVideoSizeChanged(VideoSize videoSize) {}
    
    /**
     * Called when the first frame is rendered.
     * 
     * @param output The output (Surface)
     * @param renderTimeMs The render time
     */
    default void onRenderedFirstFrame(Object output, long renderTimeMs) {}
    
    /**
     * Called when the video decoder is released.
     * 
     * @param decoderName The decoder name
     */
    default void onVideoDecoderReleased(String decoderName) {}
    
    /**
     * Called when the video renderer is disabled.
     * 
     * @param counters The decoder counters
     */
    default void onVideoDisabled(DecoderCounters counters) {}
    
    /**
     * Called when video frames are skipped.
     * 
     * @param count The number of skipped frames
     */
    default void onVideoFramesSkipped(int count) {}
    
    /**
     * Called when video codec error occurs.
     * 
     * @param videoCodecError The video codec error
     */
    default void onVideoCodecError(Exception videoCodecError) {}
}

VideoFrameMetadataListener

Listener for video frame metadata.

public interface VideoFrameMetadataListener {
    /**
     * Called when a video frame is about to be rendered.
     * 
     * @param presentationTimeUs The presentation time in microseconds
     * @param releaseTimeNs The release time in nanoseconds
     * @param format The video format
     * @param mediaFormat The media format
     */
    void onVideoFrameAboutToBeRendered(long presentationTimeUs, long releaseTimeNs, 
                                     Format format, @Nullable MediaFormat mediaFormat);
}

DecoderVideoRenderer

Base class for video renderers that use software decoders.

public abstract class DecoderVideoRenderer extends BaseRenderer {
    /**
     * Creates a DecoderVideoRenderer.
     * 
     * @param allowedJoiningTimeMs The maximum time to block for joining
     * @param eventHandler The event handler
     * @param eventListener The event listener
     * @param maxDroppedFramesToCountAsSkipped Max dropped frames to count as skipped
     */
    public DecoderVideoRenderer(long allowedJoiningTimeMs, @Nullable Handler eventHandler,
                              @Nullable VideoRendererEventListener eventListener,
                              int maxDroppedFramesToCountAsSkipped);
    
    /**
     * Sets the video surface.
     * 
     * @param surface The surface
     */
    public final void setOutputSurface(@Nullable Surface surface);
    
    /**
     * Clears the video surface.
     */
    public final void clearOutputSurface();
    
    /**
     * Creates a decoder for the given format.
     * 
     * @param format The format
     * @param cryptoConfig The crypto config
     * @return The decoder
     * @throws DecoderException If decoder creation fails
     */
    protected abstract VideoDecoder createDecoder(Format format, @Nullable CryptoConfig cryptoConfig) throws DecoderException;
    
    /**
     * Sets the decoder output mode.
     * 
     * @param outputMode The output mode
     */
    protected final void setDecoderOutputMode(@C.VideoOutputMode int outputMode);
}

Spherical Video Support

Support for 360-degree and spherical video content.

public final class SphericalGLSurfaceView extends GLSurfaceView {
    /**
     * Creates a SphericalGLSurfaceView.
     * 
     * @param context The context
     */
    public SphericalGLSurfaceView(Context context);
    
    /**
     * Creates a SphericalGLSurfaceView with attributes.
     * 
     * @param context The context
     * @param attrs The attributes
     */
    public SphericalGLSurfaceView(Context context, AttributeSet attrs);
    
    /**
     * Sets the video component.
     * 
     * @param player The ExoPlayer instance
     */
    public void setVideoComponent(@Nullable VideoComponent videoComponent);
    
    /**
     * Sets whether single tap to hide UI is enabled.
     * 
     * @param singleTapListener The tap listener, or null to disable
     */
    public void setSingleTapListener(@Nullable SingleTapListener singleTapListener);
    
    /**
     * Sets the camera motion listener.
     * 
     * @param listener The camera motion listener
     */
    public void setCameraMotionListener(@Nullable CameraMotionListener listener);
    
    /**
     * Interface for handling single tap events.
     */
    public interface SingleTapListener {
        /**
         * Called when the view is single tapped.
         * 
         * @param e The motion event
         * @return Whether the event was handled
         */
        boolean onSingleTapUp(MotionEvent e);
    }
}

public final class CameraMotionRenderer extends BaseRenderer {
    /**
     * Creates a CameraMotionRenderer.
     */
    public CameraMotionRenderer();
    
    /**
     * Sets the camera motion listener.
     * 
     * @param listener The listener
     */
    public void setCameraMotionListener(@Nullable CameraMotionListener listener);
}

public interface CameraMotionListener {
    /**
     * Called when camera motion data is available.
     * 
     * @param timeUs The timestamp in microseconds
     * @param rotation The rotation matrix
     */
    void onCameraMotion(long timeUs, float[] rotation);
    
    /**
     * Called when camera motion is reset.
     */
    void onCameraMotionReset();
}

public interface ProjectionDecoder {
    /**
     * Decodes projection data from metadata.
     * 
     * @param projectionData The projection data
     * @param stereoMode The stereo mode
     * @return The projection, or null if decoding fails
     */
    @Nullable Projection decode(byte[] projectionData, @C.StereoMode int stereoMode);
}

Usage Examples

Basic Video Renderer Setup

// Create video renderer with event listener
VideoRendererEventListener videoListener = new VideoRendererEventListener() {
    @Override
    public void onVideoSizeChanged(VideoSize videoSize) {
        Log.d(TAG, "Video size: " + videoSize.width + "x" + videoSize.height);
        // Update UI to match aspect ratio
        updateVideoAspectRatio(videoSize);
    }
    
    @Override
    public void onRenderedFirstFrame(Object output, long renderTimeMs) {
        Log.d(TAG, "First frame rendered");
        // Hide loading indicator
        hideLoadingIndicator();
    }
    
    @Override
    public void onDroppedVideoFrames(int count, long elapsed) {
        Log.w(TAG, "Dropped " + count + " video frames in " + elapsed + "ms");
    }
};

MediaCodecVideoRenderer videoRenderer = new MediaCodecVideoRenderer(
    context, 
    MediaCodecSelector.DEFAULT,
    5000, // 5 second joining time
    handler,
    videoListener,
    50 // max dropped frames to count as skipped
);

Surface Output Configuration

// Set surface for video output
SurfaceView surfaceView = findViewById(R.id.surface_view);
Surface surface = surfaceView.getHolder().getSurface();

// Configure player with surface
PlayerView playerView = findViewById(R.id.player_view);
playerView.setPlayer(player);

// Or set surface directly on renderer
videoRenderer.setOutputSurface(surface);

// Update surface when size changes
surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        videoRenderer.setOutputSurface(holder.getSurface());
    }
    
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        // Surface size changed
    }
    
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        videoRenderer.clearOutputSurface();
    }
});

TextureView Output

// Use TextureView for video output
TextureView textureView = findViewById(R.id.texture_view);

textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        videoRenderer.setOutputSurface(new Surface(surface));
    }
    
    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        // Handle size change
    }
    
    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        videoRenderer.clearOutputSurface();
        return true;
    }
    
    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
        // Frame updated
    }
});

Video Frame Metadata

VideoFrameMetadataListener frameMetadataListener = new VideoFrameMetadataListener() {
    @Override
    public void onVideoFrameAboutToBeRendered(long presentationTimeUs, long releaseTimeNs, 
                                            Format format, MediaFormat mediaFormat) {
        // Log frame timing
        Log.v(TAG, String.format("Frame at %dus, release at %dns", 
                                presentationTimeUs, releaseTimeNs));
        
        // Extract frame information
        if (mediaFormat != null) {
            int width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
            int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
            // Process frame metadata
        }
    }
};

// Set listener on renderer
((MediaCodecVideoRenderer) videoRenderer).setVideoFrameMetadataListener(frameMetadataListener);

Spherical Video Playback

// Setup spherical video view
SphericalGLSurfaceView sphericalView = findViewById(R.id.spherical_surface_view);

// Set player
sphericalView.setVideoComponent(player.getVideoComponent());

// Handle single tap to hide/show controls
sphericalView.setSingleTapListener(new SphericalGLSurfaceView.SingleTapListener() {
    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        toggleControlsVisibility();
        return true;
    }
});

// Set camera motion listener for gyroscope support
sphericalView.setCameraMotionListener(new CameraMotionListener() {
    @Override
    public void onCameraMotion(long timeUs, float[] rotation) {
        // Handle camera motion data
        updateViewOrientation(rotation);
    }
    
    @Override
    public void onCameraMotionReset() {
        // Reset view orientation
        resetViewOrientation();
    }
});

// Add camera motion renderer to player
CameraMotionRenderer cameraMotionRenderer = new CameraMotionRenderer();
cameraMotionRenderer.setCameraMotionListener(sphericalView.getCameraMotionListener());

// Use with custom renderers factory
RenderersFactory renderersFactory = new DefaultRenderersFactory(context) {
    @Override
    protected void buildCameraMotionRenderers(Context context, int extensionRendererMode,
                                            ArrayList<Renderer> out) {
        out.add(cameraMotionRenderer);
    }
};

ExoPlayer player = new ExoPlayer.Builder(context)
    .setRenderersFactory(renderersFactory)
    .build();

Video Effects

// Apply video effects (requires Media3 Effects library)
List<Effect> videoEffects = Arrays.asList(
    new ScaleAndRotateTransformation.Builder()
        .setScale(1.5f, 1.5f)  // Scale video
        .setRotationDegrees(90)  // Rotate 90 degrees
        .build(),
    new RgbFilter(/* red multiplier */ 1.2f, /* green */ 0.8f, /* blue */ 1.0f)
);

// Set effects on player
player.setVideoEffects(videoEffects);

Custom Video Renderer

public class CustomVideoRenderer extends DecoderVideoRenderer {
    private VideoDecoder decoder;
    
    public CustomVideoRenderer(long allowedJoiningTimeMs, Handler eventHandler,
                             VideoRendererEventListener eventListener,
                             int maxDroppedFramesToCountAsSkipped) {
        super(allowedJoiningTimeMs, eventHandler, eventListener, maxDroppedFramesToCountAsSkipped);
    }
    
    @Override
    protected VideoDecoder createDecoder(Format format, @Nullable CryptoConfig cryptoConfig) 
            throws DecoderException {
        // Create custom decoder based on format
        if (MimeTypes.VIDEO_H264.equals(format.sampleMimeType)) {
            return new CustomH264Decoder();
        } else if (MimeTypes.VIDEO_H265.equals(format.sampleMimeType)) {
            return new CustomH265Decoder();
        }
        throw new DecoderException("Unsupported format: " + format.sampleMimeType);
    }
    
    @Override
    public String getName() {
        return "CustomVideoRenderer";
    }
    
    @Override
    @Capabilities
    public int supportsFormat(Format format) {
        String mimeType = format.sampleMimeType;
        if (MimeTypes.VIDEO_H264.equals(mimeType) || MimeTypes.VIDEO_H265.equals(mimeType)) {
            return RendererCapabilities.create(C.FORMAT_HANDLED);
        }
        return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE);
    }
}

Video Performance Monitoring

public class VideoPerformanceMonitor implements VideoRendererEventListener {
    private long firstFrameTime = -1;
    private int totalDroppedFrames = 0;
    private long sessionStartTime;
    
    @Override
    public void onVideoEnabled(DecoderCounters counters) {
        sessionStartTime = SystemClock.elapsedRealtime();
        firstFrameTime = -1;
        totalDroppedFrames = 0;
        Log.d(TAG, "Video renderer enabled");
    }
    
    @Override
    public void onRenderedFirstFrame(Object output, long renderTimeMs) {
        firstFrameTime = SystemClock.elapsedRealtime();
        long timeToFirstFrame = firstFrameTime - sessionStartTime;
        Log.i(TAG, "Time to first frame: " + timeToFirstFrame + "ms");
        
        // Report to analytics
        reportTimeToFirstFrame(timeToFirstFrame);
    }
    
    @Override
    public void onDroppedVideoFrames(int count, long elapsed) {
        totalDroppedFrames += count;
        double dropRate = (double) count / (elapsed / 1000.0);
        
        Log.w(TAG, String.format("Dropped %d frames in %dms (%.2f fps)", 
                                count, elapsed, dropRate));
        
        // Report significant frame drops
        if (dropRate > 5.0) { // More than 5 fps drop rate
            reportFrameDrop(count, elapsed, dropRate);
        }
    }
    
    @Override
    public void onVideoDisabled(DecoderCounters counters) {
        long sessionDuration = SystemClock.elapsedRealtime() - sessionStartTime;
        
        Log.i(TAG, String.format("Video session ended - Duration: %dms, Total dropped frames: %d",
                                sessionDuration, totalDroppedFrames));
        
        reportSessionMetrics(sessionDuration, totalDroppedFrames, counters);
    }
    
    private void reportTimeToFirstFrame(long timeMs) {
        // Send to analytics backend
    }
    
    private void reportFrameDrop(int count, long elapsed, double dropRate) {
        // Send frame drop event to analytics
    }
    
    private void reportSessionMetrics(long duration, int droppedFrames, DecoderCounters counters) {
        // Send overall session metrics
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-androidx-media3--media3-exoplayer

docs

analytics.md

audio-rendering.md

core-player.md

drm-support.md

index.md

media-sources.md

offline-support.md

track-selection.md

video-rendering.md

tile.json