mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Remove Eia608TrackRenderer
Functionality is moved into Eia608Parser, so that Eia608Parser can be used with TextTrackRenderer, similar to all the other text/subtitle formats. Modified some of the other text/subtitle-related classes to support the new behaviour. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=120755145
This commit is contained in:
parent
6c45233653
commit
0bd26b74b2
19 changed files with 499 additions and 432 deletions
|
|
@ -208,7 +208,6 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer {
|
||||||
long startElapsedRealtimeMs = SystemClock.elapsedRealtime();
|
long startElapsedRealtimeMs = SystemClock.elapsedRealtime();
|
||||||
decoder = new VpxDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE);
|
decoder = new VpxDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE);
|
||||||
decoder.setOutputMode(outputMode);
|
decoder.setOutputMode(outputMode);
|
||||||
decoder.start();
|
|
||||||
notifyDecoderInitialized(startElapsedRealtimeMs, SystemClock.elapsedRealtime());
|
notifyDecoderInitialized(startElapsedRealtimeMs, SystemClock.elapsedRealtime());
|
||||||
codecCounters.codecInitCount++;
|
codecCounters.codecInitCount++;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Subtitle} output from a subtitle parser that extends {@link SimpleSubtitleParser}.
|
||||||
|
*/
|
||||||
|
public final class SimpleSubtitleOutputBuffer extends SubtitleOutputBuffer {
|
||||||
|
|
||||||
|
private SimpleSubtitleParser owner;
|
||||||
|
|
||||||
|
public SimpleSubtitleOutputBuffer(SimpleSubtitleParser owner) {
|
||||||
|
super();
|
||||||
|
this.owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void release() {
|
||||||
|
owner.releaseOutputBuffer(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -19,12 +19,12 @@ import com.google.android.exoplayer.ParserException;
|
||||||
import com.google.android.exoplayer.util.extensions.SimpleDecoder;
|
import com.google.android.exoplayer.util.extensions.SimpleDecoder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses {@link Subtitle}s from {@link SubtitleInputBuffer}s.
|
* Base class for subtitle parsers that use their own decode thread.
|
||||||
*/
|
*/
|
||||||
public abstract class SubtitleParser extends
|
public abstract class SimpleSubtitleParser extends
|
||||||
SimpleDecoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException> {
|
SimpleDecoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException> {
|
||||||
|
|
||||||
protected SubtitleParser() {
|
protected SimpleSubtitleParser() {
|
||||||
super(new SubtitleInputBuffer[2], new SubtitleOutputBuffer[2]);
|
super(new SubtitleInputBuffer[2], new SubtitleOutputBuffer[2]);
|
||||||
setInitialInputBufferSize(1024);
|
setInitialInputBufferSize(1024);
|
||||||
}
|
}
|
||||||
|
|
@ -36,7 +36,7 @@ public abstract class SubtitleParser extends
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final SubtitleOutputBuffer createOutputBuffer() {
|
protected final SubtitleOutputBuffer createOutputBuffer() {
|
||||||
return new SubtitleOutputBuffer(this);
|
return new SimpleSubtitleOutputBuffer(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -56,6 +56,14 @@ public abstract class SubtitleParser extends
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes the data and converts it into a {@link Subtitle}.
|
||||||
|
*
|
||||||
|
* @param data The data to be decoded.
|
||||||
|
* @param size The size of the data.
|
||||||
|
* @return A {@link Subtitle} to rendered.
|
||||||
|
* @throws ParserException A parsing exception.
|
||||||
|
*/
|
||||||
protected abstract Subtitle decode(byte[] data, int size) throws ParserException;
|
protected abstract Subtitle decode(byte[] data, int size) throws ParserException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -18,9 +18,10 @@ package com.google.android.exoplayer.text;
|
||||||
import com.google.android.exoplayer.DecoderInputBuffer;
|
import com.google.android.exoplayer.DecoderInputBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An input buffer for {@link SubtitleParser}.
|
* An input buffer for a subtitle parser.
|
||||||
*/
|
*/
|
||||||
/* package */ final class SubtitleInputBuffer extends DecoderInputBuffer {
|
public final class SubtitleInputBuffer extends DecoderInputBuffer
|
||||||
|
implements Comparable<SubtitleInputBuffer> {
|
||||||
|
|
||||||
public long subsampleOffsetUs;
|
public long subsampleOffsetUs;
|
||||||
|
|
||||||
|
|
@ -28,4 +29,13 @@ import com.google.android.exoplayer.DecoderInputBuffer;
|
||||||
super(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
|
super(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(SubtitleInputBuffer other) {
|
||||||
|
long delta = timeUs - other.timeUs;
|
||||||
|
if (delta == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return delta > 0 ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,17 +21,14 @@ import com.google.android.exoplayer.util.extensions.OutputBuffer;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link Subtitle} output from a {@link SubtitleParser}.
|
* Base class for a {@link Subtitle} output from a subtitle parser.
|
||||||
*/
|
*/
|
||||||
/* package */ final class SubtitleOutputBuffer extends OutputBuffer implements Subtitle {
|
public abstract class SubtitleOutputBuffer extends OutputBuffer implements Subtitle {
|
||||||
|
|
||||||
private final SubtitleParser owner;
|
|
||||||
|
|
||||||
private Subtitle subtitle;
|
private Subtitle subtitle;
|
||||||
private long offsetUs;
|
private long offsetUs;
|
||||||
|
|
||||||
public SubtitleOutputBuffer(SubtitleParser owner) {
|
public SubtitleOutputBuffer() {
|
||||||
this.owner = owner;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOutput(long timestampUs, Subtitle subtitle, long subsampleOffsetUs) {
|
public void setOutput(long timestampUs, Subtitle subtitle, long subsampleOffsetUs) {
|
||||||
|
|
@ -62,9 +59,7 @@ import java.util.List;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public abstract void release();
|
||||||
owner.releaseOutputBuffer(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
|
|
|
||||||
|
|
@ -16,30 +16,37 @@
|
||||||
package com.google.android.exoplayer.text;
|
package com.google.android.exoplayer.text;
|
||||||
|
|
||||||
import com.google.android.exoplayer.Format;
|
import com.google.android.exoplayer.Format;
|
||||||
|
import com.google.android.exoplayer.ParserException;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
|
import com.google.android.exoplayer.util.extensions.Decoder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A factory for {@link SubtitleParser} instances.
|
* A factory for {@link Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException>}
|
||||||
|
* instances that will parse subtitles.
|
||||||
*/
|
*/
|
||||||
public interface SubtitleParserFactory {
|
public interface SubtitleParserFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the factory is able to instantiate a {@link SubtitleParser} for the given
|
* Returns whether the factory is able to instantiate a
|
||||||
|
* {@link Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException>} for the given
|
||||||
* {@link Format}.
|
* {@link Format}.
|
||||||
*
|
*
|
||||||
* @param format The {@link Format}.
|
* @param format The {@link Format}.
|
||||||
* @return True if the factory can instantiate a suitable {@link SubtitleParser}. False otherwise.
|
* @return True if the factory can instantiate a suitable
|
||||||
|
* {@link Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException>}. False
|
||||||
|
* otherwise.
|
||||||
*/
|
*/
|
||||||
boolean supportsFormat(Format format);
|
boolean supportsFormat(Format format);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link SubtitleParser} for the given {@link Format}.
|
* Creates a {@link Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException>} for
|
||||||
|
* the given {@link Format}.
|
||||||
*
|
*
|
||||||
* @param format The {@link Format}.
|
* @param format The {@link Format}.
|
||||||
* @return A new {@link SubtitleParser}.
|
* @return A new {@link Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException>}.
|
||||||
* @throws IllegalArgumentException If the {@link Format} is not supported.
|
* @throws IllegalArgumentException If the {@link Format} is not supported.
|
||||||
*/
|
*/
|
||||||
SubtitleParser createParser(Format format);
|
Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException> createParser(Format format);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default {@link SubtitleParserFactory} implementation.
|
* Default {@link SubtitleParserFactory} implementation.
|
||||||
|
|
@ -51,6 +58,7 @@ public interface SubtitleParserFactory {
|
||||||
* <li>TTML ({@link com.google.android.exoplayer.text.ttml.TtmlParser})</li>
|
* <li>TTML ({@link com.google.android.exoplayer.text.ttml.TtmlParser})</li>
|
||||||
* <li>SubRip ({@link com.google.android.exoplayer.text.subrip.SubripParser})</li>
|
* <li>SubRip ({@link com.google.android.exoplayer.text.subrip.SubripParser})</li>
|
||||||
* <li>TX3G ({@link com.google.android.exoplayer.text.tx3g.Tx3gParser})</li>
|
* <li>TX3G ({@link com.google.android.exoplayer.text.tx3g.Tx3gParser})</li>
|
||||||
|
* <li>Eia608 ({@link com.google.android.exoplayer.text.eia608.Eia608Parser})</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
SubtitleParserFactory DEFAULT = new SubtitleParserFactory() {
|
SubtitleParserFactory DEFAULT = new SubtitleParserFactory() {
|
||||||
|
|
@ -61,13 +69,14 @@ public interface SubtitleParserFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SubtitleParser createParser(Format format) {
|
public Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException> createParser(
|
||||||
|
Format format) {
|
||||||
try {
|
try {
|
||||||
Class<?> clazz = getParserClass(format.sampleMimeType);
|
Class<?> clazz = getParserClass(format.sampleMimeType);
|
||||||
if (clazz == null) {
|
if (clazz == null) {
|
||||||
throw new IllegalArgumentException("Attempted to create parser for unsupported format");
|
throw new IllegalArgumentException("Attempted to create parser for unsupported format");
|
||||||
}
|
}
|
||||||
return clazz.asSubclass(SubtitleParser.class).newInstance();
|
return clazz.asSubclass(Decoder.class).newInstance();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalStateException("Unexpected error instantiating parser", e);
|
throw new IllegalStateException("Unexpected error instantiating parser", e);
|
||||||
}
|
}
|
||||||
|
|
@ -86,6 +95,8 @@ public interface SubtitleParserFactory {
|
||||||
return Class.forName("com.google.android.exoplayer.text.subrip.SubripParser");
|
return Class.forName("com.google.android.exoplayer.text.subrip.SubripParser");
|
||||||
case MimeTypes.APPLICATION_TX3G:
|
case MimeTypes.APPLICATION_TX3G:
|
||||||
return Class.forName("com.google.android.exoplayer.text.tx3g.Tx3gParser");
|
return Class.forName("com.google.android.exoplayer.text.tx3g.Tx3gParser");
|
||||||
|
case MimeTypes.APPLICATION_EIA608:
|
||||||
|
return Class.forName("com.google.android.exoplayer.text.eia608.Eia608Parser");
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import com.google.android.exoplayer.TrackRenderer;
|
||||||
import com.google.android.exoplayer.TrackStream;
|
import com.google.android.exoplayer.TrackStream;
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
|
import com.google.android.exoplayer.util.extensions.Decoder;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
|
@ -38,8 +39,9 @@ import java.util.List;
|
||||||
/**
|
/**
|
||||||
* A {@link TrackRenderer} for subtitles.
|
* A {@link TrackRenderer} for subtitles.
|
||||||
* <p>
|
* <p>
|
||||||
* Text is parsed from sample data using {@link SubtitleParser} instances obtained from a
|
* Text is parsed from sample data using
|
||||||
* {@link SubtitleParserFactory}. The actual rendering of each line of text is delegated to a
|
* {@link Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException>} instances obtained
|
||||||
|
* from a {@link SubtitleParserFactory}. The actual rendering of each line of text is delegated to a
|
||||||
* {@link TextRenderer}.
|
* {@link TextRenderer}.
|
||||||
*/
|
*/
|
||||||
@TargetApi(16)
|
@TargetApi(16)
|
||||||
|
|
@ -54,7 +56,7 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback {
|
||||||
|
|
||||||
private boolean inputStreamEnded;
|
private boolean inputStreamEnded;
|
||||||
private boolean outputStreamEnded;
|
private boolean outputStreamEnded;
|
||||||
private SubtitleParser parser;
|
private Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException> parser;
|
||||||
private SubtitleInputBuffer nextInputBuffer;
|
private SubtitleInputBuffer nextInputBuffer;
|
||||||
private SubtitleOutputBuffer subtitle;
|
private SubtitleOutputBuffer subtitle;
|
||||||
private SubtitleOutputBuffer nextSubtitle;
|
private SubtitleOutputBuffer nextSubtitle;
|
||||||
|
|
@ -79,7 +81,8 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback {
|
||||||
* normally be the looper associated with the application's main thread, which can be obtained
|
* 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 renderer
|
* using {@link android.app.Activity#getMainLooper()}. Null may be passed if the renderer
|
||||||
* should be invoked directly on the player's internal rendering thread.
|
* should be invoked directly on the player's internal rendering thread.
|
||||||
* @param parserFactory A factory from which to obtain {@link SubtitleParser} instances.
|
* @param parserFactory A factory from which to obtain
|
||||||
|
* {@link Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException>} instances.
|
||||||
*/
|
*/
|
||||||
public TextTrackRenderer(TextRenderer textRenderer, Looper textRendererLooper,
|
public TextTrackRenderer(TextRenderer textRenderer, Looper textRendererLooper,
|
||||||
SubtitleParserFactory parserFactory) {
|
SubtitleParserFactory parserFactory) {
|
||||||
|
|
@ -101,7 +104,6 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback {
|
||||||
protected void onEnabled(Format[] formats, boolean joining) throws ExoPlaybackException {
|
protected void onEnabled(Format[] formats, boolean joining) throws ExoPlaybackException {
|
||||||
super.onEnabled(formats, joining);
|
super.onEnabled(formats, joining);
|
||||||
parser = parserFactory.createParser(formats[0]);
|
parser = parserFactory.createParser(formats[0]);
|
||||||
parser.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -174,7 +176,7 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!inputStreamEnded && nextSubtitle == null) {
|
while (!inputStreamEnded) {
|
||||||
if (nextInputBuffer == null) {
|
if (nextInputBuffer == null) {
|
||||||
nextInputBuffer = parser.dequeueInputBuffer();
|
nextInputBuffer = parser.dequeueInputBuffer();
|
||||||
if (nextInputBuffer == null) {
|
if (nextInputBuffer == null) {
|
||||||
|
|
@ -193,6 +195,8 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback {
|
||||||
}
|
}
|
||||||
parser.queueInputBuffer(nextInputBuffer);
|
parser.queueInputBuffer(nextInputBuffer);
|
||||||
nextInputBuffer = null;
|
nextInputBuffer = null;
|
||||||
|
} else if (result == TrackStream.NOTHING_READ) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (ParserException e) {
|
} catch (ParserException e) {
|
||||||
|
|
|
||||||
|
|
@ -15,25 +15,14 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.text.eia608;
|
package com.google.android.exoplayer.text.eia608;
|
||||||
|
|
||||||
/* package */ final class ClosedCaptionList implements Comparable<ClosedCaptionList> {
|
/* package */ final class ClosedCaptionList {
|
||||||
|
|
||||||
public final long timeUs;
|
public final long timeUs;
|
||||||
public final boolean decodeOnly;
|
|
||||||
public final ClosedCaption[] captions;
|
public final ClosedCaption[] captions;
|
||||||
|
|
||||||
public ClosedCaptionList(long timeUs, boolean decodeOnly, ClosedCaption[] captions) {
|
public ClosedCaptionList(long timeUs, ClosedCaption[] captions) {
|
||||||
this.timeUs = timeUs;
|
this.timeUs = timeUs;
|
||||||
this.decodeOnly = decodeOnly;
|
|
||||||
this.captions = captions;
|
this.captions = captions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(ClosedCaptionList other) {
|
|
||||||
long delta = timeUs - other.timeUs;
|
|
||||||
if (delta == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return delta > 0 ? 1 : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,28 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.text.eia608;
|
package com.google.android.exoplayer.text.eia608;
|
||||||
|
|
||||||
import com.google.android.exoplayer.DecoderInputBuffer;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.ParserException;
|
||||||
|
import com.google.android.exoplayer.text.SubtitleInputBuffer;
|
||||||
|
import com.google.android.exoplayer.text.SubtitleOutputBuffer;
|
||||||
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
import com.google.android.exoplayer.util.ParsableBitArray;
|
import com.google.android.exoplayer.util.ParsableBitArray;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
import com.google.android.exoplayer.util.extensions.Decoder;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Facilitates the extraction and parsing of EIA-608 (a.k.a. "line 21 captions" and "CEA-608")
|
* Facilitates the extraction and parsing of EIA-608 (a.k.a. "line 21 captions" and "CEA-608")
|
||||||
* Closed Captions from the SEI data block from H.264.
|
* Closed Captions from the SEI data block from H.264.
|
||||||
*/
|
*/
|
||||||
public final class Eia608Parser {
|
public final class Eia608Parser implements
|
||||||
|
Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException> {
|
||||||
|
|
||||||
|
private static final int NUM_INPUT_BUFFERS = 10;
|
||||||
|
private static final int NUM_OUTPUT_BUFFERS = 2;
|
||||||
|
|
||||||
private static final int PAYLOAD_TYPE_CC = 4;
|
private static final int PAYLOAD_TYPE_CC = 4;
|
||||||
private static final int COUNTRY_CODE = 0xB5;
|
private static final int COUNTRY_CODE = 0xB5;
|
||||||
|
|
@ -34,6 +44,14 @@ public final class Eia608Parser {
|
||||||
private static final int USER_ID = 0x47413934; // "GA94"
|
private static final int USER_ID = 0x47413934; // "GA94"
|
||||||
private static final int USER_DATA_TYPE_CODE = 0x3;
|
private static final int USER_DATA_TYPE_CODE = 0x3;
|
||||||
|
|
||||||
|
private static final int CC_MODE_UNKNOWN = 0;
|
||||||
|
private static final int CC_MODE_ROLL_UP = 1;
|
||||||
|
private static final int CC_MODE_POP_ON = 2;
|
||||||
|
private static final int CC_MODE_PAINT_ON = 3;
|
||||||
|
|
||||||
|
// The default number of rows to display in roll-up captions mode.
|
||||||
|
private static final int DEFAULT_CAPTIONS_ROW_COUNT = 4;
|
||||||
|
|
||||||
// Basic North American 608 CC char set, mostly ASCII. Indexed by (char-0x20).
|
// Basic North American 608 CC char set, mostly ASCII. Indexed by (char-0x20).
|
||||||
private static final int[] BASIC_CHARACTER_SET = new int[] {
|
private static final int[] BASIC_CHARACTER_SET = new int[] {
|
||||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, // ! " # $ % & '
|
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, // ! " # $ % & '
|
||||||
|
|
@ -102,28 +120,126 @@ public final class Eia608Parser {
|
||||||
0xC5, 0xE5, 0xD8, 0xF8, 0x250C, 0x2510, 0x2514, 0x2518
|
0xC5, 0xE5, 0xD8, 0xF8, 0x250C, 0x2510, 0x2514, 0x2518
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private final LinkedList<SubtitleInputBuffer> availableInputBuffers;
|
||||||
|
private final LinkedList<SubtitleOutputBuffer> availableOutputBuffers;
|
||||||
|
private final TreeSet<SubtitleInputBuffer> queuedInputBuffers;
|
||||||
|
|
||||||
private final ParsableBitArray seiBuffer;
|
private final ParsableBitArray seiBuffer;
|
||||||
private final StringBuilder stringBuilder;
|
private final StringBuilder textStringBuilder;
|
||||||
private final ArrayList<ClosedCaption> captions;
|
private final ArrayList<ClosedCaption> captions;
|
||||||
|
|
||||||
/* package */ Eia608Parser() {
|
private final StringBuilder captionStringBuilder;
|
||||||
|
|
||||||
|
private SubtitleInputBuffer dequeuedInputBuffer;
|
||||||
|
|
||||||
|
private int captionMode;
|
||||||
|
private int captionRowCount;
|
||||||
|
private String captionString;
|
||||||
|
private ClosedCaptionCtrl repeatableControl;
|
||||||
|
|
||||||
|
public Eia608Parser() {
|
||||||
|
availableInputBuffers = new LinkedList<>();
|
||||||
|
for (int i = 0; i < NUM_INPUT_BUFFERS; i++) {
|
||||||
|
availableInputBuffers.add(new SubtitleInputBuffer());
|
||||||
|
}
|
||||||
|
availableOutputBuffers = new LinkedList<>();
|
||||||
|
for (int i = 0; i < NUM_OUTPUT_BUFFERS; i++) {
|
||||||
|
availableOutputBuffers.add(new Eia608SubtitleOutputBuffer(this));
|
||||||
|
}
|
||||||
|
queuedInputBuffers = new TreeSet<>();
|
||||||
|
|
||||||
seiBuffer = new ParsableBitArray();
|
seiBuffer = new ParsableBitArray();
|
||||||
stringBuilder = new StringBuilder();
|
textStringBuilder = new StringBuilder();
|
||||||
captions = new ArrayList<>();
|
captions = new ArrayList<>();
|
||||||
|
|
||||||
|
captionStringBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
setCaptionMode(CC_MODE_UNKNOWN);
|
||||||
|
captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ boolean canParse(String mimeType) {
|
@Override
|
||||||
return mimeType.equals(MimeTypes.APPLICATION_EIA608);
|
public SubtitleInputBuffer dequeueInputBuffer() throws ParserException {
|
||||||
|
Assertions.checkState(dequeuedInputBuffer == null);
|
||||||
|
if (availableInputBuffers.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
dequeuedInputBuffer = availableInputBuffers.pollFirst();
|
||||||
|
return dequeuedInputBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ ClosedCaptionList parse(DecoderInputBuffer buffer) {
|
@Override
|
||||||
if (buffer.size < 10) {
|
public void queueInputBuffer(SubtitleInputBuffer inputBuffer) throws ParserException {
|
||||||
|
Assertions.checkArgument(inputBuffer != null);
|
||||||
|
Assertions.checkArgument(inputBuffer == dequeuedInputBuffer);
|
||||||
|
queuedInputBuffers.add(inputBuffer);
|
||||||
|
dequeuedInputBuffer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SubtitleOutputBuffer dequeueOutputBuffer() throws ParserException {
|
||||||
|
if (queuedInputBuffers.isEmpty() || availableOutputBuffers.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst();
|
||||||
|
SubtitleInputBuffer inputBuffer = queuedInputBuffers.pollFirst();
|
||||||
|
|
||||||
|
// TODO: investigate ways of batching multiple SubtitleInputBuffers into a single
|
||||||
|
// SubtitleOutputBuffer
|
||||||
|
if (inputBuffer.isEndOfStream()) {
|
||||||
|
outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
|
||||||
|
return outputBuffer;
|
||||||
|
}
|
||||||
|
ClosedCaptionList captionList = decode(inputBuffer);
|
||||||
|
Eia608Subtitle subtitle = generateSubtitle(captionList);
|
||||||
|
outputBuffer.setOutput(inputBuffer.timeUs, subtitle, 0);
|
||||||
|
if (inputBuffer.isDecodeOnly()) {
|
||||||
|
outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
|
||||||
|
}
|
||||||
|
releaseInputBuffer(inputBuffer);
|
||||||
|
return outputBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseInputBuffer(SubtitleInputBuffer inputBuffer) {
|
||||||
|
inputBuffer.clear();
|
||||||
|
availableInputBuffers.add(inputBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void releaseOutputBuffer(SubtitleOutputBuffer outputBuffer) {
|
||||||
|
outputBuffer.clear();
|
||||||
|
availableOutputBuffers.add(outputBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
setCaptionMode(CC_MODE_UNKNOWN);
|
||||||
|
captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT;
|
||||||
|
captionStringBuilder.setLength(0);
|
||||||
|
captionString = null;
|
||||||
|
repeatableControl = null;
|
||||||
|
while (!queuedInputBuffers.isEmpty()) {
|
||||||
|
releaseInputBuffer(queuedInputBuffers.pollFirst());
|
||||||
|
}
|
||||||
|
if (dequeuedInputBuffer != null) {
|
||||||
|
releaseInputBuffer(dequeuedInputBuffer);
|
||||||
|
dequeuedInputBuffer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClosedCaptionList decode(SubtitleInputBuffer inputBuffer) {
|
||||||
|
if (inputBuffer.size < 10) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
captions.clear();
|
captions.clear();
|
||||||
stringBuilder.setLength(0);
|
textStringBuilder.setLength(0);
|
||||||
seiBuffer.reset(buffer.data.array());
|
seiBuffer.reset(inputBuffer.data.array());
|
||||||
|
|
||||||
// country_code (8) + provider_code (16) + user_identifier (32) + user_data_type_code (8) +
|
// country_code (8) + provider_code (16) + user_identifier (32) + user_data_type_code (8) +
|
||||||
// reserved (1) + process_cc_data_flag (1) + zero_bit (1)
|
// reserved (1) + process_cc_data_flag (1) + zero_bit (1)
|
||||||
|
|
@ -157,7 +273,7 @@ public final class Eia608Parser {
|
||||||
// ccData2 - P|0|1|1|X|X|X|X
|
// ccData2 - P|0|1|1|X|X|X|X
|
||||||
if ((ccData1 == 0x11 || ccData1 == 0x19)
|
if ((ccData1 == 0x11 || ccData1 == 0x19)
|
||||||
&& ((ccData2 & 0x70) == 0x30)) {
|
&& ((ccData2 & 0x70) == 0x30)) {
|
||||||
stringBuilder.append(getSpecialChar(ccData2));
|
textStringBuilder.append(getSpecialChar(ccData2));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,7 +282,7 @@ public final class Eia608Parser {
|
||||||
if ((ccData1 == 0x12 || ccData1 == 0x1A)
|
if ((ccData1 == 0x12 || ccData1 == 0x1A)
|
||||||
&& ((ccData2 & 0x60) == 0x20)) {
|
&& ((ccData2 & 0x60) == 0x20)) {
|
||||||
backspace(); // Remove standard equivalent of the special extended char.
|
backspace(); // Remove standard equivalent of the special extended char.
|
||||||
stringBuilder.append(getExtendedEsFrChar(ccData2));
|
textStringBuilder.append(getExtendedEsFrChar(ccData2));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,7 +291,7 @@ public final class Eia608Parser {
|
||||||
if ((ccData1 == 0x13 || ccData1 == 0x1B)
|
if ((ccData1 == 0x13 || ccData1 == 0x1B)
|
||||||
&& ((ccData2 & 0x60) == 0x20)) {
|
&& ((ccData2 & 0x60) == 0x20)) {
|
||||||
backspace(); // Remove standard equivalent of the special extended char.
|
backspace(); // Remove standard equivalent of the special extended char.
|
||||||
stringBuilder.append(getExtendedPtDeChar(ccData2));
|
textStringBuilder.append(getExtendedPtDeChar(ccData2));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,9 +302,9 @@ public final class Eia608Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basic North American character set.
|
// Basic North American character set.
|
||||||
stringBuilder.append(getChar(ccData1));
|
textStringBuilder.append(getChar(ccData1));
|
||||||
if (ccData2 >= 0x20) {
|
if (ccData2 >= 0x20) {
|
||||||
stringBuilder.append(getChar(ccData2));
|
textStringBuilder.append(getChar(ccData2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -200,7 +316,153 @@ public final class Eia608Parser {
|
||||||
|
|
||||||
ClosedCaption[] captionArray = new ClosedCaption[captions.size()];
|
ClosedCaption[] captionArray = new ClosedCaption[captions.size()];
|
||||||
captions.toArray(captionArray);
|
captions.toArray(captionArray);
|
||||||
return new ClosedCaptionList(buffer.timeUs, buffer.isDecodeOnly(), captionArray);
|
return new ClosedCaptionList(inputBuffer.timeUs, captionArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Eia608Subtitle generateSubtitle(ClosedCaptionList captionList) {
|
||||||
|
int captionBufferSize = (captionList == null) ? 0 : captionList.captions.length;
|
||||||
|
if (captionBufferSize != 0) {
|
||||||
|
boolean isRepeatableControl = false;
|
||||||
|
for (ClosedCaption caption : captionList.captions) {
|
||||||
|
if (caption.type == ClosedCaption.TYPE_CTRL) {
|
||||||
|
ClosedCaptionCtrl captionCtrl = (ClosedCaptionCtrl) caption;
|
||||||
|
isRepeatableControl = captionBufferSize == 1 && captionCtrl.isRepeatable();
|
||||||
|
if (isRepeatableControl && repeatableControl != null
|
||||||
|
&& repeatableControl.cc1 == captionCtrl.cc1
|
||||||
|
&& repeatableControl.cc2 == captionCtrl.cc2) {
|
||||||
|
repeatableControl = null;
|
||||||
|
continue;
|
||||||
|
} else if (isRepeatableControl) {
|
||||||
|
repeatableControl = captionCtrl;
|
||||||
|
}
|
||||||
|
if (captionCtrl.isMiscCode()) {
|
||||||
|
handleMiscCode(captionCtrl);
|
||||||
|
} else if (captionCtrl.isPreambleAddressCode()) {
|
||||||
|
handlePreambleAddressCode();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handleText((ClosedCaptionText) caption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isRepeatableControl) {
|
||||||
|
repeatableControl = null;
|
||||||
|
}
|
||||||
|
if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) {
|
||||||
|
captionString = getDisplayCaption();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Eia608Subtitle(captionString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleMiscCode(ClosedCaptionCtrl caption) {
|
||||||
|
switch (caption.cc2) {
|
||||||
|
case ClosedCaptionCtrl.ROLL_UP_CAPTIONS_2_ROWS:
|
||||||
|
captionRowCount = 2;
|
||||||
|
setCaptionMode(CC_MODE_ROLL_UP);
|
||||||
|
return;
|
||||||
|
case ClosedCaptionCtrl.ROLL_UP_CAPTIONS_3_ROWS:
|
||||||
|
captionRowCount = 3;
|
||||||
|
setCaptionMode(CC_MODE_ROLL_UP);
|
||||||
|
return;
|
||||||
|
case ClosedCaptionCtrl.ROLL_UP_CAPTIONS_4_ROWS:
|
||||||
|
captionRowCount = 4;
|
||||||
|
setCaptionMode(CC_MODE_ROLL_UP);
|
||||||
|
return;
|
||||||
|
case ClosedCaptionCtrl.RESUME_CAPTION_LOADING:
|
||||||
|
setCaptionMode(CC_MODE_POP_ON);
|
||||||
|
return;
|
||||||
|
case ClosedCaptionCtrl.RESUME_DIRECT_CAPTIONING:
|
||||||
|
setCaptionMode(CC_MODE_PAINT_ON);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (captionMode == CC_MODE_UNKNOWN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (caption.cc2) {
|
||||||
|
case ClosedCaptionCtrl.ERASE_DISPLAYED_MEMORY:
|
||||||
|
captionString = null;
|
||||||
|
if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) {
|
||||||
|
captionStringBuilder.setLength(0);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case ClosedCaptionCtrl.ERASE_NON_DISPLAYED_MEMORY:
|
||||||
|
captionStringBuilder.setLength(0);
|
||||||
|
return;
|
||||||
|
case ClosedCaptionCtrl.END_OF_CAPTION:
|
||||||
|
captionString = getDisplayCaption();
|
||||||
|
captionStringBuilder.setLength(0);
|
||||||
|
return;
|
||||||
|
case ClosedCaptionCtrl.CARRIAGE_RETURN:
|
||||||
|
maybeAppendNewline();
|
||||||
|
return;
|
||||||
|
case ClosedCaptionCtrl.BACKSPACE:
|
||||||
|
if (captionStringBuilder.length() > 0) {
|
||||||
|
captionStringBuilder.setLength(captionStringBuilder.length() - 1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePreambleAddressCode() {
|
||||||
|
// TODO: Add better handling of this with specific positioning.
|
||||||
|
maybeAppendNewline();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleText(ClosedCaptionText caption) {
|
||||||
|
captionStringBuilder.append(caption.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeAppendNewline() {
|
||||||
|
int buildLength = captionStringBuilder.length();
|
||||||
|
if (buildLength > 0 && captionStringBuilder.charAt(buildLength - 1) != '\n') {
|
||||||
|
captionStringBuilder.append('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getDisplayCaption() {
|
||||||
|
int buildLength = captionStringBuilder.length();
|
||||||
|
if (buildLength == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean endsWithNewline = captionStringBuilder.charAt(buildLength - 1) == '\n';
|
||||||
|
if (buildLength == 1 && endsWithNewline) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int endIndex = endsWithNewline ? buildLength - 1 : buildLength;
|
||||||
|
if (captionMode != CC_MODE_ROLL_UP) {
|
||||||
|
return captionStringBuilder.substring(0, endIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
int startIndex = 0;
|
||||||
|
int searchBackwardFromIndex = endIndex;
|
||||||
|
for (int i = 0; i < captionRowCount && searchBackwardFromIndex != -1; i++) {
|
||||||
|
searchBackwardFromIndex = captionStringBuilder.lastIndexOf("\n", searchBackwardFromIndex - 1);
|
||||||
|
}
|
||||||
|
if (searchBackwardFromIndex != -1) {
|
||||||
|
startIndex = searchBackwardFromIndex + 1;
|
||||||
|
}
|
||||||
|
captionStringBuilder.delete(0, startIndex);
|
||||||
|
return captionStringBuilder.substring(0, endIndex - startIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCaptionMode(int captionMode) {
|
||||||
|
if (this.captionMode == captionMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.captionMode = captionMode;
|
||||||
|
// Clear the working memory.
|
||||||
|
captionStringBuilder.setLength(0);
|
||||||
|
if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_UNKNOWN) {
|
||||||
|
// When switching to roll-up or unknown, we also need to clear the caption.
|
||||||
|
captionString = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static char getChar(byte ccData) {
|
private static char getChar(byte ccData) {
|
||||||
|
|
@ -224,9 +486,10 @@ public final class Eia608Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addBufferedText() {
|
private void addBufferedText() {
|
||||||
if (stringBuilder.length() > 0) {
|
if (textStringBuilder.length() > 0) {
|
||||||
captions.add(new ClosedCaptionText(stringBuilder.toString()));
|
String textSnippet = textStringBuilder.toString();
|
||||||
stringBuilder.setLength(0);
|
captions.add(new ClosedCaptionText(textSnippet));
|
||||||
|
textStringBuilder.setLength(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* 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.eia608;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.text.Cue;
|
||||||
|
import com.google.android.exoplayer.text.Subtitle;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of an EIA-608 subtitle.
|
||||||
|
*/
|
||||||
|
public final class Eia608Subtitle implements Subtitle {
|
||||||
|
|
||||||
|
private final String caption;
|
||||||
|
|
||||||
|
public Eia608Subtitle(String caption) {
|
||||||
|
this.caption = caption;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNextEventTimeIndex(long timeUs) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getEventTimeCount() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getEventTime(int index) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Cue> getCues(long timeUs) {
|
||||||
|
if (caption == null || caption.isEmpty()) {
|
||||||
|
return Collections.<Cue>emptyList();
|
||||||
|
} else {
|
||||||
|
return Collections.singletonList(new Cue(caption));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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.eia608;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.text.Subtitle;
|
||||||
|
import com.google.android.exoplayer.text.SubtitleOutputBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Subtitle} output from an {@link Eia608Parser}.
|
||||||
|
*/
|
||||||
|
public final class Eia608SubtitleOutputBuffer extends SubtitleOutputBuffer {
|
||||||
|
|
||||||
|
private Eia608Parser owner;
|
||||||
|
|
||||||
|
public Eia608SubtitleOutputBuffer(Eia608Parser owner) {
|
||||||
|
super();
|
||||||
|
this.owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void release() {
|
||||||
|
owner.releaseOutputBuffer(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,351 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.eia608;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
|
||||||
import com.google.android.exoplayer.DecoderInputBuffer;
|
|
||||||
import com.google.android.exoplayer.ExoPlaybackException;
|
|
||||||
import com.google.android.exoplayer.Format;
|
|
||||||
import com.google.android.exoplayer.FormatHolder;
|
|
||||||
import com.google.android.exoplayer.TrackRenderer;
|
|
||||||
import com.google.android.exoplayer.TrackStream;
|
|
||||||
import com.google.android.exoplayer.text.Cue;
|
|
||||||
import com.google.android.exoplayer.text.TextRenderer;
|
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
|
||||||
import com.google.android.exoplayer.util.Util;
|
|
||||||
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Handler.Callback;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.os.Message;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link TrackRenderer} for EIA-608 closed captions in a media stream.
|
|
||||||
*/
|
|
||||||
public final class Eia608TrackRenderer extends TrackRenderer implements Callback {
|
|
||||||
|
|
||||||
private static final int MSG_INVOKE_RENDERER = 0;
|
|
||||||
|
|
||||||
private static final int CC_MODE_UNKNOWN = 0;
|
|
||||||
private static final int CC_MODE_ROLL_UP = 1;
|
|
||||||
private static final int CC_MODE_POP_ON = 2;
|
|
||||||
private static final int CC_MODE_PAINT_ON = 3;
|
|
||||||
|
|
||||||
// The default number of rows to display in roll-up captions mode.
|
|
||||||
private static final int DEFAULT_CAPTIONS_ROW_COUNT = 4;
|
|
||||||
// The maximum duration that captions are parsed ahead of the current position.
|
|
||||||
private static final int MAX_BUFFER_READAHEAD_US = 5000000;
|
|
||||||
|
|
||||||
private final Eia608Parser eia608Parser;
|
|
||||||
private final TextRenderer textRenderer;
|
|
||||||
private final Handler textRendererHandler;
|
|
||||||
private final FormatHolder formatHolder;
|
|
||||||
private final DecoderInputBuffer buffer;
|
|
||||||
private final StringBuilder captionStringBuilder;
|
|
||||||
private final TreeSet<ClosedCaptionList> pendingCaptionLists;
|
|
||||||
|
|
||||||
private boolean inputStreamEnded;
|
|
||||||
private int captionMode;
|
|
||||||
private int captionRowCount;
|
|
||||||
private String caption;
|
|
||||||
private String lastRenderedCaption;
|
|
||||||
private ClosedCaptionCtrl repeatableControl;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param textRenderer The text renderer.
|
|
||||||
* @param textRendererLooper The looper associated with the thread on which textRenderer 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 Eia608TrackRenderer(TextRenderer textRenderer, Looper textRendererLooper) {
|
|
||||||
this.textRenderer = Assertions.checkNotNull(textRenderer);
|
|
||||||
textRendererHandler = textRendererLooper == null ? null : new Handler(textRendererLooper, this);
|
|
||||||
eia608Parser = new Eia608Parser();
|
|
||||||
formatHolder = new FormatHolder();
|
|
||||||
buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
|
|
||||||
captionStringBuilder = new StringBuilder();
|
|
||||||
pendingCaptionLists = new TreeSet<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int supportsFormat(Format format) {
|
|
||||||
return eia608Parser.canParse(format.sampleMimeType) ? TrackRenderer.FORMAT_HANDLED
|
|
||||||
: TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void reset(long positionUs) {
|
|
||||||
inputStreamEnded = false;
|
|
||||||
repeatableControl = null;
|
|
||||||
pendingCaptionLists.clear();
|
|
||||||
clearPendingBuffer();
|
|
||||||
captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT;
|
|
||||||
setCaptionMode(CC_MODE_UNKNOWN);
|
|
||||||
invokeRenderer(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
|
|
||||||
if (isBufferPending()) {
|
|
||||||
maybeParsePendingBuffer(positionUs);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!isBufferPending() && !inputStreamEnded) {
|
|
||||||
int result = readSource(formatHolder, buffer);
|
|
||||||
if (result == TrackStream.BUFFER_READ) {
|
|
||||||
if (buffer.isEndOfStream()) {
|
|
||||||
inputStreamEnded = true;
|
|
||||||
} else {
|
|
||||||
maybeParsePendingBuffer(positionUs);
|
|
||||||
}
|
|
||||||
} else if (result == TrackStream.NOTHING_READ) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!pendingCaptionLists.isEmpty()) {
|
|
||||||
if (pendingCaptionLists.first().timeUs > positionUs) {
|
|
||||||
// We're too early to render any of the pending caption lists.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Remove and consume the next caption list.
|
|
||||||
ClosedCaptionList nextCaptionList = pendingCaptionLists.pollFirst();
|
|
||||||
consumeCaptionList(nextCaptionList);
|
|
||||||
// Update the renderer, unless the caption list was marked for decoding only.
|
|
||||||
if (!nextCaptionList.decodeOnly) {
|
|
||||||
invokeRenderer(caption);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isEnded() {
|
|
||||||
return inputStreamEnded;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isReady() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void invokeRenderer(String text) {
|
|
||||||
if (Util.areEqual(lastRenderedCaption, text)) {
|
|
||||||
// No change.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.lastRenderedCaption = text;
|
|
||||||
if (textRendererHandler != null) {
|
|
||||||
textRendererHandler.obtainMessage(MSG_INVOKE_RENDERER, text).sendToTarget();
|
|
||||||
} else {
|
|
||||||
invokeRendererInternal(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public boolean handleMessage(Message msg) {
|
|
||||||
switch (msg.what) {
|
|
||||||
case MSG_INVOKE_RENDERER:
|
|
||||||
invokeRendererInternal((String) msg.obj);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void invokeRendererInternal(String cueText) {
|
|
||||||
if (cueText == null) {
|
|
||||||
textRenderer.onCues(Collections.<Cue>emptyList());
|
|
||||||
} else {
|
|
||||||
textRenderer.onCues(Collections.singletonList(new Cue(cueText)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void maybeParsePendingBuffer(long positionUs) {
|
|
||||||
if (buffer.timeUs > positionUs + MAX_BUFFER_READAHEAD_US) {
|
|
||||||
// We're too early to parse the buffer.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ClosedCaptionList holder = eia608Parser.parse(buffer);
|
|
||||||
clearPendingBuffer();
|
|
||||||
if (holder != null) {
|
|
||||||
pendingCaptionLists.add(holder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void consumeCaptionList(ClosedCaptionList captionList) {
|
|
||||||
int captionBufferSize = captionList.captions.length;
|
|
||||||
if (captionBufferSize == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isRepeatableControl = false;
|
|
||||||
for (int i = 0; i < captionBufferSize; i++) {
|
|
||||||
ClosedCaption caption = captionList.captions[i];
|
|
||||||
if (caption.type == ClosedCaption.TYPE_CTRL) {
|
|
||||||
ClosedCaptionCtrl captionCtrl = (ClosedCaptionCtrl) caption;
|
|
||||||
isRepeatableControl = captionBufferSize == 1 && captionCtrl.isRepeatable();
|
|
||||||
if (isRepeatableControl && repeatableControl != null
|
|
||||||
&& repeatableControl.cc1 == captionCtrl.cc1
|
|
||||||
&& repeatableControl.cc2 == captionCtrl.cc2) {
|
|
||||||
repeatableControl = null;
|
|
||||||
continue;
|
|
||||||
} else if (isRepeatableControl) {
|
|
||||||
repeatableControl = captionCtrl;
|
|
||||||
}
|
|
||||||
if (captionCtrl.isMiscCode()) {
|
|
||||||
handleMiscCode(captionCtrl);
|
|
||||||
} else if (captionCtrl.isPreambleAddressCode()) {
|
|
||||||
handlePreambleAddressCode();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
handleText((ClosedCaptionText) caption);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isRepeatableControl) {
|
|
||||||
repeatableControl = null;
|
|
||||||
}
|
|
||||||
if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) {
|
|
||||||
caption = getDisplayCaption();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleText(ClosedCaptionText captionText) {
|
|
||||||
if (captionMode != CC_MODE_UNKNOWN) {
|
|
||||||
captionStringBuilder.append(captionText.text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleMiscCode(ClosedCaptionCtrl captionCtrl) {
|
|
||||||
switch (captionCtrl.cc2) {
|
|
||||||
case ClosedCaptionCtrl.ROLL_UP_CAPTIONS_2_ROWS:
|
|
||||||
captionRowCount = 2;
|
|
||||||
setCaptionMode(CC_MODE_ROLL_UP);
|
|
||||||
return;
|
|
||||||
case ClosedCaptionCtrl.ROLL_UP_CAPTIONS_3_ROWS:
|
|
||||||
captionRowCount = 3;
|
|
||||||
setCaptionMode(CC_MODE_ROLL_UP);
|
|
||||||
return;
|
|
||||||
case ClosedCaptionCtrl.ROLL_UP_CAPTIONS_4_ROWS:
|
|
||||||
captionRowCount = 4;
|
|
||||||
setCaptionMode(CC_MODE_ROLL_UP);
|
|
||||||
return;
|
|
||||||
case ClosedCaptionCtrl.RESUME_CAPTION_LOADING:
|
|
||||||
setCaptionMode(CC_MODE_POP_ON);
|
|
||||||
return;
|
|
||||||
case ClosedCaptionCtrl.RESUME_DIRECT_CAPTIONING:
|
|
||||||
setCaptionMode(CC_MODE_PAINT_ON);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (captionMode == CC_MODE_UNKNOWN) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (captionCtrl.cc2) {
|
|
||||||
case ClosedCaptionCtrl.ERASE_DISPLAYED_MEMORY:
|
|
||||||
caption = null;
|
|
||||||
if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) {
|
|
||||||
captionStringBuilder.setLength(0);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case ClosedCaptionCtrl.ERASE_NON_DISPLAYED_MEMORY:
|
|
||||||
captionStringBuilder.setLength(0);
|
|
||||||
return;
|
|
||||||
case ClosedCaptionCtrl.END_OF_CAPTION:
|
|
||||||
caption = getDisplayCaption();
|
|
||||||
captionStringBuilder.setLength(0);
|
|
||||||
return;
|
|
||||||
case ClosedCaptionCtrl.CARRIAGE_RETURN:
|
|
||||||
maybeAppendNewline();
|
|
||||||
return;
|
|
||||||
case ClosedCaptionCtrl.BACKSPACE:
|
|
||||||
if (captionStringBuilder.length() > 0) {
|
|
||||||
captionStringBuilder.setLength(captionStringBuilder.length() - 1);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handlePreambleAddressCode() {
|
|
||||||
// TODO: Add better handling of this with specific positioning.
|
|
||||||
maybeAppendNewline();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setCaptionMode(int captionMode) {
|
|
||||||
if (this.captionMode == captionMode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.captionMode = captionMode;
|
|
||||||
// Clear the working memory.
|
|
||||||
captionStringBuilder.setLength(0);
|
|
||||||
if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_UNKNOWN) {
|
|
||||||
// When switching to roll-up or unknown, we also need to clear the caption.
|
|
||||||
caption = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void maybeAppendNewline() {
|
|
||||||
int buildLength = captionStringBuilder.length();
|
|
||||||
if (buildLength > 0 && captionStringBuilder.charAt(buildLength - 1) != '\n') {
|
|
||||||
captionStringBuilder.append('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getDisplayCaption() {
|
|
||||||
int buildLength = captionStringBuilder.length();
|
|
||||||
if (buildLength == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean endsWithNewline = captionStringBuilder.charAt(buildLength - 1) == '\n';
|
|
||||||
if (buildLength == 1 && endsWithNewline) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
int endIndex = endsWithNewline ? buildLength - 1 : buildLength;
|
|
||||||
if (captionMode != CC_MODE_ROLL_UP) {
|
|
||||||
return captionStringBuilder.substring(0, endIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
int startIndex = 0;
|
|
||||||
int searchBackwardFromIndex = endIndex;
|
|
||||||
for (int i = 0; i < captionRowCount && searchBackwardFromIndex != -1; i++) {
|
|
||||||
searchBackwardFromIndex = captionStringBuilder.lastIndexOf("\n", searchBackwardFromIndex - 1);
|
|
||||||
}
|
|
||||||
if (searchBackwardFromIndex != -1) {
|
|
||||||
startIndex = searchBackwardFromIndex + 1;
|
|
||||||
}
|
|
||||||
captionStringBuilder.delete(0, startIndex);
|
|
||||||
return captionStringBuilder.substring(0, endIndex - startIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearPendingBuffer() {
|
|
||||||
buffer.clear();
|
|
||||||
buffer.timeUs = C.UNSET_TIME_US;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isBufferPending() {
|
|
||||||
return buffer.timeUs != C.UNSET_TIME_US;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
package com.google.android.exoplayer.text.subrip;
|
package com.google.android.exoplayer.text.subrip;
|
||||||
|
|
||||||
import com.google.android.exoplayer.text.Cue;
|
import com.google.android.exoplayer.text.Cue;
|
||||||
import com.google.android.exoplayer.text.SubtitleParser;
|
import com.google.android.exoplayer.text.SimpleSubtitleParser;
|
||||||
import com.google.android.exoplayer.util.LongArray;
|
import com.google.android.exoplayer.util.LongArray;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ import java.util.regex.Pattern;
|
||||||
/**
|
/**
|
||||||
* A simple SubRip parser.
|
* A simple SubRip parser.
|
||||||
*/
|
*/
|
||||||
public final class SubripParser extends SubtitleParser {
|
public final class SubripParser extends SimpleSubtitleParser {
|
||||||
|
|
||||||
private static final String TAG = "SubripParser";
|
private static final String TAG = "SubripParser";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ package com.google.android.exoplayer.text.ttml;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.ParserException;
|
import com.google.android.exoplayer.ParserException;
|
||||||
import com.google.android.exoplayer.text.SubtitleParser;
|
import com.google.android.exoplayer.text.SimpleSubtitleParser;
|
||||||
import com.google.android.exoplayer.util.ColorParser;
|
import com.google.android.exoplayer.util.ColorParser;
|
||||||
import com.google.android.exoplayer.util.ParserUtil;
|
import com.google.android.exoplayer.util.ParserUtil;
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
@ -58,7 +58,7 @@ import java.util.regex.Pattern;
|
||||||
* </p>
|
* </p>
|
||||||
* @see <a href="http://www.w3.org/TR/ttaf1-dfxp/">TTML specification</a>
|
* @see <a href="http://www.w3.org/TR/ttaf1-dfxp/">TTML specification</a>
|
||||||
*/
|
*/
|
||||||
public final class TtmlParser extends SubtitleParser {
|
public final class TtmlParser extends SimpleSubtitleParser {
|
||||||
|
|
||||||
private static final String TAG = "TtmlParser";
|
private static final String TAG = "TtmlParser";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,15 +16,15 @@
|
||||||
package com.google.android.exoplayer.text.tx3g;
|
package com.google.android.exoplayer.text.tx3g;
|
||||||
|
|
||||||
import com.google.android.exoplayer.text.Cue;
|
import com.google.android.exoplayer.text.Cue;
|
||||||
|
import com.google.android.exoplayer.text.SimpleSubtitleParser;
|
||||||
import com.google.android.exoplayer.text.Subtitle;
|
import com.google.android.exoplayer.text.Subtitle;
|
||||||
import com.google.android.exoplayer.text.SubtitleParser;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link SubtitleParser} for tx3g.
|
* A subtitle parser for tx3g.
|
||||||
* <p>
|
* <p>
|
||||||
* Currently only supports parsing of a single text track.
|
* Currently only supports parsing of a single text track.
|
||||||
*/
|
*/
|
||||||
public final class Tx3gParser extends SubtitleParser {
|
public final class Tx3gParser extends SimpleSubtitleParser {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Subtitle decode(byte[] bytes, int length) {
|
protected Subtitle decode(byte[] bytes, int length) {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ package com.google.android.exoplayer.text.webvtt;
|
||||||
|
|
||||||
import com.google.android.exoplayer.ParserException;
|
import com.google.android.exoplayer.ParserException;
|
||||||
import com.google.android.exoplayer.text.Cue;
|
import com.google.android.exoplayer.text.Cue;
|
||||||
import com.google.android.exoplayer.text.SubtitleParser;
|
import com.google.android.exoplayer.text.SimpleSubtitleParser;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
|
|
@ -26,9 +26,9 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link SubtitleParser} for Webvtt embedded in a Mp4 container file.
|
* A subtitle parser for Webvtt embedded in a Mp4 container file.
|
||||||
*/
|
*/
|
||||||
public final class Mp4WebvttParser extends SubtitleParser {
|
public final class Mp4WebvttParser extends SimpleSubtitleParser {
|
||||||
|
|
||||||
private static final int BOX_HEADER_SIZE = 8;
|
private static final int BOX_HEADER_SIZE = 8;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
package com.google.android.exoplayer.text.webvtt;
|
package com.google.android.exoplayer.text.webvtt;
|
||||||
|
|
||||||
import com.google.android.exoplayer.ParserException;
|
import com.google.android.exoplayer.ParserException;
|
||||||
import com.google.android.exoplayer.text.SubtitleParser;
|
import com.google.android.exoplayer.text.SimpleSubtitleParser;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
@ -29,7 +29,7 @@ import java.util.List;
|
||||||
* <p>
|
* <p>
|
||||||
* @see <a href="http://dev.w3.org/html5/webvtt">WebVTT specification</a>
|
* @see <a href="http://dev.w3.org/html5/webvtt">WebVTT specification</a>
|
||||||
*/
|
*/
|
||||||
public final class WebvttParser extends SubtitleParser {
|
public final class WebvttParser extends SimpleSubtitleParser {
|
||||||
|
|
||||||
private static final int NO_EVENT_FOUND = -1;
|
private static final int NO_EVENT_FOUND = -1;
|
||||||
private static final int END_OF_FILE_FOUND = 0;
|
private static final int END_OF_FILE_FOUND = 0;
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,6 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
|
||||||
notifyDecoderError(e);
|
notifyDecoderError(e);
|
||||||
throw ExoPlaybackException.createForRenderer(e, getIndex());
|
throw ExoPlaybackException.createForRenderer(e, getIndex());
|
||||||
}
|
}
|
||||||
decoder.start();
|
|
||||||
codecCounters.codecInitCount++;
|
codecCounters.codecInitCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import java.util.LinkedList;
|
||||||
* Base class for {@link Decoder}s that use their own decode thread.
|
* Base class for {@link Decoder}s that use their own decode thread.
|
||||||
*/
|
*/
|
||||||
public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends OutputBuffer,
|
public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends OutputBuffer,
|
||||||
E extends Exception> extends Thread implements Decoder<I, O, E> {
|
E extends Exception> implements Decoder<I, O, E> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listener for {@link SimpleDecoder} events.
|
* Listener for {@link SimpleDecoder} events.
|
||||||
|
|
@ -41,6 +41,8 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final Thread decodeThread;
|
||||||
|
|
||||||
private final Object lock;
|
private final Object lock;
|
||||||
private final LinkedList<I> queuedInputBuffers;
|
private final LinkedList<I> queuedInputBuffers;
|
||||||
private final LinkedList<O> queuedOutputBuffers;
|
private final LinkedList<O> queuedOutputBuffers;
|
||||||
|
|
@ -73,6 +75,13 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp
|
||||||
for (int i = 0; i < availableOutputBufferCount; i++) {
|
for (int i = 0; i < availableOutputBufferCount; i++) {
|
||||||
availableOutputBuffers[i] = createOutputBuffer();
|
availableOutputBuffers[i] = createOutputBuffer();
|
||||||
}
|
}
|
||||||
|
decodeThread = new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
SimpleDecoder.this.run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
decodeThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -159,7 +168,7 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp
|
||||||
lock.notify();
|
lock.notify();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
join();
|
decodeThread.join();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
|
|
@ -188,8 +197,7 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void run() {
|
||||||
public final void run() {
|
|
||||||
try {
|
try {
|
||||||
while (decode()) {
|
while (decode()) {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue