media/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java
olly 35d7dad047 Clean up SimpleExoPlayer.
- Simplify setSurface by always blocking when the surface
  is being cleared.
- Add analogous setVolume API.
- Support setting of playback params for extension audio
  decoders. We can probably use this to add a setPlaybackRate
  API to SimpleExoPlayer for API level 23 and above only,
  but we'd probably want to make sure it works properly when
  there's no audio track too, so some thought will be
  required.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=123204581
2016-06-15 19:41:34 +01:00

508 lines
15 KiB
Java

/*
* 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.
* <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 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<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 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<TrackRenderer> 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<TrackRenderer> 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<List<Id3Frame>> id3Renderer = new MetadataTrackRenderer<>(new Id3Parser(),
componentListener, mainHandler.getLooper());
renderersList.add(id3Renderer);
}
private void buildExtensionRenderers(ArrayList<TrackRenderer> renderersList) {
// 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.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<List<Id3Frame>> {
// 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<Cue> cues) {
if (captionListener != null) {
captionListener.onCues(cues);
}
}
// MetadataRenderer implementation
@Override
public void onMetadata(List<Id3Frame> id3Frames) {
if (id3MetadataListener != null) {
id3MetadataListener.onId3Metadata(id3Frames);
}
}
}
}