/* * 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.text; import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Util; import android.media.MediaCodec; import android.os.Handler; import android.os.Looper; import android.os.Message; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; /** * Wraps a {@link SubtitleParser}, exposing an interface similar to {@link MediaCodec} for * asynchronous parsing of subtitles. */ /* package */ final class SubtitleParserHelper implements Handler.Callback { private static final int MSG_FORMAT = 0; private static final int MSG_SAMPLE = 1; private final SubtitleParser parser; private final Handler handler; private SampleHolder sampleHolder; private boolean parsing; private PlayableSubtitle result; private IOException error; private boolean subtitlesAreRelative; private long subtitleOffsetUs; /** * @param looper The {@link Looper} associated with the thread on which parsing should occur. * @param parser The parser that should be used to parse the raw data. */ public SubtitleParserHelper(Looper looper, SubtitleParser parser) { this.handler = new Handler(looper, this); this.parser = parser; flush(); } /** * Flushes the helper, canceling the current parsing operation, if there is one. */ public synchronized void flush() { sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); parsing = false; result = null; error = null; } /** * Whether the helper is currently performing a parsing operation. * * @return True if the helper is currently performing a parsing operation. False otherwise. */ public synchronized boolean isParsing() { return parsing; } /** * Gets the holder that should be populated with data to be parsed. * <p> * The returned holder will remain valid unless {@link #flush()} is called. If {@link #flush()} * is called the holder is replaced, and this method should be called again to obtain the new * holder. * * @return The holder that should be populated with data to be parsed. */ public synchronized SampleHolder getSampleHolder() { return sampleHolder; } /** * Sets the format of subsequent samples. * * @param format The format. */ public void setFormat(MediaFormat format) { handler.obtainMessage(MSG_FORMAT, format).sendToTarget(); } /** * Start a parsing operation. * <p> * The holder returned by {@link #getSampleHolder()} should be populated with the data to be * parsed prior to calling this method. */ public synchronized void startParseOperation() { Assertions.checkState(!parsing); parsing = true; result = null; error = null; handler.obtainMessage(MSG_SAMPLE, Util.getTopInt(sampleHolder.timeUs), Util.getBottomInt(sampleHolder.timeUs), sampleHolder).sendToTarget(); } /** * Gets the result of the most recent parsing operation. * <p> * The result is cleared as a result of calling this method, and so subsequent calls will return * null until a subsequent parsing operation has finished. * * @return The result of the parsing operation, or null. * @throws IOException If the parsing operation failed. */ public synchronized PlayableSubtitle getAndClearResult() throws IOException { try { if (error != null) { throw error; } return result; } finally { error = null; result = null; } } @Override public boolean handleMessage(Message msg) { switch (msg.what) { case MSG_FORMAT: handleFormat((MediaFormat) msg.obj); break; case MSG_SAMPLE: long sampleTimeUs = Util.getLong(msg.arg1, msg.arg2); SampleHolder holder = (SampleHolder) msg.obj; handleSample(sampleTimeUs, holder); break; } return true; } private void handleFormat(MediaFormat format) { subtitlesAreRelative = format.subsampleOffsetUs == MediaFormat.OFFSET_SAMPLE_RELATIVE; subtitleOffsetUs = subtitlesAreRelative ? 0 : format.subsampleOffsetUs; } private void handleSample(long sampleTimeUs, SampleHolder holder) { Subtitle parsedSubtitle = null; IOException error = null; try { InputStream inputStream = new ByteArrayInputStream(holder.data.array(), 0, holder.size); parsedSubtitle = parser.parse(inputStream); } catch (IOException e) { error = e; } synchronized (this) { if (sampleHolder != holder) { // A flush has occurred since this holder was posted. Do nothing. } else { this.result = new PlayableSubtitle(parsedSubtitle, subtitlesAreRelative, sampleTimeUs, subtitleOffsetUs); this.error = error; this.parsing = false; } } } }