From 59ab0fa9f1e5f09d1e9182383184c7ab2acf6cfc Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 9 Jan 2017 04:15:00 -0800 Subject: [PATCH] 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 --- .../exoplayer2/demo/PlayerActivity.java | 2 +- .../android/exoplayer2/SimpleExoPlayer.java | 12 +-- .../metadata/MetadataDecoderFactory.java | 99 +++++++++++++++++++ .../exoplayer2/metadata/MetadataRenderer.java | 33 +++++-- 4 files changed, 127 insertions(+), 19 deletions(-) create mode 100644 library/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index 2d7c8189a2c..01779c8acbd 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -278,7 +278,7 @@ private void initializePlayer() { player.addListener(eventLogger); player.setAudioDebugListener(eventLogger); player.setVideoDebugListener(eventLogger); - player.setId3Output(eventLogger); + player.setMetadataOutput(eventLogger); simpleExoPlayerView.setPlayer(player); if (isTimelineStatic) { diff --git a/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 73df6a1e7a2..5a3e01a1097 100644 --- a/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -36,7 +36,6 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.metadata.Metadata; 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.TrackGroupArray; import com.google.android.exoplayer2.text.Cue; @@ -448,15 +447,6 @@ public void setTextOutput(TextRenderer.Output 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. * @@ -771,7 +761,7 @@ protected void buildTextRenderers(Context context, Handler mainHandler, protected void buildMetadataRenderers(Context context, Handler mainHandler, @ExtensionRendererMode int extensionRendererMode, MetadataRenderer.Output output, ArrayList out) { - out.add(new MetadataRenderer(output, mainHandler.getLooper(), new Id3Decoder())); + out.add(new MetadataRenderer(output, mainHandler.getLooper())); } /** diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java b/library/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java new file mode 100644 index 00000000000..414a8269d7f --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java @@ -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. + *

+ * The formats supported by this factory are: + *

+ */ + 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; + } + } + + }; + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java b/library/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java index ff1364610b4..c8b51139fb9 100644 --- a/library/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java @@ -49,12 +49,13 @@ public interface Output { private static final int MSG_INVOKE_RENDERER = 0; - private final MetadataDecoder metadataDecoder; + private final MetadataDecoderFactory decoderFactory; private final Output output; private final Handler outputHandler; private final FormatHolder formatHolder; private final DecoderInputBuffer buffer; + private MetadataDecoder decoder; private boolean inputStreamEnded; private long pendingMetadataTimestamp; private Metadata pendingMetadata; @@ -66,21 +67,38 @@ public interface Output { * 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 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); this.output = Assertions.checkNotNull(output); this.outputHandler = outputLooper == null ? null : new Handler(outputLooper, this); - this.metadataDecoder = Assertions.checkNotNull(metadataDecoder); + this.decoderFactory = Assertions.checkNotNull(decoderFactory); formatHolder = new FormatHolder(); buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); } @Override public int supportsFormat(Format format) { - return metadataDecoder.canDecode(format.sampleMimeType) ? FORMAT_HANDLED - : FORMAT_UNSUPPORTED_TYPE; + return decoderFactory.supportsFormat(format) ? FORMAT_HANDLED : FORMAT_UNSUPPORTED_TYPE; + } + + @Override + protected void onStreamChanged(Format[] formats) throws ExoPlaybackException { + decoder = decoderFactory.createDecoder(formats[0]); } @Override @@ -102,7 +120,7 @@ public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackEx try { buffer.flip(); ByteBuffer bufferData = buffer.data; - pendingMetadata = metadataDecoder.decode(bufferData.array(), bufferData.limit()); + pendingMetadata = decoder.decode(bufferData.array(), bufferData.limit()); } catch (MetadataDecoderException e) { throw ExoPlaybackException.createForRenderer(e, getIndex()); } @@ -119,6 +137,7 @@ public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackEx @Override protected void onDisabled() { pendingMetadata = null; + decoder = null; super.onDisabled(); }