/* * 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.metadata; import com.google.android.exoplayer.ExoPlaybackException; import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormatHolder; import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.SampleSourceTrackRenderer; import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.util.Assertions; import android.os.Handler; import android.os.Handler.Callback; import android.os.Looper; import android.os.Message; import java.io.IOException; /** * A {@link TrackRenderer} for metadata embedded in a media stream. * * @param <T> The type of the metadata. */ public final class MetadataTrackRenderer<T> extends SampleSourceTrackRenderer implements Callback { /** * An interface for components that process metadata. * * @param <T> The type of the metadata. */ public interface MetadataRenderer<T> { /** * Invoked each time there is a metadata associated with current playback time. * * @param metadata The metadata to process. */ void onMetadata(T metadata); } private static final int MSG_INVOKE_RENDERER = 0; private final MetadataParser<T> metadataParser; private final MetadataRenderer<T> metadataRenderer; private final Handler metadataHandler; private final MediaFormatHolder formatHolder; private final SampleHolder sampleHolder; private boolean inputStreamEnded; private long pendingMetadataTimestamp; private T pendingMetadata; /** * @param source A source from which samples containing metadata can be read. * @param metadataParser A parser for parsing the metadata. * @param metadataRenderer The metadata renderer to receive the parsed metadata. * @param metadataRendererLooper The looper associated with the thread on which metadataRenderer * should be invoked. If the renderer makes use of standard Android UI components, then this * should normally be the looper associated with the applications' main thread, which can be * obtained using {@link android.app.Activity#getMainLooper()}. Null may be passed if the * renderer should be invoked directly on the player's internal rendering thread. */ public MetadataTrackRenderer(SampleSource source, MetadataParser<T> metadataParser, MetadataRenderer<T> metadataRenderer, Looper metadataRendererLooper) { super(source); this.metadataParser = Assertions.checkNotNull(metadataParser); this.metadataRenderer = Assertions.checkNotNull(metadataRenderer); this.metadataHandler = metadataRendererLooper == null ? null : new Handler(metadataRendererLooper, this); formatHolder = new MediaFormatHolder(); sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); } @Override protected boolean handlesTrack(MediaFormat mediaFormat) { return metadataParser.canParse(mediaFormat.mimeType); } @Override protected void onEnabled(int track, long positionUs, boolean joining) throws ExoPlaybackException { super.onEnabled(track, positionUs, joining); seekToInternal(); } @Override protected void seekTo(long positionUs) throws ExoPlaybackException { super.seekTo(positionUs); seekToInternal(); } private void seekToInternal() { pendingMetadata = null; inputStreamEnded = false; } @Override protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { continueBufferingSource(positionUs); if (!inputStreamEnded && pendingMetadata == null) { int result = readSource(positionUs, formatHolder, sampleHolder, false); if (result == SampleSource.SAMPLE_READ) { pendingMetadataTimestamp = sampleHolder.timeUs; try { pendingMetadata = metadataParser.parse(sampleHolder.data.array(), sampleHolder.size); } catch (IOException e) { throw new ExoPlaybackException(e); } sampleHolder.data.clear(); } else if (result == SampleSource.END_OF_STREAM) { inputStreamEnded = true; } } if (pendingMetadata != null && pendingMetadataTimestamp <= positionUs) { invokeRenderer(pendingMetadata); pendingMetadata = null; } } @Override protected void onDisabled() throws ExoPlaybackException { pendingMetadata = null; super.onDisabled(); } @Override protected long getBufferedPositionUs() { return TrackRenderer.END_OF_TRACK_US; } @Override protected boolean isEnded() { return inputStreamEnded; } @Override protected boolean isReady() { return true; } private void invokeRenderer(T metadata) { if (metadataHandler != null) { metadataHandler.obtainMessage(MSG_INVOKE_RENDERER, metadata).sendToTarget(); } else { invokeRendererInternal(metadata); } } @SuppressWarnings("unchecked") @Override public boolean handleMessage(Message msg) { switch (msg.what) { case MSG_INVOKE_RENDERER: invokeRendererInternal((T) msg.obj); return true; } return false; } private void invokeRendererInternal(T metadata) { metadataRenderer.onMetadata(metadata); } }