Commit 01affbb9 by Oliver Woodman

Simplify tx3g support.

parent 254bc5a8
...@@ -23,7 +23,7 @@ import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallba ...@@ -23,7 +23,7 @@ import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallba
import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.ExtractorSampleSource; import com.google.android.exoplayer.extractor.ExtractorSampleSource;
import com.google.android.exoplayer.text.TextTrackRenderer; import com.google.android.exoplayer.text.TextTrackRenderer;
import com.google.android.exoplayer.text.tx3g.TextParser; import com.google.android.exoplayer.text.tx3g.Tx3gParser;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.DefaultUriDataSource; import com.google.android.exoplayer.upstream.DefaultUriDataSource;
...@@ -64,8 +64,8 @@ public class ExtractorRendererBuilder implements RendererBuilder { ...@@ -64,8 +64,8 @@ public class ExtractorRendererBuilder implements RendererBuilder {
player, 50); player, 50);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource, MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource,
null, true, player.getMainHandler(), player); null, true, player.getMainHandler(), player);
TrackRenderer textRenderer = new TextTrackRenderer(sampleSource, player,
TrackRenderer textRenderer = new TextTrackRenderer(sampleSource, player, player.getMainHandler().getLooper(), new TextParser()); player.getMainHandler().getLooper(), new Tx3gParser());
// Invoke the callback. // Invoke the callback.
TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT]; TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT];
......
...@@ -225,8 +225,8 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -225,8 +225,8 @@ public final class Mp4Extractor implements Extractor, SeekMap {
} }
Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd)); Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd));
if (track == null || (track.type != Track.TYPE_AUDIO && track.type != Track.TYPE_VIDEO && if (track == null || track.mediaFormat == null || (track.type != Track.TYPE_AUDIO
track.type != Track.TYPE_TEXT)) { && track.type != Track.TYPE_VIDEO && track.type != Track.TYPE_TEXT)) {
continue; continue;
} }
......
...@@ -55,10 +55,10 @@ public class TextTrackRenderer extends TrackRenderer implements Callback { ...@@ -55,10 +55,10 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
private boolean inputStreamEnded; private boolean inputStreamEnded;
private Subtitle subtitle; private Subtitle subtitle;
private Subtitle nextSubtitle;
private SubtitleParserHelper parserHelper; private SubtitleParserHelper parserHelper;
private HandlerThread parserThread; private HandlerThread parserThread;
private int nextSubtitleEventIndex; private int nextSubtitleEventIndex;
private boolean textRendererNeedsUpdate;
/** /**
* @param source A source from which samples containing subtitle data can be read. * @param source A source from which samples containing subtitle data can be read.
...@@ -122,14 +122,10 @@ public class TextTrackRenderer extends TrackRenderer implements Callback { ...@@ -122,14 +122,10 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
inputStreamEnded = false; inputStreamEnded = false;
currentPositionUs = positionUs; currentPositionUs = positionUs;
source.seekToUs(positionUs); source.seekToUs(positionUs);
if (subtitle != null && (positionUs < subtitle.getStartTime() subtitle = null;
|| subtitle.getLastEventTime() <= positionUs)) { nextSubtitle = null;
subtitle = null;
}
parserHelper.flush(); parserHelper.flush();
clearTextRenderer(); clearTextRenderer();
syncNextEventIndex(positionUs);
textRendererNeedsUpdate = subtitle != null;
} }
@Override @Override
...@@ -141,49 +137,49 @@ public class TextTrackRenderer extends TrackRenderer implements Callback { ...@@ -141,49 +137,49 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
throw new ExoPlaybackException(e); throw new ExoPlaybackException(e);
} }
if (parserHelper.isParsing()) { if (nextSubtitle == null) {
return;
}
Subtitle dequeuedSubtitle = null;
if (subtitle == null) {
try { try {
dequeuedSubtitle = parserHelper.getAndClearResult(); nextSubtitle = parserHelper.getAndClearResult();
} catch (IOException e) { } catch (IOException e) {
throw new ExoPlaybackException(e); throw new ExoPlaybackException(e);
} }
} }
if (subtitle == null && dequeuedSubtitle != null) { boolean textRendererNeedsUpdate = false;
// We've dequeued a new subtitle. Sync the event index and update the subtitle. long subtitleNextEventTimeUs = Long.MAX_VALUE;
subtitle = dequeuedSubtitle; if (subtitle != null) {
syncNextEventIndex(positionUs);
textRendererNeedsUpdate = true;
} else if (subtitle != null) {
// We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we // We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we
// advance to the next event. // advance to the next event.
long nextEventTimeUs = getNextEventTime(); subtitleNextEventTimeUs = getNextEventTime();
while (nextEventTimeUs <= positionUs) { while (subtitleNextEventTimeUs <= positionUs) {
nextSubtitleEventIndex++; nextSubtitleEventIndex++;
nextEventTimeUs = getNextEventTime(); subtitleNextEventTimeUs = getNextEventTime();
textRendererNeedsUpdate = true; textRendererNeedsUpdate = true;
} }
if (nextEventTimeUs == Long.MAX_VALUE) {
// We've finished processing the subtitle.
subtitle = null;
}
} }
// We don't have a subtitle. Try and read the next one from the source, and if we succeed then if (subtitleNextEventTimeUs == Long.MAX_VALUE && nextSubtitle != null
// sync and set textRendererNeedsUpdate. && nextSubtitle.getStartTime() <= positionUs) {
if (!inputStreamEnded && subtitle == null) { // Advance to the next subtitle. Sync the next event index and trigger an update.
subtitle = nextSubtitle;
nextSubtitle = null;
nextSubtitleEventIndex = subtitle.getNextEventTimeIndex(positionUs);
textRendererNeedsUpdate = true;
}
if (textRendererNeedsUpdate && getState() == TrackRenderer.STATE_STARTED) {
// textRendererNeedsUpdate is set and we're playing. Update the renderer.
updateTextRenderer(subtitle.getCues(positionUs));
}
if (!inputStreamEnded && nextSubtitle == null && !parserHelper.isParsing()) {
// Try and read the next subtitle from the source.
try { try {
SampleHolder sampleHolder = parserHelper.getSampleHolder(); SampleHolder sampleHolder = parserHelper.getSampleHolder();
sampleHolder.clearData(); sampleHolder.clearData();
int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false); int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
if (result == SampleSource.SAMPLE_READ) { if (result == SampleSource.SAMPLE_READ) {
parserHelper.startParseOperation(); parserHelper.startParseOperation();
textRendererNeedsUpdate = false;
} else if (result == SampleSource.END_OF_STREAM) { } else if (result == SampleSource.END_OF_STREAM) {
inputStreamEnded = true; inputStreamEnded = true;
} }
...@@ -191,21 +187,12 @@ public class TextTrackRenderer extends TrackRenderer implements Callback { ...@@ -191,21 +187,12 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
throw new ExoPlaybackException(e); throw new ExoPlaybackException(e);
} }
} }
// Update the text renderer if we're both playing and textRendererNeedsUpdate is set.
if (textRendererNeedsUpdate && getState() == TrackRenderer.STATE_STARTED) {
textRendererNeedsUpdate = false;
if (subtitle == null) {
clearTextRenderer();
} else {
updateTextRenderer(positionUs);
}
}
} }
@Override @Override
protected void onDisabled() { protected void onDisabled() {
subtitle = null; subtitle = null;
nextSubtitle = null;
parserThread.quit(); parserThread.quit();
parserThread = null; parserThread = null;
parserHelper = null; parserHelper = null;
...@@ -236,7 +223,7 @@ public class TextTrackRenderer extends TrackRenderer implements Callback { ...@@ -236,7 +223,7 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
@Override @Override
protected boolean isEnded() { protected boolean isEnded() {
return inputStreamEnded && subtitle == null; return inputStreamEnded && (subtitle == null || getNextEventTime() == Long.MAX_VALUE);
} }
@Override @Override
...@@ -246,18 +233,13 @@ public class TextTrackRenderer extends TrackRenderer implements Callback { ...@@ -246,18 +233,13 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
return true; return true;
} }
private void syncNextEventIndex(long positionUs) {
nextSubtitleEventIndex = subtitle == null ? -1 : subtitle.getNextEventTimeIndex(positionUs);
}
private long getNextEventTime() { private long getNextEventTime() {
return ((nextSubtitleEventIndex == -1) return ((nextSubtitleEventIndex == -1)
|| (nextSubtitleEventIndex >= subtitle.getEventTimeCount())) ? Long.MAX_VALUE || (nextSubtitleEventIndex >= subtitle.getEventTimeCount())) ? Long.MAX_VALUE
: (subtitle.getEventTime(nextSubtitleEventIndex)); : (subtitle.getEventTime(nextSubtitleEventIndex));
} }
private void updateTextRenderer(long positionUs) { private void updateTextRenderer(List<Cue> cues) {
List<Cue> cues = subtitle.getCues(positionUs);
if (textRendererHandler != null) { if (textRendererHandler != null) {
textRendererHandler.obtainMessage(MSG_UPDATE_OVERLAY, cues).sendToTarget(); textRendererHandler.obtainMessage(MSG_UPDATE_OVERLAY, cues).sendToTarget();
} else { } else {
...@@ -266,12 +248,7 @@ public class TextTrackRenderer extends TrackRenderer implements Callback { ...@@ -266,12 +248,7 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
} }
private void clearTextRenderer() { private void clearTextRenderer() {
if (textRendererHandler != null) { updateTextRenderer(Collections.<Cue>emptyList());
textRendererHandler.obtainMessage(MSG_UPDATE_OVERLAY, Collections.<Cue>emptyList())
.sendToTarget();
} else {
invokeRendererInternalCues(Collections.<Cue>emptyList());
}
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
......
/*
* 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.tx3g;
import java.util.Comparator;
/**
* A representation of a single tx3g.
*/
class SubtitleData implements Comparable <SubtitleData>, Comparator<SubtitleData> {
public final long startTimePosUs;
public final String subtitle;
SubtitleData(long startTimePosUs, String subtitle)
{
this.startTimePosUs = startTimePosUs;
this.subtitle = subtitle;
}
@Override
public int compare(SubtitleData o1 , SubtitleData o2) {
if (o1.startTimePosUs < o2.startTimePosUs)
return -1;
if (o1.startTimePosUs > o2.startTimePosUs)
return 1;
return 0;
}
@Override
public int compareTo(SubtitleData another) {
if (startTimePosUs < another.startTimePosUs)
return -1;
if (startTimePosUs > another.startTimePosUs)
return 1;
return 0;
}
}
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
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.Subtitle; import com.google.android.exoplayer.text.Subtitle;
import com.google.android.exoplayer.text.SubtitleParser; import com.google.android.exoplayer.text.SubtitleParser;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
...@@ -22,67 +23,29 @@ import com.google.android.exoplayer.util.MimeTypes; ...@@ -22,67 +23,29 @@ import com.google.android.exoplayer.util.MimeTypes;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
/** /**
* A simple Text parser that supports tx3g atom. * A {@link SubtitleParser} for tx3g.
* * <p>
* Only support to parse a single text track at this version , * Currently only supports parsing of a single text track
* since ExtractorSampleSource does not handle multiple audio/video tracks.
*
*/ */
public class TextParser implements SubtitleParser { public final class Tx3gParser implements SubtitleParser {
private static final String TAG = "TextParser";
private final List<SubtitleData> subtitleList;
private static final int MAX_SUBTITLE_COUNT = 4;
public TextParser() {
subtitleList = new LinkedList<SubtitleData>();
}
@Override @Override
public Subtitle parse(InputStream inputStream, String inputEncoding, long startTimeUs) public Subtitle parse(InputStream inputStream, String inputEncoding, long startTimeUs)
throws IOException { throws IOException {
DataInputStream dataInputStream = new DataInputStream(inputStream);
DataInputStream in = new DataInputStream(inputStream); try {
String text = in.readUTF(); String cueText = dataInputStream.readUTF();
text = (text == null) ? "" : text; return new Tx3gSubtitle(startTimeUs, new Cue(cueText));
} finally {
SubtitleData cue = new SubtitleData(startTimeUs, text); dataInputStream.close();
//try to resize the list.
if (subtitleList.size() > 0) {
long lastTimeUs = subtitleList.get(subtitleList.size() - 1).startTimePosUs;
if (startTimeUs < lastTimeUs) {
//when forward seek
subtitleList.clear();
}
while (subtitleList.size() > MAX_SUBTITLE_COUNT) {
subtitleList.remove(0);
}
} }
subtitleList.add(cue);
Collections.sort(subtitleList, new Comparator<SubtitleData>() {
@Override
public int compare(SubtitleData o1 , SubtitleData o2) {
if (o1.startTimePosUs < o2.startTimePosUs)
return -1;
if (o1.startTimePosUs > o2.startTimePosUs)
return 1;
return 0;
}
});
return new TextSubtitle(subtitleList);
} }
@Override @Override
public boolean canParse(String mimeType) { public boolean canParse(String mimeType) {
return MimeTypes.APPLICATION_TX3G.equals(mimeType); return MimeTypes.APPLICATION_TX3G.equals(mimeType);
} }
} }
...@@ -15,82 +15,55 @@ ...@@ -15,82 +15,55 @@
*/ */
package com.google.android.exoplayer.text.tx3g; package com.google.android.exoplayer.text.tx3g;
import java.util.ArrayList;
import java.util.List;
import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.Cue;
import com.google.android.exoplayer.text.Subtitle; import com.google.android.exoplayer.text.Subtitle;
import com.google.android.exoplayer.util.Assertions;
import java.util.Collections;
import java.util.List;
/** /**
* A representation of a tx3g subtitle. * A representation of a tx3g subtitle.
*/ */
public final class TextSubtitle implements Subtitle { /* package */ final class Tx3gSubtitle implements Subtitle {
static String TAG = "TextSubtitle";
private final List<SubtitleData> text; private final long startTimeUs;
private final List<Cue> cues;
public TextSubtitle(List<SubtitleData> text) { public Tx3gSubtitle(long startTimeUs, Cue cue) {
this.text = text; this.startTimeUs = startTimeUs;
this.cues = Collections.singletonList(cue);
} }
@Override @Override
public long getStartTime() { public long getStartTime() {
return text.get(0).startTimePosUs; return startTimeUs;
} }
@Override @Override
public int getNextEventTimeIndex(long timeUs) { public int getNextEventTimeIndex(long timeUs) {
return timeUs < startTimeUs ? 0 : -1;
int index = findTheClosed(timeUs);
int next = (index ) < text.size() ? (index ) : -1;
return next;
} }
@Override @Override
public int getEventTimeCount() { public int getEventTimeCount() {
return text.size(); return 1;
} }
@Override @Override
public long getEventTime(int index) { public long getEventTime(int index) {
if (index > text.size() - 1) return -1; Assertions.checkArgument(index == 0);
return startTimeUs;
return text.get(index).startTimePosUs;
} }
@Override @Override
public long getLastEventTime() { public long getLastEventTime() {
return text.get(0).startTimePosUs; return startTimeUs;
} }
@Override @Override
public List<Cue> getCues(long timeUs) { public List<Cue> getCues(long timeUs) {
int index = findTheClosed(timeUs); return timeUs >= startTimeUs ? cues : Collections.<Cue>emptyList();
List<Cue> list = new ArrayList<>();
if (index == -1) return null;
String str = text.get(index).subtitle;
list.add(new Cue(str));
return list;
} }
private int findTheClosed(long timeUs) {
//TODO : Time complexity is O(n),not good solution.
int length = text.size();
for (int i = 0; i < length ; i++) {
SubtitleData data = text.get(i);
boolean bCheckFront = data.startTimePosUs <= timeUs ;
boolean bCheckEnd = false;
if (i + 1 < length) {
bCheckEnd = text.get(i + 1).startTimePosUs > timeUs ;
} else if (i + 1 == length) {
bCheckEnd = true;
}
if (bCheckFront && bCheckEnd)
return i;
}
return -1;
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment