1ba3699b568e656910641b246b27448d92632a389Sungsoo Lim/*
2ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * Copyright (C) 2014 The Android Open Source Project
3ba3699b568e656910641b246b27448d92632a389Sungsoo Lim *
4ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * Licensed under the Apache License, Version 2.0 (the "License");
5ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * you may not use this file except in compliance with the License.
6ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * You may obtain a copy of the License at
7ba3699b568e656910641b246b27448d92632a389Sungsoo Lim *
8ba3699b568e656910641b246b27448d92632a389Sungsoo Lim *      http://www.apache.org/licenses/LICENSE-2.0
9ba3699b568e656910641b246b27448d92632a389Sungsoo Lim *
10ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * Unless required by applicable law or agreed to in writing, software
11ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * distributed under the License is distributed on an "AS IS" BASIS,
12ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * See the License for the specific language governing permissions and
14ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * limitations under the License.
15ba3699b568e656910641b246b27448d92632a389Sungsoo Lim */
16ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
17ba3699b568e656910641b246b27448d92632a389Sungsoo Limpackage android.media;
18ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
19ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport android.content.Context;
20ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport android.text.TextUtils;
21ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport android.util.AttributeSet;
22ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport android.util.Log;
23ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport android.view.Gravity;
24ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport android.view.View;
25ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport android.view.accessibility.CaptioningManager;
26ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport android.widget.LinearLayout;
27ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport android.widget.TextView;
28ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
29ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport java.io.IOException;
30ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport java.io.StringReader;
31ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport java.util.ArrayList;
32ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport java.util.LinkedList;
33ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport java.util.List;
34ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport java.util.TreeSet;
35ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport java.util.Vector;
36ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport java.util.regex.Matcher;
37ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport java.util.regex.Pattern;
38ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
39ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport org.xmlpull.v1.XmlPullParser;
40ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport org.xmlpull.v1.XmlPullParserException;
41ba3699b568e656910641b246b27448d92632a389Sungsoo Limimport org.xmlpull.v1.XmlPullParserFactory;
42ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
43ba3699b568e656910641b246b27448d92632a389Sungsoo Lim/** @hide */
44ba3699b568e656910641b246b27448d92632a389Sungsoo Limpublic class TtmlRenderer extends SubtitleController.Renderer {
45ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private final Context mContext;
46ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
47ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private static final String MEDIA_MIMETYPE_TEXT_TTML = "application/ttml+xml";
48ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
49ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private TtmlRenderingWidget mRenderingWidget;
50ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
51ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public TtmlRenderer(Context context) {
52ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mContext = context;
53ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
54ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
55ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    @Override
56ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public boolean supports(MediaFormat format) {
57ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        if (format.containsKey(MediaFormat.KEY_MIME)) {
58ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            return format.getString(MediaFormat.KEY_MIME).equals(MEDIA_MIMETYPE_TEXT_TTML);
59ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
60ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        return false;
61ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
62ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
63ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    @Override
64ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public SubtitleTrack createTrack(MediaFormat format) {
65ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        if (mRenderingWidget == null) {
66ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            mRenderingWidget = new TtmlRenderingWidget(mContext);
67ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
68ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        return new TtmlTrack(mRenderingWidget, format);
69ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
70ba3699b568e656910641b246b27448d92632a389Sungsoo Lim}
71ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
72ba3699b568e656910641b246b27448d92632a389Sungsoo Lim/**
73ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * A class which provides utillity methods for TTML parsing.
74ba3699b568e656910641b246b27448d92632a389Sungsoo Lim *
75ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * @hide
76ba3699b568e656910641b246b27448d92632a389Sungsoo Lim */
77ba3699b568e656910641b246b27448d92632a389Sungsoo Limfinal class TtmlUtils {
78ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static final String TAG_TT = "tt";
79ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static final String TAG_HEAD = "head";
80ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static final String TAG_BODY = "body";
81ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static final String TAG_DIV = "div";
82ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static final String TAG_P = "p";
83ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static final String TAG_SPAN = "span";
84ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static final String TAG_BR = "br";
85ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static final String TAG_STYLE = "style";
86ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static final String TAG_STYLING = "styling";
87ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static final String TAG_LAYOUT = "layout";
88ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static final String TAG_REGION = "region";
89ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static final String TAG_METADATA = "metadata";
90ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static final String TAG_SMPTE_IMAGE = "smpte:image";
91ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static final String TAG_SMPTE_DATA = "smpte:data";
92ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static final String TAG_SMPTE_INFORMATION = "smpte:information";
93ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static final String PCDATA = "#pcdata";
94ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static final String ATTR_BEGIN = "begin";
95ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static final String ATTR_DURATION = "dur";
96ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static final String ATTR_END = "end";
97ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static final long INVALID_TIMESTAMP = Long.MAX_VALUE;
98ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
99ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    /**
100ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * Time expression RE according to the spec:
101ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * http://www.w3.org/TR/ttaf1-dfxp/#timing-value-timeExpression
102ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     */
103ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private static final Pattern CLOCK_TIME = Pattern.compile(
104ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            "^([0-9][0-9]+):([0-9][0-9]):([0-9][0-9])"
105ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            + "(?:(\\.[0-9]+)|:([0-9][0-9])(?:\\.([0-9]+))?)?$");
106ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
107ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private static final Pattern OFFSET_TIME = Pattern.compile(
108ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            "^([0-9]+(?:\\.[0-9]+)?)(h|m|s|ms|f|t)$");
109ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
110ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private TtmlUtils() {
111ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
112ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
113ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    /**
114ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * Parses the given time expression and returns a timestamp in millisecond.
115ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * <p>
116ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * For the format of the time expression, please refer <a href=
117ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * "http://www.w3.org/TR/ttaf1-dfxp/#timing-value-timeExpression">timeExpression</a>
118ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     *
119ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @param time A string which includes time expression.
120ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @param frameRate the framerate of the stream.
121ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @param subframeRate the sub-framerate of the stream
122ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @param tickRate the tick rate of the stream.
123ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @return the parsed timestamp in micro-second.
124ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @throws NumberFormatException if the given string does not match to the
125ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     *             format.
126ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     */
127ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static long parseTimeExpression(String time, int frameRate, int subframeRate,
128ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            int tickRate) throws NumberFormatException {
129ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        Matcher matcher = CLOCK_TIME.matcher(time);
130ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        if (matcher.matches()) {
131ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            String hours = matcher.group(1);
132ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            double durationSeconds = Long.parseLong(hours) * 3600;
133ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            String minutes = matcher.group(2);
134ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            durationSeconds += Long.parseLong(minutes) * 60;
135ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            String seconds = matcher.group(3);
136ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            durationSeconds += Long.parseLong(seconds);
137ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            String fraction = matcher.group(4);
138ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            durationSeconds += (fraction != null) ? Double.parseDouble(fraction) : 0;
139ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            String frames = matcher.group(5);
140ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            durationSeconds += (frames != null) ? ((double)Long.parseLong(frames)) / frameRate : 0;
141ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            String subframes = matcher.group(6);
142ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            durationSeconds += (subframes != null) ? ((double)Long.parseLong(subframes))
143ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                    / subframeRate / frameRate
144ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                    : 0;
145ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            return (long)(durationSeconds * 1000);
146ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
147ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        matcher = OFFSET_TIME.matcher(time);
148ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        if (matcher.matches()) {
149ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            String timeValue = matcher.group(1);
150ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            double value = Double.parseDouble(timeValue);
151ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            String unit = matcher.group(2);
152ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            if (unit.equals("h")) {
153ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                value *= 3600L * 1000000L;
154ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            } else if (unit.equals("m")) {
155ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                value *= 60 * 1000000;
156ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            } else if (unit.equals("s")) {
157ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                value *= 1000000;
158ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            } else if (unit.equals("ms")) {
159ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                value *= 1000;
160ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            } else if (unit.equals("f")) {
161ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                value = value / frameRate * 1000000;
162ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            } else if (unit.equals("t")) {
163ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                value = value / tickRate * 1000000;
164ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            }
165ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            return (long)value;
166ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
167ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        throw new NumberFormatException("Malformed time expression : " + time);
168ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
169ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
170ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    /**
171ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * Applies <a href
172ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * src="http://www.w3.org/TR/ttaf1-dfxp/#content-attribute-space">the
173ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * default space policy</a> to the given string.
174ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     *
175ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @param in A string to apply the policy.
176ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     */
177ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static String applyDefaultSpacePolicy(String in) {
178ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        return applySpacePolicy(in, true);
179ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
180ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
181ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    /**
182ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * Applies the space policy to the given string. This applies <a href
183ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * src="http://www.w3.org/TR/ttaf1-dfxp/#content-attribute-space">the
184ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * default space policy</a> with linefeed-treatment as treat-as-space
185ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * or preserve.
186ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     *
187ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @param in A string to apply the policy.
188ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @param treatLfAsSpace Whether convert line feeds to spaces or not.
189ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     */
190ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static String applySpacePolicy(String in, boolean treatLfAsSpace) {
191ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        // Removes CR followed by LF. ref:
192ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        // http://www.w3.org/TR/xml/#sec-line-ends
193ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        String crRemoved = in.replaceAll("\r\n", "\n");
194ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        // Apply suppress-at-line-break="auto" and
195ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        // white-space-treatment="ignore-if-surrounding-linefeed"
196ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        String spacesNeighboringLfRemoved = crRemoved.replaceAll(" *\n *", "\n");
197ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        // Apply linefeed-treatment="treat-as-space"
198ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        String lfToSpace = treatLfAsSpace ? spacesNeighboringLfRemoved.replaceAll("\n", " ")
199ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                : spacesNeighboringLfRemoved;
200ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        // Apply white-space-collapse="true"
201ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        String spacesCollapsed = lfToSpace.replaceAll("[ \t\\x0B\f\r]+", " ");
202ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        return spacesCollapsed;
203ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
204ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
205ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    /**
206ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * Returns the timed text for the given time period.
207ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     *
208ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @param root The root node of the TTML document.
209ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @param startUs The start time of the time period in microsecond.
210ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @param endUs The end time of the time period in microsecond.
211ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     */
212ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static String extractText(TtmlNode root, long startUs, long endUs) {
213ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        StringBuilder text = new StringBuilder();
214ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        extractText(root, startUs, endUs, text, false);
215ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        return text.toString().replaceAll("\n$", "");
216ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
217ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
218ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private static void extractText(TtmlNode node, long startUs, long endUs, StringBuilder out,
219ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            boolean inPTag) {
220ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        if (node.mName.equals(TtmlUtils.PCDATA) && inPTag) {
221ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            out.append(node.mText);
222ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        } else if (node.mName.equals(TtmlUtils.TAG_BR) && inPTag) {
223ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            out.append("\n");
224ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        } else if (node.mName.equals(TtmlUtils.TAG_METADATA)) {
225ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            // do nothing.
226ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        } else if (node.isActive(startUs, endUs)) {
227ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            boolean pTag = node.mName.equals(TtmlUtils.TAG_P);
228ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            int length = out.length();
229ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            for (int i = 0; i < node.mChildren.size(); ++i) {
230ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                extractText(node.mChildren.get(i), startUs, endUs, out, pTag || inPTag);
231ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            }
232ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            if (pTag && length != out.length()) {
233ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                out.append("\n");
234ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            }
235ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
236ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
237ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
238ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    /**
239ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * Returns a TTML fragment string for the given time period.
240ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     *
241ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @param root The root node of the TTML document.
242ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @param startUs The start time of the time period in microsecond.
243ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @param endUs The end time of the time period in microsecond.
244ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     */
245ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public static String extractTtmlFragment(TtmlNode root, long startUs, long endUs) {
246ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        StringBuilder fragment = new StringBuilder();
247ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        extractTtmlFragment(root, startUs, endUs, fragment);
248ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        return fragment.toString();
249ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
250ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
251ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private static void extractTtmlFragment(TtmlNode node, long startUs, long endUs,
252ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            StringBuilder out) {
253ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        if (node.mName.equals(TtmlUtils.PCDATA)) {
254ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            out.append(node.mText);
255ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        } else if (node.mName.equals(TtmlUtils.TAG_BR)) {
256ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            out.append("<br/>");
257ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        } else if (node.isActive(startUs, endUs)) {
258ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            out.append("<");
259ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            out.append(node.mName);
260ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            out.append(node.mAttributes);
261ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            out.append(">");
262ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            for (int i = 0; i < node.mChildren.size(); ++i) {
263ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                extractTtmlFragment(node.mChildren.get(i), startUs, endUs, out);
264ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            }
265ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            out.append("</");
266ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            out.append(node.mName);
267ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            out.append(">");
268ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
269ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
270ba3699b568e656910641b246b27448d92632a389Sungsoo Lim}
271ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
272ba3699b568e656910641b246b27448d92632a389Sungsoo Lim/**
273ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * A container class which represents a cue in TTML.
274ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * @hide
275ba3699b568e656910641b246b27448d92632a389Sungsoo Lim */
276ba3699b568e656910641b246b27448d92632a389Sungsoo Limclass TtmlCue extends SubtitleTrack.Cue {
277ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public String mText;
278ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public String mTtmlFragment;
279ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
280ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public TtmlCue(long startTimeMs, long endTimeMs, String text, String ttmlFragment) {
281ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        this.mStartTimeMs = startTimeMs;
282ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        this.mEndTimeMs = endTimeMs;
283ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        this.mText = text;
284ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        this.mTtmlFragment = ttmlFragment;
285ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
286ba3699b568e656910641b246b27448d92632a389Sungsoo Lim}
287ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
288ba3699b568e656910641b246b27448d92632a389Sungsoo Lim/**
289ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * A container class which represents a node in TTML.
290ba3699b568e656910641b246b27448d92632a389Sungsoo Lim *
291ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * @hide
292ba3699b568e656910641b246b27448d92632a389Sungsoo Lim */
293ba3699b568e656910641b246b27448d92632a389Sungsoo Limclass TtmlNode {
294ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public final String mName;
295ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public final String mAttributes;
296ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public final TtmlNode mParent;
297ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public final String mText;
298ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public final List<TtmlNode> mChildren = new ArrayList<TtmlNode>();
299ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public final long mRunId;
300ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public final long mStartTimeMs;
301ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public final long mEndTimeMs;
302ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
303ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public TtmlNode(String name, String attributes, String text, long startTimeMs, long endTimeMs,
304ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            TtmlNode parent, long runId) {
305ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        this.mName = name;
306ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        this.mAttributes = attributes;
307ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        this.mText = text;
308ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        this.mStartTimeMs = startTimeMs;
309ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        this.mEndTimeMs = endTimeMs;
310ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        this.mParent = parent;
311ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        this.mRunId = runId;
312ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
313ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
314ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    /**
315ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * Check if this node is active in the given time range.
316ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     *
317ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @param startTimeMs The start time of the range to check in microsecond.
318ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @param endTimeMs The end time of the range to check in microsecond.
319ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @return return true if the given range overlaps the time range of this
320ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     *         node.
321ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     */
322ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public boolean isActive(long startTimeMs, long endTimeMs) {
323ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        return this.mEndTimeMs > startTimeMs && this.mStartTimeMs < endTimeMs;
324ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
325ba3699b568e656910641b246b27448d92632a389Sungsoo Lim}
326ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
327ba3699b568e656910641b246b27448d92632a389Sungsoo Lim/**
328ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * A simple TTML parser (http://www.w3.org/TR/ttaf1-dfxp/) which supports DFXP
329ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * presentation profile.
330ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * <p>
331ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * Supported features in this parser are:
332ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * <ul>
333ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * <li>content
334ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * <li>core
335ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * <li>presentation
336ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * <li>profile
337ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * <li>structure
338ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * <li>time-offset
339ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * <li>timing
340ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * <li>tickRate
341ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * <li>time-clock-with-frames
342ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * <li>time-clock
343ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * <li>time-offset-with-frames
344ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * <li>time-offset-with-ticks
345ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * </ul>
346ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * </p>
347ba3699b568e656910641b246b27448d92632a389Sungsoo Lim *
348ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * @hide
349ba3699b568e656910641b246b27448d92632a389Sungsoo Lim */
350ba3699b568e656910641b246b27448d92632a389Sungsoo Limclass TtmlParser {
351ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    static final String TAG = "TtmlParser";
352ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
353ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    // TODO: read and apply the following attributes if specified.
354ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private static final int DEFAULT_FRAMERATE = 30;
355ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private static final int DEFAULT_SUBFRAMERATE = 1;
356ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private static final int DEFAULT_TICKRATE = 1;
357ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
358ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private XmlPullParser mParser;
359ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private final TtmlNodeListener mListener;
360ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private long mCurrentRunId;
361ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
362ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public TtmlParser(TtmlNodeListener listener) {
363ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mListener = listener;
364ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
365ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
366ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    /**
367ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * Parse TTML data. Once this is called, all the previous data are
368ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * reset and it starts parsing for the given text.
369ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     *
370ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @param ttmlText TTML text to parse.
371ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @throws XmlPullParserException
372ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * @throws IOException
373ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     */
374ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public void parse(String ttmlText, long runId) throws XmlPullParserException, IOException {
375ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mParser = null;
376ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mCurrentRunId = runId;
377ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        loadParser(ttmlText);
378ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        parseTtml();
379ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
380ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
381ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private void loadParser(String ttmlFragment) throws XmlPullParserException {
382ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
383ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        factory.setNamespaceAware(false);
384ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mParser = factory.newPullParser();
385ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        StringReader in = new StringReader(ttmlFragment);
386ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mParser.setInput(in);
387ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
388ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
389ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private void extractAttribute(XmlPullParser parser, int i, StringBuilder out) {
390ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        out.append(" ");
391ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        out.append(parser.getAttributeName(i));
392ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        out.append("=\"");
393ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        out.append(parser.getAttributeValue(i));
394ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        out.append("\"");
395ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
396ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
397ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private void parseTtml() throws XmlPullParserException, IOException {
398ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        LinkedList<TtmlNode> nodeStack = new LinkedList<TtmlNode>();
399ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        int depthInUnsupportedTag = 0;
400ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        boolean active = true;
401ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        while (!isEndOfDoc()) {
402ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            int eventType = mParser.getEventType();
403ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            TtmlNode parent = nodeStack.peekLast();
404ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            if (active) {
405ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                if (eventType == XmlPullParser.START_TAG) {
406ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                    if (!isSupportedTag(mParser.getName())) {
407ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                        Log.w(TAG, "Unsupported tag " + mParser.getName() + " is ignored.");
408ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                        depthInUnsupportedTag++;
409ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                        active = false;
410ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                    } else {
411ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                        TtmlNode node = parseNode(parent);
412ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                        nodeStack.addLast(node);
413ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                        if (parent != null) {
414ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                            parent.mChildren.add(node);
415ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                        }
416ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                    }
417ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                } else if (eventType == XmlPullParser.TEXT) {
418ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                    String text = TtmlUtils.applyDefaultSpacePolicy(mParser.getText());
419ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                    if (!TextUtils.isEmpty(text)) {
420ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                        parent.mChildren.add(new TtmlNode(
421ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                                TtmlUtils.PCDATA, "", text, 0, TtmlUtils.INVALID_TIMESTAMP,
422ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                                parent, mCurrentRunId));
423ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
424ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                    }
425ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                } else if (eventType == XmlPullParser.END_TAG) {
426ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                    if (mParser.getName().equals(TtmlUtils.TAG_P)) {
427ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                        mListener.onTtmlNodeParsed(nodeStack.getLast());
428ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                    } else if (mParser.getName().equals(TtmlUtils.TAG_TT)) {
429ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                        mListener.onRootNodeParsed(nodeStack.getLast());
430ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                    }
431ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                    nodeStack.removeLast();
432ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                }
433ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            } else {
434ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                if (eventType == XmlPullParser.START_TAG) {
435ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                    depthInUnsupportedTag++;
436ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                } else if (eventType == XmlPullParser.END_TAG) {
437ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                    depthInUnsupportedTag--;
438ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                    if (depthInUnsupportedTag == 0) {
439ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                        active = true;
440ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                    }
441ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                }
442ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            }
443ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            mParser.next();
444ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
445ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
446ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
447ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private TtmlNode parseNode(TtmlNode parent) throws XmlPullParserException, IOException {
448ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        int eventType = mParser.getEventType();
449ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        if (!(eventType == XmlPullParser.START_TAG)) {
450ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            return null;
451ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
452ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        StringBuilder attrStr = new StringBuilder();
453ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        long start = 0;
454ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        long end = TtmlUtils.INVALID_TIMESTAMP;
455ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        long dur = 0;
456ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        for (int i = 0; i < mParser.getAttributeCount(); ++i) {
457ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            String attr = mParser.getAttributeName(i);
458ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            String value = mParser.getAttributeValue(i);
459ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            // TODO: check if it's safe to ignore the namespace of attributes as follows.
460ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            attr = attr.replaceFirst("^.*:", "");
461ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            if (attr.equals(TtmlUtils.ATTR_BEGIN)) {
462ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                start = TtmlUtils.parseTimeExpression(value, DEFAULT_FRAMERATE,
463ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                        DEFAULT_SUBFRAMERATE, DEFAULT_TICKRATE);
464ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            } else if (attr.equals(TtmlUtils.ATTR_END)) {
465ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                end = TtmlUtils.parseTimeExpression(value, DEFAULT_FRAMERATE, DEFAULT_SUBFRAMERATE,
466ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                        DEFAULT_TICKRATE);
467ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            } else if (attr.equals(TtmlUtils.ATTR_DURATION)) {
468ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                dur = TtmlUtils.parseTimeExpression(value, DEFAULT_FRAMERATE, DEFAULT_SUBFRAMERATE,
469ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                        DEFAULT_TICKRATE);
470ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            } else {
471ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                extractAttribute(mParser, i, attrStr);
472ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            }
473ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
474ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        if (parent != null) {
475ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            start += parent.mStartTimeMs;
476ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            if (end != TtmlUtils.INVALID_TIMESTAMP) {
477ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                end += parent.mStartTimeMs;
478ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            }
479ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
480ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        if (dur > 0) {
481ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            if (end != TtmlUtils.INVALID_TIMESTAMP) {
482ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                Log.e(TAG, "'dur' and 'end' attributes are defined at the same time." +
483ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                        "'end' value is ignored.");
484ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            }
485ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            end = start + dur;
486ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
487ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        if (parent != null) {
488ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            // If the end time remains unspecified, then the end point is
489ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            // interpreted as the end point of the external time interval.
490ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            if (end == TtmlUtils.INVALID_TIMESTAMP &&
491ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                    parent.mEndTimeMs != TtmlUtils.INVALID_TIMESTAMP &&
492ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                    end > parent.mEndTimeMs) {
493ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                end = parent.mEndTimeMs;
494ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            }
495ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
496ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        TtmlNode node = new TtmlNode(mParser.getName(), attrStr.toString(), null, start, end,
497ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                parent, mCurrentRunId);
498ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        return node;
499ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
500ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
501ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private boolean isEndOfDoc() throws XmlPullParserException {
502ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        return (mParser.getEventType() == XmlPullParser.END_DOCUMENT);
503ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
504ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
505ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private static boolean isSupportedTag(String tag) {
506ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        if (tag.equals(TtmlUtils.TAG_TT) || tag.equals(TtmlUtils.TAG_HEAD) ||
507ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                tag.equals(TtmlUtils.TAG_BODY) || tag.equals(TtmlUtils.TAG_DIV) ||
508ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                tag.equals(TtmlUtils.TAG_P) || tag.equals(TtmlUtils.TAG_SPAN) ||
509ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                tag.equals(TtmlUtils.TAG_BR) || tag.equals(TtmlUtils.TAG_STYLE) ||
510ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                tag.equals(TtmlUtils.TAG_STYLING) || tag.equals(TtmlUtils.TAG_LAYOUT) ||
511ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                tag.equals(TtmlUtils.TAG_REGION) || tag.equals(TtmlUtils.TAG_METADATA) ||
512ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                tag.equals(TtmlUtils.TAG_SMPTE_IMAGE) || tag.equals(TtmlUtils.TAG_SMPTE_DATA) ||
513ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                tag.equals(TtmlUtils.TAG_SMPTE_INFORMATION)) {
514ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            return true;
515ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
516ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        return false;
517ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
518ba3699b568e656910641b246b27448d92632a389Sungsoo Lim}
519ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
520ba3699b568e656910641b246b27448d92632a389Sungsoo Lim/** @hide */
521ba3699b568e656910641b246b27448d92632a389Sungsoo Liminterface TtmlNodeListener {
522ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    void onTtmlNodeParsed(TtmlNode node);
523ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    void onRootNodeParsed(TtmlNode node);
524ba3699b568e656910641b246b27448d92632a389Sungsoo Lim}
525ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
526ba3699b568e656910641b246b27448d92632a389Sungsoo Lim/** @hide */
527ba3699b568e656910641b246b27448d92632a389Sungsoo Limclass TtmlTrack extends SubtitleTrack implements TtmlNodeListener {
528ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private static final String TAG = "TtmlTrack";
529ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
530ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private final TtmlParser mParser = new TtmlParser(this);
531ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private final TtmlRenderingWidget mRenderingWidget;
532ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private String mParsingData;
533ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private Long mCurrentRunID;
534ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
535ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private final LinkedList<TtmlNode> mTtmlNodes;
536ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private final TreeSet<Long> mTimeEvents;
537ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private TtmlNode mRootNode;
538ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
539ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    TtmlTrack(TtmlRenderingWidget renderingWidget, MediaFormat format) {
540ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        super(format);
541ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
542ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mTtmlNodes = new LinkedList<TtmlNode>();
543ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mTimeEvents = new TreeSet<Long>();
544ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mRenderingWidget = renderingWidget;
545ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mParsingData = "";
546ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
547ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
548ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    @Override
549ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public TtmlRenderingWidget getRenderingWidget() {
550ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        return mRenderingWidget;
551ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
552ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
553ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    @Override
554079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang    public void onData(byte[] data, boolean eos, long runID) {
555079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang        try {
556079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang            // TODO: handle UTF-8 conversion properly
557079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang            String str = new String(data, "UTF-8");
558079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang
559079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang            // implement intermixing restriction for TTML.
560079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang            synchronized(mParser) {
561079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                if (mCurrentRunID != null && runID != mCurrentRunID) {
562079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                    throw new IllegalStateException(
563079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                            "Run #" + mCurrentRunID +
564079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                            " in progress.  Cannot process run #" + runID);
565079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                }
566079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                mCurrentRunID = runID;
567079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                mParsingData += str;
568079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                if (eos) {
569079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                    try {
570079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                        mParser.parse(mParsingData, mCurrentRunID);
571079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                    } catch (XmlPullParserException e) {
572079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                        e.printStackTrace();
573079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                    } catch (IOException e) {
574079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                        e.printStackTrace();
575079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                    }
576079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                    finishedRun(runID);
577079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                    mParsingData = "";
578079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                    mCurrentRunID = null;
579ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                }
580ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            }
581079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang        } catch (java.io.UnsupportedEncodingException e) {
582079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang            Log.w(TAG, "subtitle data is not UTF-8 encoded: " + e);
583ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
584ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
585ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
586ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    @Override
587ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public void onTtmlNodeParsed(TtmlNode node) {
588ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mTtmlNodes.addLast(node);
589ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        addTimeEvents(node);
590ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
591ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
592ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    @Override
593ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public void onRootNodeParsed(TtmlNode node) {
594ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mRootNode = node;
595ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        TtmlCue cue = null;
596ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        while ((cue = getNextResult()) != null) {
597ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            addCue(cue);
598ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
599ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mRootNode = null;
600ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mTtmlNodes.clear();
601ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mTimeEvents.clear();
602ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
603ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
604ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    @Override
605ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public void updateView(Vector<SubtitleTrack.Cue> activeCues) {
606ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        if (!mVisible) {
607ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            // don't keep the state if we are not visible
608ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            return;
609ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
610ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
611ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        if (DEBUG && mTimeProvider != null) {
612ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            try {
613ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                Log.d(TAG, "at " +
614ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                        (mTimeProvider.getCurrentTimeUs(false, true) / 1000) +
615ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                        " ms the active cues are:");
616ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            } catch (IllegalStateException e) {
617ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                Log.d(TAG, "at (illegal state) the active cues are:");
618ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            }
619ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
620ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
621ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mRenderingWidget.setActiveCues(activeCues);
622ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
623ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
624ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    /**
625ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * Returns a {@link TtmlCue} in the presentation time order.
626ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     * {@code null} is returned if there is no more timed text to show.
627ba3699b568e656910641b246b27448d92632a389Sungsoo Lim     */
628ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public TtmlCue getNextResult() {
629ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        while (mTimeEvents.size() >= 2) {
630ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            long start = mTimeEvents.pollFirst();
631ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            long end = mTimeEvents.first();
632ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            List<TtmlNode> activeCues = getActiveNodes(start, end);
633ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            if (!activeCues.isEmpty()) {
634ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                return new TtmlCue(start, end,
635ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                        TtmlUtils.applySpacePolicy(TtmlUtils.extractText(
636ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                                mRootNode, start, end), false),
637ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                        TtmlUtils.extractTtmlFragment(mRootNode, start, end));
638ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            }
639ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
640ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        return null;
641ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
642ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
643ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private void addTimeEvents(TtmlNode node) {
644ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mTimeEvents.add(node.mStartTimeMs);
645ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mTimeEvents.add(node.mEndTimeMs);
646ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        for (int i = 0; i < node.mChildren.size(); ++i) {
647ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            addTimeEvents(node.mChildren.get(i));
648ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
649ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
650ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
651ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private List<TtmlNode> getActiveNodes(long startTimeUs, long endTimeUs) {
652ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        List<TtmlNode> activeNodes = new ArrayList<TtmlNode>();
653ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        for (int i = 0; i < mTtmlNodes.size(); ++i) {
654ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            TtmlNode node = mTtmlNodes.get(i);
655ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            if (node.isActive(startTimeUs, endTimeUs)) {
656ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                activeNodes.add(node);
657ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            }
658ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
659ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        return activeNodes;
660ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
661ba3699b568e656910641b246b27448d92632a389Sungsoo Lim}
662ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
663ba3699b568e656910641b246b27448d92632a389Sungsoo Lim/**
664ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * Widget capable of rendering TTML captions.
665ba3699b568e656910641b246b27448d92632a389Sungsoo Lim *
666ba3699b568e656910641b246b27448d92632a389Sungsoo Lim * @hide
667ba3699b568e656910641b246b27448d92632a389Sungsoo Lim */
668ba3699b568e656910641b246b27448d92632a389Sungsoo Limclass TtmlRenderingWidget extends LinearLayout implements SubtitleTrack.RenderingWidget {
669ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
670ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    /** Callback for rendering changes. */
671ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private OnChangedListener mListener;
672ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    private final TextView mTextView;
673ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
674ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public TtmlRenderingWidget(Context context) {
675ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        this(context, null);
676ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
677ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
678ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public TtmlRenderingWidget(Context context, AttributeSet attrs) {
679ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        this(context, attrs, 0);
680ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
681ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
682ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public TtmlRenderingWidget(Context context, AttributeSet attrs, int defStyleAttr) {
683ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        this(context, attrs, defStyleAttr, 0);
684ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
685ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
686ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public TtmlRenderingWidget(Context context, AttributeSet attrs, int defStyleAttr,
687ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            int defStyleRes) {
688ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        super(context, attrs, defStyleAttr, defStyleRes);
689ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        // Cannot render text over video when layer type is hardware.
690ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
691ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
692ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        CaptioningManager captionManager = (CaptioningManager) context.getSystemService(
693ba3699b568e656910641b246b27448d92632a389Sungsoo Lim                Context.CAPTIONING_SERVICE);
694ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mTextView = new TextView(context);
695ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mTextView.setTextColor(captionManager.getUserStyle().foregroundColor);
696ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        addView(mTextView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
697ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mTextView.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
698ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
699ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
700ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    @Override
701ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public void setOnChangedListener(OnChangedListener listener) {
702ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mListener = listener;
703ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
704ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
705ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    @Override
706ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public void setSize(int width, int height) {
707ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        final int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
708ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        final int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
709ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
710ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        measure(widthSpec, heightSpec);
711ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        layout(0, 0, width, height);
712ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
713ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
714ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    @Override
715ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public void setVisible(boolean visible) {
716ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        if (visible) {
717ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            setVisibility(View.VISIBLE);
718ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        } else {
719ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            setVisibility(View.GONE);
720ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
721ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
722ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
723ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    @Override
724ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public void onAttachedToWindow() {
725ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        super.onAttachedToWindow();
726ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
727ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
728ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    @Override
729ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public void onDetachedFromWindow() {
730ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        super.onDetachedFromWindow();
731ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
732ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
733ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    public void setActiveCues(Vector<SubtitleTrack.Cue> activeCues) {
734ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        final int count = activeCues.size();
735ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        String subtitleText = "";
736ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        for (int i = 0; i < count; i++) {
737ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            TtmlCue cue = (TtmlCue) activeCues.get(i);
738ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            subtitleText += cue.mText + "\n";
739ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
740ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        mTextView.setText(subtitleText);
741ba3699b568e656910641b246b27448d92632a389Sungsoo Lim
742ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        if (mListener != null) {
743ba3699b568e656910641b246b27448d92632a389Sungsoo Lim            mListener.onChanged(this);
744ba3699b568e656910641b246b27448d92632a389Sungsoo Lim        }
745ba3699b568e656910641b246b27448d92632a389Sungsoo Lim    }
746ba3699b568e656910641b246b27448d92632a389Sungsoo Lim}
747