/* * Copyright (C) 2016 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.exoplayer2; import com.google.android.exoplayer2.audio.AudioCapabilities; import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AudioTrack; import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.metadata.MetadataRenderer; import com.google.android.exoplayer2.metadata.id3.Id3Decoder; import com.google.android.exoplayer2.metadata.id3.Id3Frame; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.Timeline; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.TextRenderer; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; import com.google.android.exoplayer2.video.VideoRendererEventListener; import android.annotation.TargetApi; import android.content.Context; import android.media.AudioManager; import android.media.MediaCodec; import android.media.PlaybackParams; 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 Renderer} 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 onAudioEnabled(DecoderCounters counters); void onAudioSessionId(int audioSessionId); void onAudioDecoderInitialized(String decoderName, long elapsedRealtimeMs, long initializationDurationMs); void onAudioFormatChanged(Format format); void onAudioDisabled(DecoderCounters counters); void onVideoEnabled(DecoderCounters counters); void onVideoDecoderInitialized(String decoderName, long elapsedRealtimeMs, long initializationDurationMs); void onVideoFormatChanged(Format format); void onVideoDisabled(DecoderCounters counters); 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 static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50; private final ExoPlayer player; private final Renderer[] renderers; private final ComponentListener componentListener; private final Handler mainHandler; private final int videoRendererCount; private final int audioRendererCount; private Format videoFormat; private Format audioFormat; private CaptionListener captionListener; private Id3MetadataListener id3MetadataListener; private VideoListener videoListener; private DebugListener debugListener; private DecoderCounters videoDecoderCounters; private DecoderCounters audioDecoderCounters; private int audioSessionId; /* package */ SimpleExoPlayer(Context context, TrackSelector trackSelector, LoadControl loadControl, DrmSessionManager drmSessionManager, boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) { mainHandler = new Handler(); componentListener = new ComponentListener(); // Build the renderers. ArrayList renderersList = new ArrayList<>(); if (preferExtensionDecoders) { buildExtensionRenderers(renderersList, allowedVideoJoiningTimeMs); buildRenderers(context, drmSessionManager, renderersList, allowedVideoJoiningTimeMs); } else { buildRenderers(context, drmSessionManager, renderersList, allowedVideoJoiningTimeMs); buildExtensionRenderers(renderersList, allowedVideoJoiningTimeMs); } renderers = renderersList.toArray(new Renderer[renderersList.size()]); // Obtain counts of video and audio renderers. int videoRendererCount = 0; int audioRendererCount = 0; for (Renderer renderer : renderers) { switch (renderer.getTrackType()) { case C.TRACK_TYPE_VIDEO: videoRendererCount++; break; case C.TRACK_TYPE_AUDIO: audioRendererCount++; break; } } this.videoRendererCount = videoRendererCount; this.audioRendererCount = audioRendererCount; this.audioSessionId = AudioTrack.SESSION_ID_NOT_SET; // Build the player and associated objects. player = new ExoPlayerImpl(renderers, trackSelector, loadControl); } /** * 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 Renderer#getTrackType() * @param index The index of the renderer. * @return One of the {@code 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) { ExoPlayerMessage[] messages = new ExoPlayerMessage[videoRendererCount]; int count = 0; for (Renderer renderer : renderers) { if (renderer.getTrackType() == C.TRACK_TYPE_VIDEO) { messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_SURFACE, surface); } } if (surface == null) { // Block to ensure that the surface is not accessed after the method returns. player.blockingSendMessages(messages); } else { player.sendMessages(messages); } } /** * Sets the audio volume, with 0 being silence and 1 being unity gain. * * @param volume The volume. */ public void setVolume(float volume) { ExoPlayerMessage[] messages = new ExoPlayerMessage[audioRendererCount]; int count = 0; for (Renderer renderer : renderers) { if (renderer.getTrackType() == C.TRACK_TYPE_AUDIO) { messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_VOLUME, volume); } } player.sendMessages(messages); } /** * Sets {@link PlaybackParams} governing audio playback. * * @param params The {@link PlaybackParams}. */ public void setPlaybackParams(PlaybackParams params) { ExoPlayerMessage[] messages = new ExoPlayerMessage[audioRendererCount]; int count = 0; for (Renderer renderer : renderers) { if (renderer.getTrackType() == C.TRACK_TYPE_AUDIO) { messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_PLAYBACK_PARAMS, params); } } player.sendMessages(messages); } /** * @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 audio session identifier. If not set {@code AudioTrack.SESSION_ID_NOT_SET} is * returned. */ public int getAudioSessionId() { return audioSessionId; } /** * @return The {@link DecoderCounters} for video, or null if there is no video component to the * current media. */ public DecoderCounters getVideoDecoderCounters() { return videoDecoderCounters; } /** * @return The {@link DecoderCounters} for audio, or null if there is no audio component to the * current media. */ public DecoderCounters getAudioDecoderCounters() { return audioDecoderCounters; } /** * 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 @Override public void addListener(EventListener listener) { player.addListener(listener); } @Override public void removeListener(EventListener listener) { player.removeListener(listener); } @Override public int getPlaybackState() { return player.getPlaybackState(); } @Override public void setMediaSource(MediaSource mediaSource) { player.setMediaSource(mediaSource); } @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 boolean isLoading() { return player.isLoading(); } @Override public void seekTo(long positionMs) { player.seekTo(positionMs); } @Override public void seekTo(int periodIndex, long positionMs) { player.seekTo(periodIndex, positionMs); } @Override public void stop() { player.stop(); } @Override public void release() { player.release(); } @Override public void sendMessages(ExoPlayerMessage... messages) { player.sendMessages(messages); } @Override public void blockingSendMessages(ExoPlayerMessage... messages) { player.blockingSendMessages(messages); } @Override public long getDuration() { return player.getDuration(); } @Override public long getCurrentPosition() { return player.getCurrentPosition(); } @Override public int getCurrentPeriodIndex() { return player.getCurrentPeriodIndex(); } @Override public Timeline getCurrentTimeline() { return player.getCurrentTimeline(); } @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, long allowedVideoJoiningTimeMs) { MediaCodecVideoRenderer videoRenderer = new MediaCodecVideoRenderer(context, MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, allowedVideoJoiningTimeMs, drmSessionManager, false, mainHandler, componentListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY); renderersList.add(videoRenderer); Renderer audioRenderer = new MediaCodecAudioRenderer(MediaCodecSelector.DEFAULT, drmSessionManager, true, mainHandler, componentListener, AudioCapabilities.getCapabilities(context), AudioManager.STREAM_MUSIC); renderersList.add(audioRenderer); Renderer textRenderer = new TextRenderer(componentListener, mainHandler.getLooper()); renderersList.add(textRenderer); MetadataRenderer> id3Renderer = new MetadataRenderer<>(componentListener, mainHandler.getLooper(), new Id3Decoder()); renderersList.add(id3Renderer); } private void buildExtensionRenderers(ArrayList renderersList, long allowedVideoJoiningTimeMs) { // 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.exoplayer2.ext.vp9.LibvpxVideoRenderer"); Constructor constructor = clazz.getConstructor(boolean.class, long.class, Handler.class, VideoRendererEventListener.class, int.class); renderersList.add((Renderer) constructor.newInstance(true, allowedVideoJoiningTimeMs, mainHandler, componentListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY)); Log.i(TAG, "Loaded LibvpxVideoRenderer."); } catch (ClassNotFoundException e) { // Expected if the app was built without the extension. } catch (Exception e) { throw new RuntimeException(e); } try { Class clazz = Class.forName("com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer"); Constructor constructor = clazz.getConstructor(Handler.class, AudioRendererEventListener.class); renderersList.add((Renderer) constructor.newInstance(mainHandler, componentListener)); Log.i(TAG, "Loaded LibopusAudioRenderer."); } catch (ClassNotFoundException e) { // Expected if the app was built without the extension. } catch (Exception e) { throw new RuntimeException(e); } try { Class clazz = Class.forName("com.google.android.exoplayer2.ext.flac.LibflacAudioRenderer"); Constructor constructor = clazz.getConstructor(Handler.class, AudioRendererEventListener.class); renderersList.add((Renderer) constructor.newInstance(mainHandler, componentListener)); Log.i(TAG, "Loaded LibflacAudioRenderer."); } catch (ClassNotFoundException e) { // Expected if the app was built without the extension. } catch (Exception e) { throw new RuntimeException(e); } try { Class clazz = Class.forName("com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioRenderer"); Constructor constructor = clazz.getConstructor(Handler.class, AudioRendererEventListener.class); renderersList.add((Renderer) constructor.newInstance(mainHandler, componentListener)); Log.i(TAG, "Loaded FfmpegAudioRenderer."); } catch (ClassNotFoundException e) { // Expected if the app was built without the extension. } catch (Exception e) { throw new RuntimeException(e); } } private final class ComponentListener implements VideoRendererEventListener, AudioRendererEventListener, TextRenderer.Output, MetadataRenderer.Output> { // VideoRendererEventListener implementation @Override public void onVideoEnabled(DecoderCounters counters) { videoDecoderCounters = counters; if (debugListener != null) { debugListener.onVideoEnabled(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; if (debugListener != null) { debugListener.onVideoFormatChanged(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(DecoderCounters counters) { if (debugListener != null) { debugListener.onVideoDisabled(counters); } videoFormat = null; videoDecoderCounters = null; } // AudioRendererEventListener implementation @Override public void onAudioEnabled(DecoderCounters counters) { audioDecoderCounters = counters; if (debugListener != null) { debugListener.onAudioEnabled(counters); } } @Override public void onAudioSessionId(int sessionId) { audioSessionId = sessionId; if (debugListener != null) { debugListener.onAudioSessionId(sessionId); } } @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; if (debugListener != null) { debugListener.onAudioFormatChanged(format); } } @Override public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) { if (debugListener != null) { debugListener.onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs); } } @Override public void onAudioDisabled(DecoderCounters counters) { if (debugListener != null) { debugListener.onAudioDisabled(counters); } audioFormat = null; audioDecoderCounters = null; audioSessionId = AudioTrack.SESSION_ID_NOT_SET; } // TextRendererOutput 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); } } } }