media/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java
olly f758082d40 Big (and hopefully near-final) rename.
1. Rename "extensions" package to "decoder". It's used by both
   text and (should be used by) metadata, so it's no longer just
   for extensions.
2. Move Buffer objects move into the decoder package.
3. Rename SubtitleParser and MetadataParser to SubtitleDecoder
   and MetadataDecoder respectively, since they extend Decoder.
   Ditto for all subclasses.
4. Subtitle and Metadata decoders now throw their own exception
   types rather than ParserException.
5. Move MediaCodec classes into a mediacodec package, with the
   exception of the concrete audio and video renderers.
6. Create an audio package to hold the two audio renderer classes
   plus related util classes.
7. Create a video package to hold the one video renderer class
   plus related util classes.

After this change the following nice properties hold:

1. Want a video renderer? Look in the video package. Ditto for
   audio, text and metadata.
2. All TrackRenderer implementations use a decoder of some kind
   to decode buffers received from the source, so we have
   consistent terminology there.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=127326805
2016-07-15 18:14:55 +01:00

619 lines
19 KiB
Java

/*
* 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.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.
* <p>
* 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<Cue> cues);
}
/**
* A listener for receiving ID3 metadata parsed from the media stream.
*/
public interface Id3MetadataListener {
void onId3Metadata(List<Id3Frame> 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<Renderer> 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 long getBufferedPosition() {
return player.getBufferedPosition();
}
@Override
public int getBufferedPercentage() {
return player.getBufferedPercentage();
}
// Internal methods.
private void buildRenderers(Context context, DrmSessionManager drmSessionManager,
ArrayList<Renderer> 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<List<Id3Frame>> id3Renderer = new MetadataRenderer<>(componentListener,
mainHandler.getLooper(), new Id3Decoder());
renderersList.add(id3Renderer);
}
private void buildExtensionRenderers(ArrayList<Renderer> renderersList,
long allowedVideoJoiningTimeMs) {
// Load extension renderers using reflection so that demo app doesn't depend on them.
// Class.forName(<class name>) 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<List<Id3Frame>> {
// 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<Cue> cues) {
if (captionListener != null) {
captionListener.onCues(cues);
}
}
// MetadataRenderer implementation
@Override
public void onMetadata(List<Id3Frame> id3Frames) {
if (id3MetadataListener != null) {
id3MetadataListener.onId3Metadata(id3Frames);
}
}
}
}