diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorRendererBuilder.java index 6cab32227d..c94aa1e24d 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorRendererBuilder.java @@ -22,6 +22,8 @@ import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder; import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback; import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.ExtractorSampleSource; +import com.google.android.exoplayer.text.TextTrackRenderer; +import com.google.android.exoplayer.text.tx3g.TextParser; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer.upstream.DefaultUriDataSource; @@ -63,10 +65,13 @@ public class ExtractorRendererBuilder implements RendererBuilder { MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource, null, true, player.getMainHandler(), player); + TrackRenderer textRenderer = new TextTrackRenderer(sampleSource, player, player.getMainHandler().getLooper(), new TextParser()); + // Invoke the callback. TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT]; renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer; renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer; + renderers[DemoPlayer.TYPE_TEXT] = textRenderer; callback.onRenderers(null, null, renderers, bandwidthMeter); } diff --git a/library/src/main/java/com/google/android/exoplayer/MediaFormat.java b/library/src/main/java/com/google/android/exoplayer/MediaFormat.java index bb266934f4..aaaad2cf26 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaFormat.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaFormat.java @@ -106,6 +106,10 @@ public class MediaFormat { return createFormatForMimeType(MimeTypes.APPLICATION_TTML); } + public static MediaFormat createTx3GFormat() { + return createFormatForMimeType(MimeTypes.APPLICATION_TX3G); + } + public static MediaFormat createFormatForMimeType(String mimeType) { return new MediaFormat(mimeType, NO_VALUE, C.UNKNOWN_TIME_US, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Atom.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Atom.java index a2b3524ddb..55378fa9fa 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Atom.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Atom.java @@ -91,7 +91,7 @@ import java.util.List; public static final int TYPE_stsz = Util.getIntegerCodeForString("stsz"); public static final int TYPE_stco = Util.getIntegerCodeForString("stco"); public static final int TYPE_co64 = Util.getIntegerCodeForString("co64"); - + public static final int TYPE_tx3g = Util.getIntegerCodeForString("tx3g"); public final int type; Atom(int type) { diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java index 846c71ad9b..418fb3e855 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java @@ -340,6 +340,8 @@ import java.util.List; holder, i); } else if (childAtomType == Atom.TYPE_TTML) { holder.mediaFormat = MediaFormat.createTtmlFormat(); + } else if (childAtomType == Atom.TYPE_tx3g) { + holder.mediaFormat = MediaFormat.createTx3GFormat(); } stsd.setPosition(childStartPosition + childAtomSize); } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java index 1c8b2b4faa..ed0241477a 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java @@ -225,7 +225,8 @@ public final class Mp4Extractor implements Extractor, SeekMap { } 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.type != Track.TYPE_AUDIO && track.type != Track.TYPE_VIDEO && + track.type != Track.TYPE_TEXT)) { continue; } @@ -359,7 +360,8 @@ public final class Mp4Extractor implements Extractor, SeekMap { || atom == Atom.TYPE_avc1 || atom == Atom.TYPE_avcC || atom == Atom.TYPE_mp4a || atom == Atom.TYPE_esds || atom == Atom.TYPE_stts || atom == Atom.TYPE_stss || atom == Atom.TYPE_ctts || atom == Atom.TYPE_stsc || atom == Atom.TYPE_stsz - || atom == Atom.TYPE_stco || atom == Atom.TYPE_co64 || atom == Atom.TYPE_tkhd; + || atom == Atom.TYPE_stco || atom == Atom.TYPE_co64 || atom == Atom.TYPE_tkhd + || atom == Atom.TYPE_tx3g; } /** Returns whether the extractor should parse a container atom with type {@code atom}. */ diff --git a/library/src/main/java/com/google/android/exoplayer/text/tx3g/SubtitleData.java b/library/src/main/java/com/google/android/exoplayer/text/tx3g/SubtitleData.java new file mode 100644 index 0000000000..ce4603b29d --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/text/tx3g/SubtitleData.java @@ -0,0 +1,52 @@ +/* + * 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 , Comparator { + + 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; + } +} diff --git a/library/src/main/java/com/google/android/exoplayer/text/tx3g/TextParser.java b/library/src/main/java/com/google/android/exoplayer/text/tx3g/TextParser.java new file mode 100644 index 0000000000..7f4098d8e5 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/text/tx3g/TextParser.java @@ -0,0 +1,88 @@ +/* + * 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 com.google.android.exoplayer.text.Subtitle; +import com.google.android.exoplayer.text.SubtitleParser; +import com.google.android.exoplayer.util.MimeTypes; + +import java.io.DataInputStream; +import java.io.IOException; +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. + * + * Only support to parse a single text track at this version , + * since ExtractorSampleSource does not handle multiple audio/video tracks. + * + */ +public class TextParser implements SubtitleParser { + private static final String TAG = "TextParser"; + + private final List subtitleList; + private static final int MAX_SUBTITLE_COUNT = 4; + public TextParser() { + + subtitleList = new LinkedList(); + } + + @Override + public Subtitle parse(InputStream inputStream, String inputEncoding, long startTimeUs) + throws IOException { + + DataInputStream in = new DataInputStream(inputStream); + String text = in.readUTF(); + text = (text == null) ? "" : text; + + SubtitleData cue = new SubtitleData(startTimeUs, text); + + //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() { + @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 + public boolean canParse(String mimeType) { + return MimeTypes.APPLICATION_TX3G.equals(mimeType); + } +} diff --git a/library/src/main/java/com/google/android/exoplayer/text/tx3g/TextSubtitle.java b/library/src/main/java/com/google/android/exoplayer/text/tx3g/TextSubtitle.java new file mode 100644 index 0000000000..c59389b4bb --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/text/tx3g/TextSubtitle.java @@ -0,0 +1,96 @@ +/* + * 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.ArrayList; +import java.util.List; + +import com.google.android.exoplayer.text.Cue; +import com.google.android.exoplayer.text.Subtitle; + +/** + * A representation of a tx3g subtitle. + */ +public final class TextSubtitle implements Subtitle { + static String TAG = "TextSubtitle"; + private final List text; + + public TextSubtitle(List text) { + this.text = text; + } + + @Override + public long getStartTime() { + return text.get(0).startTimePosUs; + } + + @Override + public int getNextEventTimeIndex(long timeUs) { + + int index = findTheClosed(timeUs); + int next = (index ) < text.size() ? (index ) : -1; + return next; + } + + @Override + public int getEventTimeCount() { + return text.size(); + } + + @Override + public long getEventTime(int index) { + if (index > text.size() - 1) return -1; + + return text.get(index).startTimePosUs; + } + + @Override + public long getLastEventTime() { + return text.get(0).startTimePosUs; + } + + @Override + public List getCues(long timeUs) { + int index = findTheClosed(timeUs); + List 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; + } +} diff --git a/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java b/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java index 21aa81a965..d81ea84991 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java +++ b/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java @@ -58,6 +58,7 @@ public class MimeTypes { public static final String APPLICATION_EIA608 = BASE_TYPE_APPLICATION + "/eia-608"; public static final String APPLICATION_TTML = BASE_TYPE_APPLICATION + "/ttml+xml"; public static final String APPLICATION_M3U8 = BASE_TYPE_APPLICATION + "/x-mpegURL"; + public static final String APPLICATION_TX3G = BASE_TYPE_APPLICATION + "/x-quicktime-tx3g"; private MimeTypes() {}