Introduce MetadataDecoderFactory

This is analogous to what we do for text/subtitles, and
adds support for playlists where the type of metadata
changes from one playlist item to the next.

Issue: #2176

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=143948307
This commit is contained in:
olly 2017-01-09 04:15:00 -08:00 committed by Oliver Woodman
parent ae2e84df35
commit 59ab0fa9f1
4 changed files with 127 additions and 19 deletions

View File

@ -278,7 +278,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
player.addListener(eventLogger); player.addListener(eventLogger);
player.setAudioDebugListener(eventLogger); player.setAudioDebugListener(eventLogger);
player.setVideoDebugListener(eventLogger); player.setVideoDebugListener(eventLogger);
player.setId3Output(eventLogger); player.setMetadataOutput(eventLogger);
simpleExoPlayerView.setPlayer(player); simpleExoPlayerView.setPlayer(player);
if (isTimelineStatic) { if (isTimelineStatic) {

View File

@ -36,7 +36,6 @@ import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataRenderer; import com.google.android.exoplayer2.metadata.MetadataRenderer;
import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Cue;
@ -448,15 +447,6 @@ public class SimpleExoPlayer implements ExoPlayer {
textOutput = output; textOutput = output;
} }
/**
* @deprecated Use {@link #setMetadataOutput(MetadataRenderer.Output)} instead.
* @param output The output.
*/
@Deprecated
public void setId3Output(MetadataRenderer.Output output) {
setMetadataOutput(output);
}
/** /**
* Sets a listener to receive metadata events. * Sets a listener to receive metadata events.
* *
@ -771,7 +761,7 @@ public class SimpleExoPlayer implements ExoPlayer {
protected void buildMetadataRenderers(Context context, Handler mainHandler, protected void buildMetadataRenderers(Context context, Handler mainHandler,
@ExtensionRendererMode int extensionRendererMode, MetadataRenderer.Output output, @ExtensionRendererMode int extensionRendererMode, MetadataRenderer.Output output,
ArrayList<Renderer> out) { ArrayList<Renderer> out) {
out.add(new MetadataRenderer(output, mainHandler.getLooper(), new Id3Decoder())); out.add(new MetadataRenderer(output, mainHandler.getLooper()));
} }
/** /**

View File

@ -0,0 +1,99 @@
/*
* Copyright (C) 2017 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.metadata;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.metadata.emsg.EventMessageDecoder;
import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
import com.google.android.exoplayer2.metadata.scte35.SpliceInfoDecoder;
import com.google.android.exoplayer2.util.MimeTypes;
/**
* A factory for {@link MetadataDecoder} instances.
*/
public interface MetadataDecoderFactory {
/**
* Returns whether the factory is able to instantiate a {@link MetadataDecoder} for the given
* {@link Format}.
*
* @param format The {@link Format}.
* @return Whether the factory can instantiate a suitable {@link MetadataDecoder}.
*/
boolean supportsFormat(Format format);
/**
* Creates a {@link MetadataDecoder} for the given {@link Format}.
*
* @param format The {@link Format}.
* @return A new {@link MetadataDecoder}.
* @throws IllegalArgumentException If the {@link Format} is not supported.
*/
MetadataDecoder createDecoder(Format format);
/**
* Default {@link MetadataDecoder} implementation.
* <p>
* The formats supported by this factory are:
* <ul>
* <li>ID3 ({@link Id3Decoder})</li>
* <li>EMSG ({@link EventMessageDecoder})</li>
* <li>SCTE-35 ({@link SpliceInfoDecoder})</li>
* </ul>
*/
MetadataDecoderFactory DEFAULT = new MetadataDecoderFactory() {
@Override
public boolean supportsFormat(Format format) {
return getDecoderClass(format.sampleMimeType) != null;
}
@Override
public MetadataDecoder createDecoder(Format format) {
try {
Class<?> clazz = getDecoderClass(format.sampleMimeType);
if (clazz == null) {
throw new IllegalArgumentException("Attempted to create decoder for unsupported format");
}
return clazz.asSubclass(MetadataDecoder.class).getConstructor().newInstance();
} catch (Exception e) {
throw new IllegalStateException("Unexpected error instantiating decoder", e);
}
}
private Class<?> getDecoderClass(String mimeType) {
if (mimeType == null) {
return null;
}
try {
switch (mimeType) {
case MimeTypes.APPLICATION_ID3:
return Class.forName("com.google.android.exoplayer2.metadata.id3.Id3Decoder");
case MimeTypes.APPLICATION_EMSG:
return Class.forName("com.google.android.exoplayer2.metadata.emsg.EventMessageDecoder");
case MimeTypes.APPLICATION_SCTE35:
return Class.forName("com.google.android.exoplayer2.metadata.scte35.SpliceInfoDecoder");
default:
return null;
}
} catch (ClassNotFoundException e) {
return null;
}
}
};
}

View File

@ -49,12 +49,13 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
private static final int MSG_INVOKE_RENDERER = 0; private static final int MSG_INVOKE_RENDERER = 0;
private final MetadataDecoder metadataDecoder; private final MetadataDecoderFactory decoderFactory;
private final Output output; private final Output output;
private final Handler outputHandler; private final Handler outputHandler;
private final FormatHolder formatHolder; private final FormatHolder formatHolder;
private final DecoderInputBuffer buffer; private final DecoderInputBuffer buffer;
private MetadataDecoder decoder;
private boolean inputStreamEnded; private boolean inputStreamEnded;
private long pendingMetadataTimestamp; private long pendingMetadataTimestamp;
private Metadata pendingMetadata; private Metadata pendingMetadata;
@ -66,21 +67,38 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
* looper associated with the application's main thread, which can be obtained using * looper associated with the application's main thread, which can be obtained using
* {@link android.app.Activity#getMainLooper()}. Null may be passed if the output should be * {@link android.app.Activity#getMainLooper()}. Null may be passed if the output should be
* called directly on the player's internal rendering thread. * called directly on the player's internal rendering thread.
* @param metadataDecoder A decoder for the metadata.
*/ */
public MetadataRenderer(Output output, Looper outputLooper, MetadataDecoder metadataDecoder) { public MetadataRenderer(Output output, Looper outputLooper) {
this(output, outputLooper, MetadataDecoderFactory.DEFAULT);
}
/**
* @param output The output.
* @param outputLooper The looper associated with the thread on which the output should be called.
* If the output makes use of standard Android UI components, then this should normally be the
* looper associated with the application's main thread, which can be obtained using
* {@link android.app.Activity#getMainLooper()}. Null may be passed if the output should be
* called directly on the player's internal rendering thread.
* @param decoderFactory A factory from which to obtain {@link MetadataDecoder} instances.
*/
public MetadataRenderer(Output output, Looper outputLooper,
MetadataDecoderFactory decoderFactory) {
super(C.TRACK_TYPE_METADATA); super(C.TRACK_TYPE_METADATA);
this.output = Assertions.checkNotNull(output); this.output = Assertions.checkNotNull(output);
this.outputHandler = outputLooper == null ? null : new Handler(outputLooper, this); this.outputHandler = outputLooper == null ? null : new Handler(outputLooper, this);
this.metadataDecoder = Assertions.checkNotNull(metadataDecoder); this.decoderFactory = Assertions.checkNotNull(decoderFactory);
formatHolder = new FormatHolder(); formatHolder = new FormatHolder();
buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
} }
@Override @Override
public int supportsFormat(Format format) { public int supportsFormat(Format format) {
return metadataDecoder.canDecode(format.sampleMimeType) ? FORMAT_HANDLED return decoderFactory.supportsFormat(format) ? FORMAT_HANDLED : FORMAT_UNSUPPORTED_TYPE;
: FORMAT_UNSUPPORTED_TYPE; }
@Override
protected void onStreamChanged(Format[] formats) throws ExoPlaybackException {
decoder = decoderFactory.createDecoder(formats[0]);
} }
@Override @Override
@ -102,7 +120,7 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
try { try {
buffer.flip(); buffer.flip();
ByteBuffer bufferData = buffer.data; ByteBuffer bufferData = buffer.data;
pendingMetadata = metadataDecoder.decode(bufferData.array(), bufferData.limit()); pendingMetadata = decoder.decode(bufferData.array(), bufferData.limit());
} catch (MetadataDecoderException e) { } catch (MetadataDecoderException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex()); throw ExoPlaybackException.createForRenderer(e, getIndex());
} }
@ -119,6 +137,7 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
@Override @Override
protected void onDisabled() { protected void onDisabled() {
pendingMetadata = null; pendingMetadata = null;
decoder = null;
super.onDisabled(); super.onDisabled();
} }