/*
* 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;
}
}
}
}