mirror of
https://github.com/samsonjs/media.git
synced 2026-03-26 09:35:47 +00:00
- 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
508 lines
15 KiB
Java
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);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|