/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer; import com.google.android.exoplayer.audio.AudioCapabilities; import com.google.android.exoplayer.drm.DrmSessionManager; import com.google.android.exoplayer.metadata.MetadataTrackRenderer; import com.google.android.exoplayer.metadata.MetadataTrackRenderer.MetadataRenderer; import com.google.android.exoplayer.metadata.id3.Id3Frame; import com.google.android.exoplayer.metadata.id3.Id3Parser; import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.TextRenderer; import com.google.android.exoplayer.text.TextTrackRenderer; import com.google.android.exoplayer.upstream.BandwidthMeter; import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; import android.annotation.TargetApi; import android.content.Context; import android.media.AudioManager; import android.media.MediaCodec; import android.os.Handler; import android.util.Log; import android.view.Surface; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.List; /** * An {@link ExoPlayer} that uses default {@link TrackRenderer} components. *

* Instances of this class can be obtained from {@link ExoPlayerFactory}. */ @TargetApi(16) public final class SimpleExoPlayer implements ExoPlayer { /** * A listener for video rendering information. */ public interface VideoListener { void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio); void onDrawnToSurface(Surface surface); } /** * A listener for debugging information. */ public interface DebugListener { void onAudioDecoderInitialized(String decoderName, long elapsedRealtimeMs, long initializationDurationMs); void onVideoDecoderInitialized(String decoderName, long elapsedRealtimeMs, long initializationDurationMs); void onDroppedFrames(int count, long elapsed); void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs); } /** * A listener for receiving notifications of timed text. */ public interface CaptionListener { void onCues(List cues); } /** * A listener for receiving ID3 metadata parsed from the media stream. */ public interface Id3MetadataListener { void onId3Metadata(List id3Frames); } private static final String TAG = "SimpleExoPlayer"; private final ExoPlayer player; private final BandwidthMeter bandwidthMeter; private final TrackRenderer[] renderers; private final ComponentListener componentListener; private final Handler mainHandler; private Format videoFormat; private Format audioFormat; private CaptionListener captionListener; private Id3MetadataListener id3MetadataListener; private VideoListener videoListener; private DebugListener debugListener; private CodecCounters videoCodecCounters; private CodecCounters audioCodecCounters; /* package */ SimpleExoPlayer(Context context, TrackSelector trackSelector, DrmSessionManager drmSessionManager, boolean useExtensionDecoders, int minBufferMs, int minRebufferMs) { mainHandler = new Handler(); bandwidthMeter = new DefaultBandwidthMeter(); componentListener = new ComponentListener(); // Build the renderers. ArrayList renderersList = new ArrayList<>(); if (useExtensionDecoders) { buildExtensionRenderers(renderersList); } buildRenderers(context, drmSessionManager, renderersList); renderers = renderersList.toArray(new TrackRenderer[renderersList.size()]); // Build the player and associated objects. player = new ExoPlayerImpl(renderers, trackSelector, minBufferMs, minRebufferMs); } /** * Returns the number of renderers. * * @return The number of renderers. */ public int getRendererCount() { return renderers.length; } /** * Returns the track type that the renderer at a given index handles. * * @see {@link TrackRenderer#getTrackType()}. * @param index The index of the renderer. * @return One of the TRACK_TYPE_* constants defined in {@link C}. */ public int getRendererType(int index) { return renderers[index].getTrackType(); } /** * Sets the {@link Surface} onto which video will be rendered. * * @param surface The {@link Surface}. */ public void setSurface(Surface surface) { for (TrackRenderer renderer : renderers) { if (renderer.getTrackType() == C.TRACK_TYPE_VIDEO) { if (surface == null) { // Block to ensure that the surface is not accessed after the method returns. player.blockingSendMessage(renderer, C.MSG_SET_SURFACE, null); } else { player.sendMessage(renderer, C.MSG_SET_SURFACE, surface); } } } } /** * Sets the audio volume, with 0 being silence and 1 being unity gain. * * @param volume The volume. */ public void setVolume(float volume) { for (TrackRenderer renderer : renderers) { if (renderer.getTrackType() == C.TRACK_TYPE_AUDIO) { player.sendMessage(renderer, C.MSG_SET_VOLUME, volume); } } } /** * @return The {@link BandwidthMeter} being used by the player. */ public BandwidthMeter getBandwidthMeter() { return bandwidthMeter; } /** * @return The video format currently being played, or null if there is no video component to the * current media. */ public Format getVideoFormat() { return videoFormat; } /** * @return The audio format currently being played, or null if there is no audio component to the * current media. */ public Format getAudioFormat() { return audioFormat; } /** * @return The {@link CodecCounters} for video, or null if there is no video component to the * current media. */ public CodecCounters getVideoCodecCounters() { return videoCodecCounters; } /** * @return The {@link CodecCounters} for audio, or null if there is no audio component to the * current media. */ public CodecCounters getAudioCodecCounters() { return audioCodecCounters; } /** * Sets a listener to receive video events. * * @param listener The listener. */ public void setVideoListener(VideoListener listener) { videoListener = listener; } /** * Sets a listener to receive debug events. * * @param listener The listener. */ public void setDebugListener(DebugListener listener) { debugListener = listener; } /** * Sets a listener to receive caption events. * * @param listener The listener. */ public void setCaptionListener(CaptionListener listener) { captionListener = listener; } /** * Sets a listener to receive metadata events. * * @param listener The listener. */ public void setMetadataListener(Id3MetadataListener listener) { id3MetadataListener = listener; } // ExoPlayer implementation public void addListener(EventListener listener) { player.addListener(listener); } public void removeListener(EventListener listener) { player.removeListener(listener); } @Override public int getPlaybackState() { return player.getPlaybackState(); } @Override public void setSource(SampleSource source) { player.setSource(source); } @Override public void setPlayWhenReady(boolean playWhenReady) { player.setPlayWhenReady(playWhenReady); } @Override public boolean getPlayWhenReady() { return player.getPlayWhenReady(); } @Override public boolean isPlayWhenReadyCommitted() { return player.isPlayWhenReadyCommitted(); } @Override public void seekTo(long positionMs) { player.seekTo(positionMs); } @Override public void stop() { player.stop(); } @Override public void release() { player.release(); } @Override public void sendMessage(ExoPlayerComponent target, int messageType, Object message) { player.sendMessage(target, messageType, message); } @Override public void blockingSendMessage(ExoPlayerComponent target, int messageType, Object message) { player.blockingSendMessage(target, messageType, message); } @Override public long getDuration() { return player.getDuration(); } @Override public long getCurrentPosition() { return player.getCurrentPosition(); } @Override public long getBufferedPosition() { return player.getBufferedPosition(); } @Override public int getBufferedPercentage() { return player.getBufferedPercentage(); } // Internal methods. private void buildRenderers(Context context, DrmSessionManager drmSessionManager, ArrayList renderersList) { MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, drmSessionManager, false, mainHandler, componentListener, 50); renderersList.add(videoRenderer); TrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(MediaCodecSelector.DEFAULT, drmSessionManager, true, mainHandler, componentListener, AudioCapabilities.getCapabilities(context), AudioManager.STREAM_MUSIC); renderersList.add(audioRenderer); TrackRenderer textRenderer = new TextTrackRenderer(componentListener, mainHandler.getLooper()); renderersList.add(textRenderer); MetadataTrackRenderer> id3Renderer = new MetadataTrackRenderer<>(new Id3Parser(), componentListener, mainHandler.getLooper()); renderersList.add(id3Renderer); } private void buildExtensionRenderers(ArrayList renderersList) { // Load extension renderers using reflection so that demo app doesn't depend on them. // Class.forName() appears for each renderer so that automated tools like proguard // can detect the use of reflection (see http://proguard.sourceforge.net/FAQ.html#forname). try { Class clazz = Class.forName("com.google.android.exoplayer.ext.vp9.LibvpxVideoTrackRenderer"); Constructor constructor = clazz.getConstructor(boolean.class, Handler.class, VideoTrackRendererEventListener.class, int.class); renderersList.add((TrackRenderer) constructor.newInstance(true, mainHandler, componentListener, 50)); Log.i(TAG, "Loaded LibvpxVideoTrackRenderer."); } catch (Exception e) { // Expected if the app was built without the extension. } try { Class clazz = Class.forName("com.google.android.exoplayer.ext.opus.LibopusAudioTrackRenderer"); Constructor constructor = clazz.getConstructor(Handler.class, AudioTrackRendererEventListener.class); renderersList.add((TrackRenderer) constructor.newInstance(mainHandler, componentListener)); Log.i(TAG, "Loaded LibopusAudioTrackRenderer."); } catch (Exception e) { // Expected if the app was built without the extension. } try { Class clazz = Class.forName("com.google.android.exoplayer.ext.flac.LibflacAudioTrackRenderer"); Constructor constructor = clazz.getConstructor(Handler.class, AudioTrackRendererEventListener.class); renderersList.add((TrackRenderer) constructor.newInstance(mainHandler, componentListener)); Log.i(TAG, "Loaded LibflacAudioTrackRenderer."); } catch (Exception e) { // Expected if the app was built without the extension. } try { Class clazz = Class.forName("com.google.android.exoplayer.ext.ffmpeg.FfmpegAudioTrackRenderer"); Constructor constructor = clazz.getConstructor(Handler.class, AudioTrackRendererEventListener.class); renderersList.add((TrackRenderer) constructor.newInstance(mainHandler, componentListener)); Log.i(TAG, "Loaded FfmpegAudioTrackRenderer."); } catch (Exception e) { // Expected if the app was built without the extension. } } private final class ComponentListener implements VideoTrackRendererEventListener, AudioTrackRendererEventListener, TextRenderer, MetadataRenderer> { // VideoTrackRendererEventListener implementation @Override public void onVideoEnabled(CodecCounters counters) { videoCodecCounters = counters; } @Override public void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs, long initializationDurationMs) { if (debugListener != null) { debugListener.onVideoDecoderInitialized(decoderName, initializedTimestampMs, initializationDurationMs); } } @Override public void onVideoInputFormatChanged(Format format) { videoFormat = format; } @Override public void onDroppedFrames(int count, long elapsed) { if (debugListener != null) { debugListener.onDroppedFrames(count, elapsed); } } @Override public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { if (videoListener != null) { videoListener.onVideoSizeChanged(width, height, unappliedRotationDegrees, pixelWidthHeightRatio); } } @Override public void onDrawnToSurface(Surface surface) { if (videoListener != null) { videoListener.onDrawnToSurface(surface); } } @Override public void onVideoDisabled() { videoFormat = null; videoCodecCounters = null; } // AudioTrackRendererEventListener implementation @Override public void onAudioEnabled(CodecCounters counters) { audioCodecCounters = counters; } @Override public void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs, long initializationDurationMs) { if (debugListener != null) { debugListener.onAudioDecoderInitialized(decoderName, initializedTimestampMs, initializationDurationMs); } } @Override public void onAudioInputFormatChanged(Format format) { audioFormat = format; } @Override public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) { if (debugListener != null) { debugListener.onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs); } } @Override public void onAudioDisabled() { audioFormat = null; audioCodecCounters = null; } // TextRenderer implementation @Override public void onCues(List cues) { if (captionListener != null) { captionListener.onCues(cues); } } // MetadataRenderer implementation @Override public void onMetadata(List id3Frames) { if (id3MetadataListener != null) { id3MetadataListener.onId3Metadata(id3Frames); } } } }