1d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette/*
2d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette * Copyright (C) 2013 The Android Open Source Project
3d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette *
4d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette * Licensed under the Apache License, Version 2.0 (the "License");
5d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette * you may not use this file except in compliance with the License.
6d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette * You may obtain a copy of the License at
7d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette *
8d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette *      http://www.apache.org/licenses/LICENSE-2.0
9d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette *
10d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette * Unless required by applicable law or agreed to in writing, software
11d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette * distributed under the License is distributed on an "AS IS" BASIS,
12d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette * See the License for the specific language governing permissions and
14d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette * limitations under the License.
15d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette */
16d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnarpackage android.media;
1876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
1976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnarimport android.content.Context;
207fe420f31b4de2bbd7a89194384d0c9ac6293226Alan Viveretteimport android.text.Layout.Alignment;
21d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viveretteimport android.text.SpannableStringBuilder;
22d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viveretteimport android.util.ArrayMap;
2376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnarimport android.util.AttributeSet;
2476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnarimport android.util.Log;
25d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viveretteimport android.view.Gravity;
2676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnarimport android.view.View;
27d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viveretteimport android.view.ViewGroup;
28d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viveretteimport android.view.accessibility.CaptioningManager;
29d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viveretteimport android.view.accessibility.CaptioningManager.CaptionStyle;
30d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viveretteimport android.view.accessibility.CaptioningManager.CaptioningChangeListener;
31d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viveretteimport android.widget.LinearLayout;
32d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
33d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viveretteimport com.android.internal.widget.SubtitleView;
3476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
35d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viveretteimport java.util.ArrayList;
3676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnarimport java.util.Arrays;
3776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnarimport java.util.HashMap;
3876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnarimport java.util.Map;
3976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnarimport java.util.Vector;
4076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
4176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar/** @hide */
4276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnarpublic class WebVttRenderer extends SubtitleController.Renderer {
43d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private final Context mContext;
4476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
45d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private WebVttRenderingWidget mRenderingWidget;
46d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
47d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    public WebVttRenderer(Context context) {
48d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        mContext = context;
4976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
5076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
5176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    @Override
5276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public boolean supports(MediaFormat format) {
5376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (format.containsKey(MediaFormat.KEY_MIME)) {
5476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            return format.getString(MediaFormat.KEY_MIME).equals("text/vtt");
5576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
5676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        return false;
5776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
5876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
5976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    @Override
6076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public SubtitleTrack createTrack(MediaFormat format) {
61d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        if (mRenderingWidget == null) {
62d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            mRenderingWidget = new WebVttRenderingWidget(mContext);
63d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
6476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
65d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        return new WebVttTrack(mRenderingWidget, format);
6676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
6776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar}
6876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
6976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar/** @hide */
7076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnarclass TextTrackCueSpan {
7176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    long mTimestampMs;
7276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    boolean mEnabled;
7376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    String mText;
7476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    TextTrackCueSpan(String text, long timestamp) {
7576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mTimestampMs = timestamp;
7676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mText = text;
7776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        // spans with timestamp will be enabled by Cue.onTime
7876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mEnabled = (mTimestampMs < 0);
7976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
8076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
8176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    @Override
8276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public boolean equals(Object o) {
8376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (!(o instanceof TextTrackCueSpan)) {
8476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            return false;
8576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
8676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        TextTrackCueSpan span = (TextTrackCueSpan) o;
8776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        return mTimestampMs == span.mTimestampMs &&
8876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mText.equals(span.mText);
8976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
9076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar}
9176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
9276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar/**
9376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar * @hide
9476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar *
9576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar * Extract all text without style, but with timestamp spans.
9676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar */
9776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnarclass UnstyledTextExtractor implements Tokenizer.OnTokenListener {
9876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    StringBuilder mLine = new StringBuilder();
9976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    Vector<TextTrackCueSpan[]> mLines = new Vector<TextTrackCueSpan[]>();
10076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    Vector<TextTrackCueSpan> mCurrentLine = new Vector<TextTrackCueSpan>();
10176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    long mLastTimestamp;
10276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
10376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    UnstyledTextExtractor() {
10476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        init();
10576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
10676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
10776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private void init() {
10876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mLine.delete(0, mLine.length());
10976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mLines.clear();
11076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mCurrentLine.clear();
11176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mLastTimestamp = -1;
11276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
11376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
11476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    @Override
11576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public void onData(String s) {
11676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mLine.append(s);
11776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
11876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
11976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    @Override
12076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public void onStart(String tag, String[] classes, String annotation) { }
12176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
12276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    @Override
12376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public void onEnd(String tag) { }
12476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
12576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    @Override
12676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public void onTimeStamp(long timestampMs) {
12776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        // finish any prior span
12876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (mLine.length() > 0 && timestampMs != mLastTimestamp) {
12976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mCurrentLine.add(
13076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    new TextTrackCueSpan(mLine.toString(), mLastTimestamp));
13176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mLine.delete(0, mLine.length());
13276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
13376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mLastTimestamp = timestampMs;
13476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
13576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
13676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    @Override
13776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public void onLineEnd() {
13876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        // finish any pending span
13976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (mLine.length() > 0) {
14076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mCurrentLine.add(
14176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    new TextTrackCueSpan(mLine.toString(), mLastTimestamp));
14276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mLine.delete(0, mLine.length());
14376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
14476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
14576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        TextTrackCueSpan[] spans = new TextTrackCueSpan[mCurrentLine.size()];
14676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mCurrentLine.toArray(spans);
14776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mCurrentLine.clear();
14876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mLines.add(spans);
14976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
15076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
15176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public TextTrackCueSpan[][] getText() {
15276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        // for politeness, finish last cue-line if it ends abruptly
15376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (mLine.length() > 0 || mCurrentLine.size() > 0) {
15476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            onLineEnd();
15576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
15676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        TextTrackCueSpan[][] lines = new TextTrackCueSpan[mLines.size()][];
15776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mLines.toArray(lines);
15876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        init();
15976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        return lines;
16076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
16176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar}
16276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
16376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar/**
16476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar * @hide
16576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar *
16676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar * Tokenizer tokenizes the WebVTT Cue Text into tags and data
16776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar */
16876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnarclass Tokenizer {
16976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private static final String TAG = "Tokenizer";
17076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private TokenizerPhase mPhase;
17176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private TokenizerPhase mDataTokenizer;
17276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private TokenizerPhase mTagTokenizer;
17376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
17476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private OnTokenListener mListener;
17576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private String mLine;
17676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private int mHandledLen;
17776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
17876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    interface TokenizerPhase {
17976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        TokenizerPhase start();
18076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        void tokenize();
18176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
18276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
18376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    class DataTokenizer implements TokenizerPhase {
18476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        // includes both WebVTT data && escape state
18576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        private StringBuilder mData;
18676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
18776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        public TokenizerPhase start() {
18876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mData = new StringBuilder();
18976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            return this;
19076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
19176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
19276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        private boolean replaceEscape(String escape, String replacement, int pos) {
19376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            if (mLine.startsWith(escape, pos)) {
19476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mData.append(mLine.substring(mHandledLen, pos));
19576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mData.append(replacement);
19676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mHandledLen = pos + escape.length();
19776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                pos = mHandledLen - 1;
19876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                return true;
19976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
20076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            return false;
20176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
20276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
20376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        @Override
20476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        public void tokenize() {
20576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            int end = mLine.length();
20676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            for (int pos = mHandledLen; pos < mLine.length(); pos++) {
20776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                if (mLine.charAt(pos) == '&') {
20876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    if (replaceEscape("&amp;", "&", pos) ||
20976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                            replaceEscape("&lt;", "<", pos) ||
21076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                            replaceEscape("&gt;", ">", pos) ||
21176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                            replaceEscape("&lrm;", "\u200e", pos) ||
21276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                            replaceEscape("&rlm;", "\u200f", pos) ||
21376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                            replaceEscape("&nbsp;", "\u00a0", pos)) {
21476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        continue;
21576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    }
21676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                } else if (mLine.charAt(pos) == '<') {
21776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    end = pos;
21876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    mPhase = mTagTokenizer.start();
21976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    break;
22076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
22176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
22276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mData.append(mLine.substring(mHandledLen, end));
22376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            // yield mData
22476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mListener.onData(mData.toString());
22576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mData.delete(0, mData.length());
22676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mHandledLen = end;
22776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
22876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
22976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
23076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    class TagTokenizer implements TokenizerPhase {
23176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        private boolean mAtAnnotation;
23276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        private String mName, mAnnotation;
23376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
23476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        public TokenizerPhase start() {
23576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mName = mAnnotation = "";
23676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mAtAnnotation = false;
23776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            return this;
23876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
23976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
24076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        @Override
24176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        public void tokenize() {
24276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            if (!mAtAnnotation)
24376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mHandledLen++;
24476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            if (mHandledLen < mLine.length()) {
24576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                String[] parts;
24676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                /**
24776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                 * Collect annotations and end-tags to closing >.  Collect tag
24876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                 * name to closing bracket or next white-space.
24976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                 */
25076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                if (mAtAnnotation || mLine.charAt(mHandledLen) == '/') {
25176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    parts = mLine.substring(mHandledLen).split(">");
25276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                } else {
25376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    parts = mLine.substring(mHandledLen).split("[\t\f >]");
25476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
25576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                String part = mLine.substring(
25676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                            mHandledLen, mHandledLen + parts[0].length());
25776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mHandledLen += parts[0].length();
25876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
25976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                if (mAtAnnotation) {
26076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    mAnnotation += " " + part;
26176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                } else {
26276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    mName = part;
26376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
26476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
26576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
26676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mAtAnnotation = true;
26776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
26876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            if (mHandledLen < mLine.length() && mLine.charAt(mHandledLen) == '>') {
26976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                yield_tag();
27076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mPhase = mDataTokenizer.start();
27176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mHandledLen++;
27276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
27376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
27476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
27576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        private void yield_tag() {
27676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            if (mName.startsWith("/")) {
27776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mListener.onEnd(mName.substring(1));
27876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            } else if (mName.length() > 0 && Character.isDigit(mName.charAt(0))) {
27976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                // timestamp
28076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                try {
28176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    long timestampMs = WebVttParser.parseTimestampMs(mName);
28276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    mListener.onTimeStamp(timestampMs);
28376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                } catch (NumberFormatException e) {
28476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    Log.d(TAG, "invalid timestamp tag: <" + mName + ">");
28576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
28676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            } else {
28776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mAnnotation = mAnnotation.replaceAll("\\s+", " ");
28876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                if (mAnnotation.startsWith(" ")) {
28976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    mAnnotation = mAnnotation.substring(1);
29076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
29176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                if (mAnnotation.endsWith(" ")) {
29276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    mAnnotation = mAnnotation.substring(0, mAnnotation.length() - 1);
29376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
29476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
29576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                String[] classes = null;
29676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                int dotAt = mName.indexOf('.');
29776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                if (dotAt >= 0) {
29876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    classes = mName.substring(dotAt + 1).split("\\.");
29976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    mName = mName.substring(0, dotAt);
30076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
30176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mListener.onStart(mName, classes, mAnnotation);
30276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
30376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
30476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
30576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
30676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    Tokenizer(OnTokenListener listener) {
30776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mDataTokenizer = new DataTokenizer();
30876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mTagTokenizer = new TagTokenizer();
30976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        reset();
31076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mListener = listener;
31176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
31276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
31376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    void reset() {
31476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mPhase = mDataTokenizer.start();
31576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
31676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
31776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    void tokenize(String s) {
31876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mHandledLen = 0;
31976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mLine = s;
32076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        while (mHandledLen < mLine.length()) {
32176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mPhase.tokenize();
32276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
32376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        /* we are finished with a line unless we are in the middle of a tag */
32476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (!(mPhase instanceof TagTokenizer)) {
32576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            // yield END-OF-LINE
32676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mListener.onLineEnd();
32776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
32876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
32976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
33076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    interface OnTokenListener {
33176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        void onData(String s);
33276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        void onStart(String tag, String[] classes, String annotation);
33376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        void onEnd(String tag);
33476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        void onTimeStamp(long timestampMs);
33576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        void onLineEnd();
33676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
33776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar}
33876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
33976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar/** @hide */
34076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnarclass TextTrackRegion {
34176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    final static int SCROLL_VALUE_NONE      = 300;
34276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    final static int SCROLL_VALUE_SCROLL_UP = 301;
34376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
34476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    String mId;
34576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    float mWidth;
34676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    int mLines;
34776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    float mAnchorPointX, mAnchorPointY;
34876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    float mViewportAnchorPointX, mViewportAnchorPointY;
34976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    int mScrollValue;
35076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
35176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    TextTrackRegion() {
35276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mId = "";
35376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mWidth = 100;
35476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mLines = 3;
35576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mAnchorPointX = mViewportAnchorPointX = 0.f;
35676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mAnchorPointY = mViewportAnchorPointY = 100.f;
35776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mScrollValue = SCROLL_VALUE_NONE;
35876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
35976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
36076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public String toString() {
36176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        StringBuilder res = new StringBuilder(" {id:\"").append(mId)
36276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            .append("\", width:").append(mWidth)
36376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            .append(", lines:").append(mLines)
36476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            .append(", anchorPoint:(").append(mAnchorPointX)
36576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            .append(", ").append(mAnchorPointY)
36676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            .append("), viewportAnchorPoints:").append(mViewportAnchorPointX)
36776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            .append(", ").append(mViewportAnchorPointY)
36876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            .append("), scrollValue:")
36976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            .append(mScrollValue == SCROLL_VALUE_NONE ? "none" :
37076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    mScrollValue == SCROLL_VALUE_SCROLL_UP ? "scroll_up" :
37176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    "INVALID")
37276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            .append("}");
37376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        return res.toString();
37476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
37576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar}
37676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
37776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar/** @hide */
37876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnarclass TextTrackCue extends SubtitleTrack.Cue {
37976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    final static int WRITING_DIRECTION_HORIZONTAL  = 100;
38076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    final static int WRITING_DIRECTION_VERTICAL_RL = 101;
38176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    final static int WRITING_DIRECTION_VERTICAL_LR = 102;
38276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
38376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    final static int ALIGNMENT_MIDDLE = 200;
38476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    final static int ALIGNMENT_START  = 201;
38576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    final static int ALIGNMENT_END    = 202;
38676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    final static int ALIGNMENT_LEFT   = 203;
38776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    final static int ALIGNMENT_RIGHT  = 204;
38876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private static final String TAG = "TTCue";
38976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
39076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    String  mId;
39176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    boolean mPauseOnExit;
39276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    int     mWritingDirection;
39376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    String  mRegionId;
39476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    boolean mSnapToLines;
39576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    Integer mLinePosition;  // null means AUTO
39676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    boolean mAutoLinePosition;
39776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    int     mTextPosition;
39876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    int     mSize;
39976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    int     mAlignment;
40076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    // Vector<String> mText;
40176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    String[] mStrings;
40276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    TextTrackCueSpan[][] mLines;
40376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    TextTrackRegion mRegion;
40476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
40576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    TextTrackCue() {
40676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mId = "";
40776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mPauseOnExit = false;
40876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mWritingDirection = WRITING_DIRECTION_HORIZONTAL;
40976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mRegionId = "";
41076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mSnapToLines = true;
41176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mLinePosition = null /* AUTO */;
41276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mTextPosition = 50;
41376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mSize = 100;
41476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mAlignment = ALIGNMENT_MIDDLE;
41576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mLines = null;
41676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mRegion = null;
41776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
41876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
41976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    @Override
42076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public boolean equals(Object o) {
42176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (!(o instanceof TextTrackCue)) {
42276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            return false;
42376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
42476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (this == o) {
42576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            return true;
42676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
42776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
42876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        try {
42976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            TextTrackCue cue = (TextTrackCue) o;
43076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            boolean res = mId.equals(cue.mId) &&
43176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    mPauseOnExit == cue.mPauseOnExit &&
43276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    mWritingDirection == cue.mWritingDirection &&
43376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    mRegionId.equals(cue.mRegionId) &&
43476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    mSnapToLines == cue.mSnapToLines &&
43576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    mAutoLinePosition == cue.mAutoLinePosition &&
4365c5978985a3457539d1f3b611f23d26b77484536Andreas Gampe                    (mAutoLinePosition ||
4375c5978985a3457539d1f3b611f23d26b77484536Andreas Gampe                            ((mLinePosition != null && mLinePosition.equals(cue.mLinePosition)) ||
4385c5978985a3457539d1f3b611f23d26b77484536Andreas Gampe                             (mLinePosition == null && cue.mLinePosition == null))) &&
43976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    mTextPosition == cue.mTextPosition &&
44076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    mSize == cue.mSize &&
44176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    mAlignment == cue.mAlignment &&
44276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    mLines.length == cue.mLines.length;
44376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            if (res == true) {
44476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                for (int line = 0; line < mLines.length; line++) {
44576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    if (!Arrays.equals(mLines[line], cue.mLines[line])) {
44676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        return false;
44776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    }
44876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
44976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
45076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            return res;
45176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        } catch(IncompatibleClassChangeError e) {
45276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            return false;
45376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
45476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
45576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
45676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public StringBuilder appendStringsToBuilder(StringBuilder builder) {
45776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (mStrings == null) {
45876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            builder.append("null");
45976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        } else {
46076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            builder.append("[");
46176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            boolean first = true;
46276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            for (String s: mStrings) {
46376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                if (!first) {
46476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    builder.append(", ");
46576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
46676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                if (s == null) {
46776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    builder.append("null");
46876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                } else {
46976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    builder.append("\"");
47076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    builder.append(s);
47176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    builder.append("\"");
47276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
47376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                first = false;
47476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
47576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            builder.append("]");
47676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
47776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        return builder;
47876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
47976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
48076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public StringBuilder appendLinesToBuilder(StringBuilder builder) {
48176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (mLines == null) {
48276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            builder.append("null");
48376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        } else {
48476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            builder.append("[");
48576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            boolean first = true;
48676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            for (TextTrackCueSpan[] spans: mLines) {
48776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                if (!first) {
48876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    builder.append(", ");
48976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
49076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                if (spans == null) {
49176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    builder.append("null");
49276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                } else {
49376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    builder.append("\"");
49476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    boolean innerFirst = true;
49576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    long lastTimestamp = -1;
49676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    for (TextTrackCueSpan span: spans) {
49776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        if (!innerFirst) {
49876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                            builder.append(" ");
49976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        }
50076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        if (span.mTimestampMs != lastTimestamp) {
50176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                            builder.append("<")
50276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                                    .append(WebVttParser.timeToString(
50376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                                            span.mTimestampMs))
50476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                                    .append(">");
50576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                            lastTimestamp = span.mTimestampMs;
50676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        }
50776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        builder.append(span.mText);
50876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        innerFirst = false;
50976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    }
51076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    builder.append("\"");
51176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
51276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                first = false;
51376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
51476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            builder.append("]");
51576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
51676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        return builder;
51776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
51876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
51976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public String toString() {
52076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        StringBuilder res = new StringBuilder();
52176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
52276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        res.append(WebVttParser.timeToString(mStartTimeMs))
52376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                .append(" --> ").append(WebVttParser.timeToString(mEndTimeMs))
52476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                .append(" {id:\"").append(mId)
52576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                .append("\", pauseOnExit:").append(mPauseOnExit)
52676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                .append(", direction:")
52776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                .append(mWritingDirection == WRITING_DIRECTION_HORIZONTAL ? "horizontal" :
52876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        mWritingDirection == WRITING_DIRECTION_VERTICAL_LR ? "vertical_lr" :
52976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        mWritingDirection == WRITING_DIRECTION_VERTICAL_RL ? "vertical_rl" :
53076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        "INVALID")
53176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                .append(", regionId:\"").append(mRegionId)
53276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                .append("\", snapToLines:").append(mSnapToLines)
53376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                .append(", linePosition:").append(mAutoLinePosition ? "auto" :
53476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                                                  mLinePosition)
53576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                .append(", textPosition:").append(mTextPosition)
53676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                .append(", size:").append(mSize)
53776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                .append(", alignment:")
53876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                .append(mAlignment == ALIGNMENT_END ? "end" :
53976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        mAlignment == ALIGNMENT_LEFT ? "left" :
54076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        mAlignment == ALIGNMENT_MIDDLE ? "middle" :
54176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        mAlignment == ALIGNMENT_RIGHT ? "right" :
54276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        mAlignment == ALIGNMENT_START ? "start" : "INVALID")
54376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                .append(", text:");
54476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        appendStringsToBuilder(res).append("}");
54576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        return res.toString();
54676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
54776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
54876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    @Override
54976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public int hashCode() {
55076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        return toString().hashCode();
55176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
55276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
55376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    @Override
55476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public void onTime(long timeMs) {
55576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        for (TextTrackCueSpan[] line: mLines) {
55676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            for (TextTrackCueSpan span: line) {
55776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                span.mEnabled = timeMs >= span.mTimestampMs;
55876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
55976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
56076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
56176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar}
56276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
56372bbe6cca6470e86b56081c3fbe3cbb4360f5271Lajos Molnar/**
56472bbe6cca6470e86b56081c3fbe3cbb4360f5271Lajos Molnar *  Supporting July 10 2013 draft version
56572bbe6cca6470e86b56081c3fbe3cbb4360f5271Lajos Molnar *
56672bbe6cca6470e86b56081c3fbe3cbb4360f5271Lajos Molnar *  @hide
56772bbe6cca6470e86b56081c3fbe3cbb4360f5271Lajos Molnar */
56876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnarclass WebVttParser {
56976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private static final String TAG = "WebVttParser";
57076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private Phase mPhase;
57176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private TextTrackCue mCue;
57276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private Vector<String> mCueTexts;
57376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private WebVttCueListener mListener;
57476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private String mBuffer;
57576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
57676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    WebVttParser(WebVttCueListener listener) {
57776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mPhase = mParseStart;
57876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mBuffer = "";   /* mBuffer contains up to 1 incomplete line */
57976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mListener = listener;
58076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mCueTexts = new Vector<String>();
58176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
58276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
58376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    /* parsePercentageString */
58476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public static float parseFloatPercentage(String s)
58576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            throws NumberFormatException {
58676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (!s.endsWith("%")) {
58776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            throw new NumberFormatException("does not end in %");
58876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
58976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        s = s.substring(0, s.length() - 1);
59076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        // parseFloat allows an exponent or a sign
59176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (s.matches(".*[^0-9.].*")) {
59276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            throw new NumberFormatException("contains an invalid character");
59376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
59476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
59576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        try {
59676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            float value = Float.parseFloat(s);
59776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            if (value < 0.0f || value > 100.0f) {
59876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                throw new NumberFormatException("is out of range");
59976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
60076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            return value;
60176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        } catch (NumberFormatException e) {
60276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            throw new NumberFormatException("is not a number");
60376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
60476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
60576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
60676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public static int parseIntPercentage(String s) throws NumberFormatException {
60776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (!s.endsWith("%")) {
60876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            throw new NumberFormatException("does not end in %");
60976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
61076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        s = s.substring(0, s.length() - 1);
61176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        // parseInt allows "-0" that returns 0, so check for non-digits
61276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (s.matches(".*[^0-9].*")) {
61376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            throw new NumberFormatException("contains an invalid character");
61476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
61576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
61676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        try {
61776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            int value = Integer.parseInt(s);
61876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            if (value < 0 || value > 100) {
61976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                throw new NumberFormatException("is out of range");
62076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
62176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            return value;
62276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        } catch (NumberFormatException e) {
62376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            throw new NumberFormatException("is not a number");
62476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
62576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
62676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
62776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public static long parseTimestampMs(String s) throws NumberFormatException {
62876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (!s.matches("(\\d+:)?[0-5]\\d:[0-5]\\d\\.\\d{3}")) {
62976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            throw new NumberFormatException("has invalid format");
63076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
63176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
63276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        String[] parts = s.split("\\.", 2);
63376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        long value = 0;
63476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        for (String group: parts[0].split(":")) {
63576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            value = value * 60 + Long.parseLong(group);
63676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
63776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        return value * 1000 + Long.parseLong(parts[1]);
63876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
63976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
64076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public static String timeToString(long timeMs) {
64176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        return String.format("%d:%02d:%02d.%03d",
64276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                timeMs / 3600000, (timeMs / 60000) % 60,
64376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                (timeMs / 1000) % 60, timeMs % 1000);
64476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
64576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
64676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public void parse(String s) {
64776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        boolean trailingCR = false;
64876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mBuffer = (mBuffer + s.replace("\0", "\ufffd")).replace("\r\n", "\n");
64976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
65076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        /* keep trailing '\r' in case matching '\n' arrives in next packet */
65176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (mBuffer.endsWith("\r")) {
65276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            trailingCR = true;
65376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mBuffer = mBuffer.substring(0, mBuffer.length() - 1);
65476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
65576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
65676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        String[] lines = mBuffer.split("[\r\n]");
65776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        for (int i = 0; i < lines.length - 1; i++) {
65876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mPhase.parse(lines[i]);
65976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
66076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
66176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mBuffer = lines[lines.length - 1];
66276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (trailingCR)
66376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mBuffer += "\r";
66476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
66576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
66676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public void eos() {
66776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (mBuffer.endsWith("\r")) {
66876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mBuffer = mBuffer.substring(0, mBuffer.length() - 1);
66976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
67076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
67176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mPhase.parse(mBuffer);
67276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mBuffer = "";
67376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
67476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        yieldCue();
67576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mPhase = mParseStart;
67676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
67776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
67876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public void yieldCue() {
67976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (mCue != null && mCueTexts.size() > 0) {
68076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mCue.mStrings = new String[mCueTexts.size()];
68176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mCueTexts.toArray(mCue.mStrings);
68276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mCueTexts.clear();
68376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mListener.onCueParsed(mCue);
68476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
68576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        mCue = null;
68676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
68776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
68876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    interface Phase {
68976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        void parse(String line);
69076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
69176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
69276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    final private Phase mSkipRest = new Phase() {
69376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        @Override
69476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        public void parse(String line) { }
69576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    };
69676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
69776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    final private Phase mParseStart = new Phase() { // 5-9
69876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        @Override
69976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        public void parse(String line) {
700283fe37aa20d6f5910ed1c86847c6228e151e1ffLajos Molnar            if (line.startsWith("\ufeff")) {
701283fe37aa20d6f5910ed1c86847c6228e151e1ffLajos Molnar                line = line.substring(1);
702283fe37aa20d6f5910ed1c86847c6228e151e1ffLajos Molnar            }
70376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            if (!line.equals("WEBVTT") &&
70476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    !line.startsWith("WEBVTT ") &&
70576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    !line.startsWith("WEBVTT\t")) {
70676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                log_warning("Not a WEBVTT header", line);
70776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mPhase = mSkipRest;
70876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            } else {
70976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mPhase = mParseHeader;
71076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
71176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
71276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    };
71376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
71476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    final private Phase mParseHeader = new Phase() { // 10-13
71576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        TextTrackRegion parseRegion(String s) {
71676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            TextTrackRegion region = new TextTrackRegion();
71776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            for (String setting: s.split(" +")) {
71876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                int equalAt = setting.indexOf('=');
71976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                if (equalAt <= 0 || equalAt == setting.length() - 1) {
72076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    continue;
72176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
72276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
72376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                String name = setting.substring(0, equalAt);
72476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                String value = setting.substring(equalAt + 1);
72576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                if (name.equals("id")) {
72676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    region.mId = value;
72776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                } else if (name.equals("width")) {
72876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    try {
72976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        region.mWidth = parseFloatPercentage(value);
73076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    } catch (NumberFormatException e) {
73176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        log_warning("region setting", name,
73276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                                "has invalid value", e.getMessage(), value);
73376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    }
73476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                } else if (name.equals("lines")) {
73572bbe6cca6470e86b56081c3fbe3cbb4360f5271Lajos Molnar                    if (value.matches(".*[^0-9].*")) {
73672bbe6cca6470e86b56081c3fbe3cbb4360f5271Lajos Molnar                        log_warning("lines", name, "contains an invalid character", value);
73772bbe6cca6470e86b56081c3fbe3cbb4360f5271Lajos Molnar                    } else {
73872bbe6cca6470e86b56081c3fbe3cbb4360f5271Lajos Molnar                        try {
73972bbe6cca6470e86b56081c3fbe3cbb4360f5271Lajos Molnar                            region.mLines = Integer.parseInt(value);
74072bbe6cca6470e86b56081c3fbe3cbb4360f5271Lajos Molnar                            assert(region.mLines >= 0); // lines contains only digits
74172bbe6cca6470e86b56081c3fbe3cbb4360f5271Lajos Molnar                        } catch (NumberFormatException e) {
74272bbe6cca6470e86b56081c3fbe3cbb4360f5271Lajos Molnar                            log_warning("region setting", name, "is not numeric", value);
74376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        }
74476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    }
74576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                } else if (name.equals("regionanchor") ||
74676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                           name.equals("viewportanchor")) {
74776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    int commaAt = value.indexOf(",");
74876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    if (commaAt < 0) {
74976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        log_warning("region setting", name, "contains no comma", value);
75076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        continue;
75176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    }
75276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
75376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    String anchorX = value.substring(0, commaAt);
75476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    String anchorY = value.substring(commaAt + 1);
75576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    float x, y;
75676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
75776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    try {
75876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        x = parseFloatPercentage(anchorX);
75976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    } catch (NumberFormatException e) {
76076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        log_warning("region setting", name,
76176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                                "has invalid x component", e.getMessage(), anchorX);
76276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        continue;
76376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    }
76476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    try {
76576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        y = parseFloatPercentage(anchorY);
76676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    } catch (NumberFormatException e) {
76776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        log_warning("region setting", name,
76876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                                "has invalid y component", e.getMessage(), anchorY);
76976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        continue;
77076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    }
77176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
77276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    if (name.charAt(0) == 'r') {
77376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        region.mAnchorPointX = x;
77476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        region.mAnchorPointY = y;
77576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    } else {
77676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        region.mViewportAnchorPointX = x;
77776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        region.mViewportAnchorPointY = y;
77876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    }
77976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                } else if (name.equals("scroll")) {
78076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    if (value.equals("up")) {
78176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        region.mScrollValue =
78276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                            TextTrackRegion.SCROLL_VALUE_SCROLL_UP;
78376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    } else {
78476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        log_warning("region setting", name, "has invalid value", value);
78576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    }
78676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
78776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
78876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            return region;
78976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
79076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
79176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        @Override
79276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        public void parse(String line)  {
79376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            if (line.length() == 0) {
79476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mPhase = mParseCueId;
79576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            } else if (line.contains("-->")) {
79676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mPhase = mParseCueTime;
79776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mPhase.parse(line);
79876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            } else {
79976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                int colonAt = line.indexOf(':');
80076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                if (colonAt <= 0 || colonAt >= line.length() - 1) {
80176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    log_warning("meta data header has invalid format", line);
80276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
80376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                String name = line.substring(0, colonAt);
80476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                String value = line.substring(colonAt + 1);
80576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
80676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                if (name.equals("Region")) {
80776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    TextTrackRegion region = parseRegion(value);
80876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    mListener.onRegionParsed(region);
80976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
81076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
81176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
81276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    };
81376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
81476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    final private Phase mParseCueId = new Phase() {
81576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        @Override
81676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        public void parse(String line) {
81776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            if (line.length() == 0) {
81876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                return;
81976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
82076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
82176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            assert(mCue == null);
82276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
82376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            if (line.equals("NOTE") || line.startsWith("NOTE ")) {
82476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mPhase = mParseCueText;
82576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
82676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
82776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mCue = new TextTrackCue();
82876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mCueTexts.clear();
82976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
83076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mPhase = mParseCueTime;
83176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            if (line.contains("-->")) {
83276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mPhase.parse(line);
83376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            } else {
83476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mCue.mId = line;
83576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
83676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
83776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    };
83876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
83976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    final private Phase mParseCueTime = new Phase() {
84076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        @Override
84176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        public void parse(String line) {
84276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            int arrowAt = line.indexOf("-->");
84376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            if (arrowAt < 0) {
84476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mCue = null;
84576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mPhase = mParseCueId;
84676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                return;
84776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
84876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
84976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            String start = line.substring(0, arrowAt).trim();
85076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            // convert only initial and first other white-space to space
85176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            String rest = line.substring(arrowAt + 3)
85276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    .replaceFirst("^\\s+", "").replaceFirst("\\s+", " ");
85376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            int spaceAt = rest.indexOf(' ');
85476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            String end = spaceAt > 0 ? rest.substring(0, spaceAt) : rest;
85576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            rest = spaceAt > 0 ? rest.substring(spaceAt + 1) : "";
85676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
85776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mCue.mStartTimeMs = parseTimestampMs(start);
85876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mCue.mEndTimeMs = parseTimestampMs(end);
85976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            for (String setting: rest.split(" +")) {
86076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                int colonAt = setting.indexOf(':');
86176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                if (colonAt <= 0 || colonAt == setting.length() - 1) {
86276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    continue;
86376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
86476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                String name = setting.substring(0, colonAt);
86576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                String value = setting.substring(colonAt + 1);
86676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
86776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                if (name.equals("region")) {
86876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    mCue.mRegionId = value;
86976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                } else if (name.equals("vertical")) {
87076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    if (value.equals("rl")) {
87176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        mCue.mWritingDirection =
87276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                            TextTrackCue.WRITING_DIRECTION_VERTICAL_RL;
87376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    } else if (value.equals("lr")) {
87476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        mCue.mWritingDirection =
87576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                            TextTrackCue.WRITING_DIRECTION_VERTICAL_LR;
87676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    } else {
87776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        log_warning("cue setting", name, "has invalid value", value);
87876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    }
87976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                } else if (name.equals("line")) {
88076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    try {
88176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        /* TRICKY: we know that there are no spaces in value */
88276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        assert(value.indexOf(' ') < 0);
88376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        if (value.endsWith("%")) {
88476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                            mCue.mSnapToLines = false;
88572bbe6cca6470e86b56081c3fbe3cbb4360f5271Lajos Molnar                            mCue.mLinePosition = parseIntPercentage(value);
88672bbe6cca6470e86b56081c3fbe3cbb4360f5271Lajos Molnar                        } else if (value.matches(".*[^0-9].*")) {
88772bbe6cca6470e86b56081c3fbe3cbb4360f5271Lajos Molnar                            log_warning("cue setting", name,
88872bbe6cca6470e86b56081c3fbe3cbb4360f5271Lajos Molnar                                    "contains an invalid character", value);
88976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        } else {
89076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                            mCue.mSnapToLines = true;
89176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                            mCue.mLinePosition = Integer.parseInt(value);
89276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        }
89376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    } catch (NumberFormatException e) {
89476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        log_warning("cue setting", name,
89572bbe6cca6470e86b56081c3fbe3cbb4360f5271Lajos Molnar                                "is not numeric or percentage", value);
89676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    }
89772bbe6cca6470e86b56081c3fbe3cbb4360f5271Lajos Molnar                    // TODO: add support for optional alignment value [,start|middle|end]
89876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                } else if (name.equals("position")) {
89976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    try {
90076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        mCue.mTextPosition = parseIntPercentage(value);
90176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    } catch (NumberFormatException e) {
90276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        log_warning("cue setting", name,
90376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                               "is not numeric or percentage", value);
90476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    }
90576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                } else if (name.equals("size")) {
90676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    try {
90776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        mCue.mSize = parseIntPercentage(value);
90876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    } catch (NumberFormatException e) {
90976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        log_warning("cue setting", name,
91076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                               "is not numeric or percentage", value);
91176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    }
91276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                } else if (name.equals("align")) {
91376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    if (value.equals("start")) {
91476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        mCue.mAlignment = TextTrackCue.ALIGNMENT_START;
91576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    } else if (value.equals("middle")) {
91676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        mCue.mAlignment = TextTrackCue.ALIGNMENT_MIDDLE;
91776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    } else if (value.equals("end")) {
91876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        mCue.mAlignment = TextTrackCue.ALIGNMENT_END;
91976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    } else if (value.equals("left")) {
92076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        mCue.mAlignment = TextTrackCue.ALIGNMENT_LEFT;
92176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    } else if (value.equals("right")) {
92276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        mCue.mAlignment = TextTrackCue.ALIGNMENT_RIGHT;
92376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    } else {
92476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        log_warning("cue setting", name, "has invalid value", value);
92576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        continue;
92676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    }
92776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
92876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
92976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
93076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            if (mCue.mLinePosition != null ||
93176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    mCue.mSize != 100 ||
93276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    (mCue.mWritingDirection !=
93376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        TextTrackCue.WRITING_DIRECTION_HORIZONTAL)) {
93476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mCue.mRegionId = "";
93576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
93676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
93776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mPhase = mParseCueText;
93876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
93976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    };
94076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
94176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    /* also used for notes */
94276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    final private Phase mParseCueText = new Phase() {
94376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        @Override
94476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        public void parse(String line) {
94576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            if (line.length() == 0) {
94676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                yieldCue();
94776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mPhase = mParseCueId;
94876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                return;
94976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            } else if (mCue != null) {
95076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mCueTexts.add(line);
95176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
95276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
95376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    };
95476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
95576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private void log_warning(
95676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            String nameType, String name, String message,
95776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            String subMessage, String value) {
95876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        Log.w(this.getClass().getName(), nameType + " '" + name + "' " +
95976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                message + " ('" + value + "' " + subMessage + ")");
96076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
96176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
96276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private void log_warning(
96376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            String nameType, String name, String message, String value) {
96476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        Log.w(this.getClass().getName(), nameType + " '" + name + "' " +
96576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                message + " ('" + value + "')");
96676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
96776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
96876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private void log_warning(String message, String value) {
96976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        Log.w(this.getClass().getName(), message + " ('" + value + "')");
97076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
97176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar}
97276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
97376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar/** @hide */
97476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnarinterface WebVttCueListener {
97576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    void onCueParsed(TextTrackCue cue);
97676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    void onRegionParsed(TextTrackRegion region);
97776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar}
97876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
97976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar/** @hide */
98076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnarclass WebVttTrack extends SubtitleTrack implements WebVttCueListener {
98176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private static final String TAG = "WebVttTrack";
98276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
98376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private final WebVttParser mParser = new WebVttParser(this);
98476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private final UnstyledTextExtractor mExtractor =
98576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        new UnstyledTextExtractor();
98676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private final Tokenizer mTokenizer = new Tokenizer(mExtractor);
98776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private final Vector<Long> mTimestamps = new Vector<Long>();
988d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private final WebVttRenderingWidget mRenderingWidget;
98976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
99076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private final Map<String, TextTrackRegion> mRegions =
99176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        new HashMap<String, TextTrackRegion>();
99276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    private Long mCurrentRunID;
99376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
994d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    WebVttTrack(WebVttRenderingWidget renderingWidget, MediaFormat format) {
99576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        super(format);
996d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
997d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        mRenderingWidget = renderingWidget;
99876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
99976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
100076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    @Override
1001d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    public WebVttRenderingWidget getRenderingWidget() {
1002d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        return mRenderingWidget;
100376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
100476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
100576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    @Override
1006079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang    public void onData(byte[] data, boolean eos, long runID) {
1007079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang        try {
1008079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang            String str = new String(data, "UTF-8");
1009079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang
1010079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang            // implement intermixing restriction for WebVTT only for now
1011079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang            synchronized(mParser) {
1012079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                if (mCurrentRunID != null && runID != mCurrentRunID) {
1013079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                    throw new IllegalStateException(
1014079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                            "Run #" + mCurrentRunID +
1015079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                            " in progress.  Cannot process run #" + runID);
1016079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                }
1017079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                mCurrentRunID = runID;
1018079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                mParser.parse(str);
1019079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                if (eos) {
1020079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                    finishedRun(runID);
1021079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                    mParser.eos();
1022079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                    mRegions.clear();
1023079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                    mCurrentRunID = null;
1024079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang                }
102576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
1026079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang        } catch (java.io.UnsupportedEncodingException e) {
1027079fa9683dc062f154333a661f5e84a5ac5e43c7Chong Zhang            Log.w(TAG, "subtitle data is not UTF-8 encoded: " + e);
102876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
102976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
103076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
103176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    @Override
103276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public void onCueParsed(TextTrackCue cue) {
103376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        synchronized (mParser) {
103476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            // resolve region
103576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            if (cue.mRegionId.length() != 0) {
103676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                cue.mRegion = mRegions.get(cue.mRegionId);
103776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
103876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
103976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            if (DEBUG) Log.v(TAG, "adding cue " + cue);
104076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
104176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            // tokenize text track string-lines into lines of spans
104276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mTokenizer.reset();
104376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            for (String s: cue.mStrings) {
104476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mTokenizer.tokenize(s);
104576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
104676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            cue.mLines = mExtractor.getText();
104776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            if (DEBUG) Log.v(TAG, cue.appendLinesToBuilder(
104876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    cue.appendStringsToBuilder(
104976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        new StringBuilder()).append(" simplified to: "))
105076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                            .toString());
105176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
105276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            // extract inner timestamps
105376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            for (TextTrackCueSpan[] line: cue.mLines) {
105476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                for (TextTrackCueSpan span: line) {
105576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    if (span.mTimestampMs > cue.mStartTimeMs &&
105676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                            span.mTimestampMs < cue.mEndTimeMs &&
105776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                            !mTimestamps.contains(span.mTimestampMs)) {
105876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        mTimestamps.add(span.mTimestampMs);
105976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    }
106076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
106176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
106276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
106376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            if (mTimestamps.size() > 0) {
106476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                cue.mInnerTimesMs = new long[mTimestamps.size()];
106576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                for (int ix=0; ix < mTimestamps.size(); ++ix) {
106676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                    cue.mInnerTimesMs[ix] = mTimestamps.get(ix);
106776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
106876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                mTimestamps.clear();
106976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            } else {
107076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                cue.mInnerTimesMs = null;
107176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
107276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
107376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            cue.mRunID = mCurrentRunID;
107476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
107576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
107676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        addCue(cue);
107776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
107876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
107976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    @Override
108076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public void onRegionParsed(TextTrackRegion region) {
108176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        synchronized(mParser) {
108276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            mRegions.put(region.mId, region);
108376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
108476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
108576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
1086d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    @Override
108776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    public void updateView(Vector<SubtitleTrack.Cue> activeCues) {
108876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (!mVisible) {
108976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            // don't keep the state if we are not visible
109076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            return;
109176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
109276935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
109376935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        if (DEBUG && mTimeProvider != null) {
109476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            try {
109576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                Log.d(TAG, "at " +
109676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        (mTimeProvider.getCurrentTimeUs(false, true) / 1000) +
109776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                        " ms the active cues are:");
109876935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            } catch (IllegalStateException e) {
109976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                Log.d(TAG, "at (illegal state) the active cues are:");
110076935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
110176935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
1102d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
11033cdf7c5b622a8fbb20410736bdab5888d0e1873cRobert Shih        if (mRenderingWidget != null) {
11043cdf7c5b622a8fbb20410736bdab5888d0e1873cRobert Shih            mRenderingWidget.setActiveCues(activeCues);
11053cdf7c5b622a8fbb20410736bdab5888d0e1873cRobert Shih        }
1106d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1107d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette}
1108d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1109d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette/**
1110d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette * Widget capable of rendering WebVTT captions.
1111d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette *
1112d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette * @hide
1113d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette */
1114d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viveretteclass WebVttRenderingWidget extends ViewGroup implements SubtitleTrack.RenderingWidget {
1115d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private static final boolean DEBUG = false;
1116e88aee8ad85b01229b12dbc0c3cc2f0b8b490192Alan Viverette
1117e88aee8ad85b01229b12dbc0c3cc2f0b8b490192Alan Viverette    private static final CaptionStyle DEFAULT_CAPTION_STYLE = CaptionStyle.DEFAULT;
1118e88aee8ad85b01229b12dbc0c3cc2f0b8b490192Alan Viverette
1119d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private static final int DEBUG_REGION_BACKGROUND = 0x800000FF;
1120d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private static final int DEBUG_CUE_BACKGROUND = 0x80FF0000;
1121d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1122d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    /** WebVtt specifies line height as 5.3% of the viewport height. */
1123d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private static final float LINE_HEIGHT_RATIO = 0.0533f;
1124d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1125d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    /** Map of active regions, used to determine enter/exit. */
1126d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private final ArrayMap<TextTrackRegion, RegionLayout> mRegionBoxes =
1127d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            new ArrayMap<TextTrackRegion, RegionLayout>();
1128d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1129d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    /** Map of active cues, used to determine enter/exit. */
1130d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private final ArrayMap<TextTrackCue, CueLayout> mCueBoxes =
1131d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            new ArrayMap<TextTrackCue, CueLayout>();
1132d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1133d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    /** Captioning manager, used to obtain and track caption properties. */
1134d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private final CaptioningManager mManager;
1135d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1136d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    /** Callback for rendering changes. */
1137d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private OnChangedListener mListener;
1138d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1139d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    /** Current caption style. */
1140d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private CaptionStyle mCaptionStyle;
1141d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1142d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    /** Current font size, computed from font scaling factor and height. */
1143d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private float mFontSize;
1144d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1145d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    /** Whether a caption style change listener is registered. */
1146d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private boolean mHasChangeListener;
1147d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1148d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    public WebVttRenderingWidget(Context context) {
1149d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        this(context, null);
1150d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1151d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1152d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    public WebVttRenderingWidget(Context context, AttributeSet attrs) {
1153d6479ec5eec13914f656f6be996d95fe1610fd57Alan Viverette        this(context, attrs, 0);
1154d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1155d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1156617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette    public WebVttRenderingWidget(Context context, AttributeSet attrs, int defStyleAttr) {
1157617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette        this(context, attrs, defStyleAttr, 0);
1158617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette    }
1159617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette
1160e88aee8ad85b01229b12dbc0c3cc2f0b8b490192Alan Viverette    public WebVttRenderingWidget(
1161e88aee8ad85b01229b12dbc0c3cc2f0b8b490192Alan Viverette            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
1162617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette        super(context, attrs, defStyleAttr, defStyleRes);
1163d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1164d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        // Cannot render text over video when layer type is hardware.
1165d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
1166d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1167d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        mManager = (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
1168d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        mCaptionStyle = mManager.getUserStyle();
1169d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        mFontSize = mManager.getFontScale() * getHeight() * LINE_HEIGHT_RATIO;
1170d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1171d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1172d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    @Override
1173d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    public void setSize(int width, int height) {
1174d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
1175d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
1176d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1177d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        measure(widthSpec, heightSpec);
1178d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        layout(0, 0, width, height);
1179d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1180d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1181d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    @Override
1182d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    public void onAttachedToWindow() {
1183d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        super.onAttachedToWindow();
1184d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1185d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        manageChangeListener();
1186d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1187d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1188d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    @Override
1189d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    public void onDetachedFromWindow() {
1190d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        super.onDetachedFromWindow();
1191d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1192d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        manageChangeListener();
1193d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1194d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1195d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    @Override
1196d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    public void setOnChangedListener(OnChangedListener listener) {
1197d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        mListener = listener;
1198d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1199d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1200d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    @Override
1201d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    public void setVisible(boolean visible) {
1202d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        if (visible) {
1203d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            setVisibility(View.VISIBLE);
1204d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        } else {
1205d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            setVisibility(View.GONE);
1206d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1207d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1208d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        manageChangeListener();
1209d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1210d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1211d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    /**
1212d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * Manages whether this renderer is listening for caption style changes.
1213d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     */
1214d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private void manageChangeListener() {
1215d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final boolean needsListener = isAttachedToWindow() && getVisibility() == View.VISIBLE;
1216d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        if (mHasChangeListener != needsListener) {
1217d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            mHasChangeListener = needsListener;
1218d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1219d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            if (needsListener) {
1220d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                mManager.addCaptioningChangeListener(mCaptioningListener);
1221d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1222d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                final CaptionStyle captionStyle = mManager.getUserStyle();
1223d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                final float fontSize = mManager.getFontScale() * getHeight() * LINE_HEIGHT_RATIO;
1224d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                setCaptionStyle(captionStyle, fontSize);
1225d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            } else {
1226d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                mManager.removeCaptioningChangeListener(mCaptioningListener);
1227d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            }
1228d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1229d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1230d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1231d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    public void setActiveCues(Vector<SubtitleTrack.Cue> activeCues) {
1232d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final Context context = getContext();
1233d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final CaptionStyle captionStyle = mCaptionStyle;
1234d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final float fontSize = mFontSize;
1235d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1236d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        prepForPrune();
1237d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1238d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        // Ensure we have all necessary cue and region boxes.
1239d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int count = activeCues.size();
1240d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        for (int i = 0; i < count; i++) {
1241d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final TextTrackCue cue = (TextTrackCue) activeCues.get(i);
1242d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final TextTrackRegion region = cue.mRegion;
1243d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            if (region != null) {
1244d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                RegionLayout regionBox = mRegionBoxes.get(region);
1245d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                if (regionBox == null) {
1246d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    regionBox = new RegionLayout(context, region, captionStyle, fontSize);
1247d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    mRegionBoxes.put(region, regionBox);
1248d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    addView(regionBox, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
124976935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
1250d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                regionBox.put(cue);
1251d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            } else {
1252d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                CueLayout cueBox = mCueBoxes.get(cue);
1253d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                if (cueBox == null) {
1254d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    cueBox = new CueLayout(context, cue, captionStyle, fontSize);
1255d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    mCueBoxes.put(cue, cueBox);
1256d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    addView(cueBox, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
125776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar                }
1258d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                cueBox.update();
1259d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                cueBox.setOrder(i);
1260d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            }
1261d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1262d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1263d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        prune();
1264d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1265d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        // Force measurement and layout.
1266d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int width = getWidth();
1267d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int height = getHeight();
1268d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        setSize(width, height);
1269d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1270d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        if (mListener != null) {
1271d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            mListener.onChanged(this);
1272d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1273d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1274d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1275d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private void setCaptionStyle(CaptionStyle captionStyle, float fontSize) {
1276e88aee8ad85b01229b12dbc0c3cc2f0b8b490192Alan Viverette        captionStyle = DEFAULT_CAPTION_STYLE.applyStyle(captionStyle);
1277d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        mCaptionStyle = captionStyle;
1278d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        mFontSize = fontSize;
1279d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1280d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int cueCount = mCueBoxes.size();
1281d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        for (int i = 0; i < cueCount; i++) {
1282d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final CueLayout cueBox = mCueBoxes.valueAt(i);
1283d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            cueBox.setCaptionStyle(captionStyle, fontSize);
1284d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1285d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1286d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int regionCount = mRegionBoxes.size();
1287d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        for (int i = 0; i < regionCount; i++) {
1288d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final RegionLayout regionBox = mRegionBoxes.valueAt(i);
1289d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            regionBox.setCaptionStyle(captionStyle, fontSize);
1290d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1291d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1292d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1293d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    /**
1294d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * Remove inactive cues and regions.
1295d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     */
1296d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private void prune() {
1297d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        int regionCount = mRegionBoxes.size();
1298d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        for (int i = 0; i < regionCount; i++) {
1299d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final RegionLayout regionBox = mRegionBoxes.valueAt(i);
1300d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            if (regionBox.prune()) {
1301d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                removeView(regionBox);
1302d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                mRegionBoxes.removeAt(i);
1303d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                regionCount--;
1304d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                i--;
130576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar            }
130676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
130776935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar
1308d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        int cueCount = mCueBoxes.size();
1309d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        for (int i = 0; i < cueCount; i++) {
1310d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final CueLayout cueBox = mCueBoxes.valueAt(i);
1311d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            if (!cueBox.isActive()) {
1312d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                removeView(cueBox);
1313d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                mCueBoxes.removeAt(i);
1314d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                cueCount--;
1315d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                i--;
1316d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            }
1317d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1318d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1319d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1320d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    /**
1321d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * Reset active cues and regions.
1322d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     */
1323d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private void prepForPrune() {
1324d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int regionCount = mRegionBoxes.size();
1325d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        for (int i = 0; i < regionCount; i++) {
1326d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final RegionLayout regionBox = mRegionBoxes.valueAt(i);
1327d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            regionBox.prepForPrune();
1328d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1329d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1330d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int cueCount = mCueBoxes.size();
1331d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        for (int i = 0; i < cueCount; i++) {
1332d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final CueLayout cueBox = mCueBoxes.valueAt(i);
1333d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            cueBox.prepForPrune();
1334d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1335d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1336d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1337d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    @Override
1338d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1339d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1340d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1341d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int regionCount = mRegionBoxes.size();
1342d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        for (int i = 0; i < regionCount; i++) {
1343d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final RegionLayout regionBox = mRegionBoxes.valueAt(i);
1344d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            regionBox.measureForParent(widthMeasureSpec, heightMeasureSpec);
1345d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1346d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1347d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int cueCount = mCueBoxes.size();
1348d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        for (int i = 0; i < cueCount; i++) {
1349d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final CueLayout cueBox = mCueBoxes.valueAt(i);
1350d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            cueBox.measureForParent(widthMeasureSpec, heightMeasureSpec);
1351d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1352d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1353d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1354d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    @Override
1355d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    protected void onLayout(boolean changed, int l, int t, int r, int b) {
1356d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int viewportWidth = r - l;
1357d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int viewportHeight = b - t;
1358d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1359d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        setCaptionStyle(mCaptionStyle,
1360d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                mManager.getFontScale() * LINE_HEIGHT_RATIO * viewportHeight);
1361d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1362d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int regionCount = mRegionBoxes.size();
1363d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        for (int i = 0; i < regionCount; i++) {
1364d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final RegionLayout regionBox = mRegionBoxes.valueAt(i);
1365d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            layoutRegion(viewportWidth, viewportHeight, regionBox);
1366d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1367d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1368d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int cueCount = mCueBoxes.size();
1369d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        for (int i = 0; i < cueCount; i++) {
1370d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final CueLayout cueBox = mCueBoxes.valueAt(i);
1371d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            layoutCue(viewportWidth, viewportHeight, cueBox);
1372d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1373d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1374d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1375d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    /**
1376d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * Lays out a region within the viewport. The region handles layout for
1377d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * contained cues.
1378d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     */
1379d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private void layoutRegion(
1380d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            int viewportWidth, int viewportHeight,
1381d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            RegionLayout regionBox) {
1382d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final TextTrackRegion region = regionBox.getRegion();
1383d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int regionHeight = regionBox.getMeasuredHeight();
1384d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int regionWidth = regionBox.getMeasuredWidth();
1385d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1386d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        // TODO: Account for region anchor point.
1387d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final float x = region.mViewportAnchorPointX;
1388d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final float y = region.mViewportAnchorPointY;
1389d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int left = (int) (x * (viewportWidth - regionWidth) / 100);
1390d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int top = (int) (y * (viewportHeight - regionHeight) / 100);
1391d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1392d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        regionBox.layout(left, top, left + regionWidth, top + regionHeight);
1393d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1394d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1395d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    /**
1396d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * Lays out a cue within the viewport.
1397d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     */
1398d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private void layoutCue(
1399d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            int viewportWidth, int viewportHeight, CueLayout cueBox) {
1400d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final TextTrackCue cue = cueBox.getCue();
1401d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int direction = getLayoutDirection();
1402d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int absAlignment = resolveCueAlignment(direction, cue.mAlignment);
1403d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final boolean cueSnapToLines = cue.mSnapToLines;
1404d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1405d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        int size = 100 * cueBox.getMeasuredWidth() / viewportWidth;
1406d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1407d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        // Determine raw x-position.
1408d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        int xPosition;
1409d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        switch (absAlignment) {
1410d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            case TextTrackCue.ALIGNMENT_LEFT:
1411d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                xPosition = cue.mTextPosition;
1412d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                break;
1413d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            case TextTrackCue.ALIGNMENT_RIGHT:
1414d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                xPosition = cue.mTextPosition - size;
1415d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                break;
1416d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            case TextTrackCue.ALIGNMENT_MIDDLE:
1417d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            default:
1418d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                xPosition = cue.mTextPosition - size / 2;
1419d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                break;
1420d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1421d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1422d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        // Adjust x-position for layout.
1423d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        if (direction == LAYOUT_DIRECTION_RTL) {
1424d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            xPosition = 100 - xPosition;
1425d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1426d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1427d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        // If the text track cue snap-to-lines flag is set, adjust
1428d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        // x-position and size for padding. This is equivalent to placing the
1429d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        // cue within the title-safe area.
1430d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        if (cueSnapToLines) {
1431d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final int paddingLeft = 100 * getPaddingLeft() / viewportWidth;
1432d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final int paddingRight = 100 * getPaddingRight() / viewportWidth;
1433d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            if (xPosition < paddingLeft && xPosition + size > paddingLeft) {
1434d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                xPosition += paddingLeft;
1435d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                size -= paddingLeft;
1436d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            }
1437d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final float rightEdge = 100 - paddingRight;
1438d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            if (xPosition < rightEdge && xPosition + size > rightEdge) {
1439d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                size -= paddingRight;
1440d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            }
1441d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1442d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1443d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        // Compute absolute left position and width.
1444d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int left = xPosition * viewportWidth / 100;
1445d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int width = size * viewportWidth / 100;
1446d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1447d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        // Determine initial y-position.
1448d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int yPosition = calculateLinePosition(cueBox);
1449d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1450d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        // Compute absolute final top position and height.
1451d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int height = cueBox.getMeasuredHeight();
1452d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final int top;
1453d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        if (yPosition < 0) {
1454d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            // TODO: This needs to use the actual height of prior boxes.
1455d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            top = viewportHeight + yPosition * height;
1456d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        } else {
1457d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            top = yPosition * (viewportHeight - height) / 100;
1458d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1459d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1460d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        // Layout cue in final position.
1461d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        cueBox.layout(left, top, left + width, top + height);
1462d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1463d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1464d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    /**
1465d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * Calculates the line position for a cue.
1466d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * <p>
1467d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * If the resulting position is negative, it represents a bottom-aligned
1468d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * position relative to the number of active cues. Otherwise, it represents
1469d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * a percentage [0-100] of the viewport height.
1470d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     */
1471d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private int calculateLinePosition(CueLayout cueBox) {
1472d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final TextTrackCue cue = cueBox.getCue();
1473d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final Integer linePosition = cue.mLinePosition;
1474d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final boolean snapToLines = cue.mSnapToLines;
1475d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        final boolean autoPosition = (linePosition == null);
1476d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1477d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        if (!snapToLines && !autoPosition && (linePosition < 0 || linePosition > 100)) {
1478d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            // Invalid line position defaults to 100.
1479d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            return 100;
1480d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        } else if (!autoPosition) {
1481d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            // Use the valid, supplied line position.
1482d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            return linePosition;
1483d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        } else if (!snapToLines) {
1484d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            // Automatic, non-snapped line position defaults to 100.
1485d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            return 100;
1486d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        } else {
1487d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            // Automatic snapped line position uses active cue order.
1488d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            return -(cueBox.mOrder + 1);
1489d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1490d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1491d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1492d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    /**
1493d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * Resolves cue alignment according to the specified layout direction.
1494d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     */
1495d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private static int resolveCueAlignment(int layoutDirection, int alignment) {
1496d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        switch (alignment) {
1497d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            case TextTrackCue.ALIGNMENT_START:
1498d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                return layoutDirection == View.LAYOUT_DIRECTION_LTR ?
1499d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                        TextTrackCue.ALIGNMENT_LEFT : TextTrackCue.ALIGNMENT_RIGHT;
1500d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            case TextTrackCue.ALIGNMENT_END:
1501d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                return layoutDirection == View.LAYOUT_DIRECTION_LTR ?
1502d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                        TextTrackCue.ALIGNMENT_RIGHT : TextTrackCue.ALIGNMENT_LEFT;
1503d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1504d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        return alignment;
1505d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1506d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1507d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private final CaptioningChangeListener mCaptioningListener = new CaptioningChangeListener() {
1508d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        @Override
1509d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public void onFontScaleChanged(float fontScale) {
1510d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final float fontSize = fontScale * getHeight() * LINE_HEIGHT_RATIO;
1511d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            setCaptionStyle(mCaptionStyle, fontSize);
1512d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1513d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1514d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        @Override
1515d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public void onUserStyleChanged(CaptionStyle userStyle) {
1516d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            setCaptionStyle(userStyle, mFontSize);
1517d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1518d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    };
1519d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1520d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    /**
1521d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * A text track region represents a portion of the video viewport and
1522d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * provides a rendering area for text track cues.
1523d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     */
1524d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private static class RegionLayout extends LinearLayout {
1525d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        private final ArrayList<CueLayout> mRegionCueBoxes = new ArrayList<CueLayout>();
1526d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        private final TextTrackRegion mRegion;
1527d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1528d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        private CaptionStyle mCaptionStyle;
1529d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        private float mFontSize;
1530d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1531d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public RegionLayout(Context context, TextTrackRegion region, CaptionStyle captionStyle,
1532d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                float fontSize) {
1533d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            super(context);
1534d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1535d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            mRegion = region;
1536d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            mCaptionStyle = captionStyle;
1537d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            mFontSize = fontSize;
1538d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1539d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            // TODO: Add support for vertical text
1540d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            setOrientation(VERTICAL);
1541d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1542d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            if (DEBUG) {
1543d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                setBackgroundColor(DEBUG_REGION_BACKGROUND);
154455d70620d9fda8afafb2fdec59757a710eec0e89Alan Viverette            } else {
154555d70620d9fda8afafb2fdec59757a710eec0e89Alan Viverette                setBackgroundColor(captionStyle.windowColor);
1546d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            }
1547d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1548d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1549d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public void setCaptionStyle(CaptionStyle captionStyle, float fontSize) {
1550d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            mCaptionStyle = captionStyle;
1551d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            mFontSize = fontSize;
1552d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1553d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final int cueCount = mRegionCueBoxes.size();
1554d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            for (int i = 0; i < cueCount; i++) {
1555d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                final CueLayout cueBox = mRegionCueBoxes.get(i);
1556d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                cueBox.setCaptionStyle(captionStyle, fontSize);
1557d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            }
155855d70620d9fda8afafb2fdec59757a710eec0e89Alan Viverette
155955d70620d9fda8afafb2fdec59757a710eec0e89Alan Viverette            setBackgroundColor(captionStyle.windowColor);
1560d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1561d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1562d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        /**
1563d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         * Performs the parent's measurement responsibilities, then
1564d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         * automatically performs its own measurement.
1565d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         */
1566d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public void measureForParent(int widthMeasureSpec, int heightMeasureSpec) {
1567d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final TextTrackRegion region = mRegion;
1568d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final int specWidth = MeasureSpec.getSize(widthMeasureSpec);
1569d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final int specHeight = MeasureSpec.getSize(heightMeasureSpec);
1570d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final int width = (int) region.mWidth;
1571d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1572d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            // Determine the absolute maximum region size as the requested size.
1573d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final int size = width * specWidth / 100;
1574d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1575d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            widthMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
1576d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            heightMeasureSpec = MeasureSpec.makeMeasureSpec(specHeight, MeasureSpec.AT_MOST);
1577d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            measure(widthMeasureSpec, heightMeasureSpec);
1578d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1579d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1580d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        /**
1581d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         * Prepares this region for pruning by setting all tracks as inactive.
1582d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         * <p>
1583d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         * Tracks that are added or updated using {@link #put(TextTrackCue)}
1584d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         * after this calling this method will be marked as active.
1585d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         */
1586d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public void prepForPrune() {
1587d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final int cueCount = mRegionCueBoxes.size();
1588d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            for (int i = 0; i < cueCount; i++) {
1589d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                final CueLayout cueBox = mRegionCueBoxes.get(i);
1590d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                cueBox.prepForPrune();
1591d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            }
1592d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1593d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1594d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        /**
1595d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         * Adds a {@link TextTrackCue} to this region. If the track had already
1596d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         * been added, updates its active state.
1597d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         *
1598d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         * @param cue
1599d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         */
1600d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public void put(TextTrackCue cue) {
1601d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final int cueCount = mRegionCueBoxes.size();
1602d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            for (int i = 0; i < cueCount; i++) {
1603d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                final CueLayout cueBox = mRegionCueBoxes.get(i);
1604d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                if (cueBox.getCue() == cue) {
1605d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    cueBox.update();
1606d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    return;
1607d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                }
1608d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            }
1609d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1610d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final CueLayout cueBox = new CueLayout(getContext(), cue, mCaptionStyle, mFontSize);
16117fe420f31b4de2bbd7a89194384d0c9ac6293226Alan Viverette            mRegionCueBoxes.add(cueBox);
1612d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            addView(cueBox, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
1613d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1614d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            if (getChildCount() > mRegion.mLines) {
1615d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                removeViewAt(0);
1616d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            }
1617d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1618d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1619d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        /**
1620d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         * Remove all inactive tracks from this region.
1621d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         *
1622d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         * @return true if this region is empty and should be pruned
1623d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         */
1624d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public boolean prune() {
1625d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            int cueCount = mRegionCueBoxes.size();
1626d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            for (int i = 0; i < cueCount; i++) {
1627d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                final CueLayout cueBox = mRegionCueBoxes.get(i);
1628d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                if (!cueBox.isActive()) {
1629d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    mRegionCueBoxes.remove(i);
1630d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    removeView(cueBox);
1631d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    cueCount--;
1632d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    i--;
1633d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                }
1634d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            }
1635d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1636d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            return mRegionCueBoxes.isEmpty();
1637d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1638d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1639d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        /**
1640d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         * @return the region data backing this layout
1641d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         */
1642d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public TextTrackRegion getRegion() {
1643d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            return mRegion;
1644d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1645d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1646d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1647d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    /**
1648d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * A text track cue is the unit of time-sensitive data in a text track,
1649d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * corresponding for instance for subtitles and captions to the text that
1650d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * appears at a particular time and disappears at another time.
1651d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * <p>
1652d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * A single cue may contain multiple {@link SpanLayout}s, each representing a
1653d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * single line of text.
1654d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     */
1655d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private static class CueLayout extends LinearLayout {
1656d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public final TextTrackCue mCue;
1657d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1658d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        private CaptionStyle mCaptionStyle;
1659d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        private float mFontSize;
1660d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1661d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        private boolean mActive;
1662d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        private int mOrder;
1663d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1664d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public CueLayout(
1665d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                Context context, TextTrackCue cue, CaptionStyle captionStyle, float fontSize) {
1666d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            super(context);
1667d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1668d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            mCue = cue;
1669d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            mCaptionStyle = captionStyle;
1670d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            mFontSize = fontSize;
1671d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1672d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            // TODO: Add support for vertical text.
1673d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final boolean horizontal = cue.mWritingDirection
1674d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    == TextTrackCue.WRITING_DIRECTION_HORIZONTAL;
1675d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            setOrientation(horizontal ? VERTICAL : HORIZONTAL);
1676d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1677d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            switch (cue.mAlignment) {
1678d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                case TextTrackCue.ALIGNMENT_END:
1679d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    setGravity(Gravity.END);
1680d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    break;
1681d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                case TextTrackCue.ALIGNMENT_LEFT:
1682d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    setGravity(Gravity.LEFT);
1683d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    break;
1684d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                case TextTrackCue.ALIGNMENT_MIDDLE:
1685d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    setGravity(horizontal
1686d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                            ? Gravity.CENTER_HORIZONTAL : Gravity.CENTER_VERTICAL);
1687d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    break;
1688d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                case TextTrackCue.ALIGNMENT_RIGHT:
1689d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    setGravity(Gravity.RIGHT);
1690d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    break;
1691d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                case TextTrackCue.ALIGNMENT_START:
1692d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    setGravity(Gravity.START);
1693d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    break;
1694d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            }
1695d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1696d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            if (DEBUG) {
1697d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                setBackgroundColor(DEBUG_CUE_BACKGROUND);
1698d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            }
1699d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1700d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            update();
1701d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1702d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1703d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public void setCaptionStyle(CaptionStyle style, float fontSize) {
1704d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            mCaptionStyle = style;
1705d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            mFontSize = fontSize;
1706d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1707d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final int n = getChildCount();
1708d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            for (int i = 0; i < n; i++) {
1709d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                final View child = getChildAt(i);
1710d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                if (child instanceof SpanLayout) {
1711d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    ((SpanLayout) child).setCaptionStyle(style, fontSize);
1712d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                }
1713d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            }
1714d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1715d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1716d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public void prepForPrune() {
1717d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            mActive = false;
1718d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1719d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1720d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public void update() {
1721d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            mActive = true;
1722d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1723d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            removeAllViews();
1724d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
17257fe420f31b4de2bbd7a89194384d0c9ac6293226Alan Viverette            final int cueAlignment = resolveCueAlignment(getLayoutDirection(), mCue.mAlignment);
17267fe420f31b4de2bbd7a89194384d0c9ac6293226Alan Viverette            final Alignment alignment;
17277fe420f31b4de2bbd7a89194384d0c9ac6293226Alan Viverette            switch (cueAlignment) {
17287fe420f31b4de2bbd7a89194384d0c9ac6293226Alan Viverette                case TextTrackCue.ALIGNMENT_LEFT:
17297fe420f31b4de2bbd7a89194384d0c9ac6293226Alan Viverette                    alignment = Alignment.ALIGN_LEFT;
17307fe420f31b4de2bbd7a89194384d0c9ac6293226Alan Viverette                    break;
17317fe420f31b4de2bbd7a89194384d0c9ac6293226Alan Viverette                case TextTrackCue.ALIGNMENT_RIGHT:
17327fe420f31b4de2bbd7a89194384d0c9ac6293226Alan Viverette                    alignment = Alignment.ALIGN_RIGHT;
17337fe420f31b4de2bbd7a89194384d0c9ac6293226Alan Viverette                    break;
17347fe420f31b4de2bbd7a89194384d0c9ac6293226Alan Viverette                case TextTrackCue.ALIGNMENT_MIDDLE:
17357fe420f31b4de2bbd7a89194384d0c9ac6293226Alan Viverette                default:
17367fe420f31b4de2bbd7a89194384d0c9ac6293226Alan Viverette                    alignment = Alignment.ALIGN_CENTER;
17377fe420f31b4de2bbd7a89194384d0c9ac6293226Alan Viverette            }
17387fe420f31b4de2bbd7a89194384d0c9ac6293226Alan Viverette
1739d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final CaptionStyle captionStyle = mCaptionStyle;
1740d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final float fontSize = mFontSize;
1741d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final TextTrackCueSpan[][] lines = mCue.mLines;
1742d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final int lineCount = lines.length;
1743d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            for (int i = 0; i < lineCount; i++) {
1744d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                final SpanLayout lineBox = new SpanLayout(getContext(), lines[i]);
17457fe420f31b4de2bbd7a89194384d0c9ac6293226Alan Viverette                lineBox.setAlignment(alignment);
1746d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                lineBox.setCaptionStyle(captionStyle, fontSize);
1747d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1748d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                addView(lineBox, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
1749d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            }
1750d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1751d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1752d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        @Override
1753d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1754d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1755d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1756d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1757d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        /**
1758d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         * Performs the parent's measurement responsibilities, then
1759d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         * automatically performs its own measurement.
1760d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         */
1761d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public void measureForParent(int widthMeasureSpec, int heightMeasureSpec) {
1762d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final TextTrackCue cue = mCue;
1763d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final int specWidth = MeasureSpec.getSize(widthMeasureSpec);
1764d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final int specHeight = MeasureSpec.getSize(heightMeasureSpec);
1765d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final int direction = getLayoutDirection();
1766d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final int absAlignment = resolveCueAlignment(direction, cue.mAlignment);
1767d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1768d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            // Determine the maximum size of cue based on its starting position
1769d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            // and the direction in which it grows.
1770d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final int maximumSize;
1771d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            switch (absAlignment) {
1772d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                case TextTrackCue.ALIGNMENT_LEFT:
1773d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    maximumSize = 100 - cue.mTextPosition;
1774d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    break;
1775d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                case TextTrackCue.ALIGNMENT_RIGHT:
1776d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    maximumSize = cue.mTextPosition;
1777d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    break;
1778d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                case TextTrackCue.ALIGNMENT_MIDDLE:
1779d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    if (cue.mTextPosition <= 50) {
1780d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                        maximumSize = cue.mTextPosition * 2;
1781d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    } else {
1782d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                        maximumSize = (100 - cue.mTextPosition) * 2;
1783d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    }
1784d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    break;
1785d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                default:
1786d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    maximumSize = 0;
1787d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            }
1788d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1789d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            // Determine absolute maximum cue size as the smaller of the
1790d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            // requested size and the maximum theoretical size.
1791d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final int size = Math.min(cue.mSize, maximumSize) * specWidth / 100;
1792d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            widthMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
1793d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            heightMeasureSpec = MeasureSpec.makeMeasureSpec(specHeight, MeasureSpec.AT_MOST);
1794d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            measure(widthMeasureSpec, heightMeasureSpec);
1795d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1796d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1797d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        /**
1798d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         * Sets the order of this cue in the list of active cues.
1799d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         *
1800d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         * @param order the order of this cue in the list of active cues
1801d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         */
1802d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public void setOrder(int order) {
1803d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            mOrder = order;
1804d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1805d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1806d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        /**
1807d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         * @return whether this cue is marked as active
1808d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         */
1809d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public boolean isActive() {
1810d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            return mActive;
1811d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1812d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1813d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        /**
1814d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         * @return the cue data backing this layout
1815d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette         */
1816d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public TextTrackCue getCue() {
1817d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            return mCue;
1818d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1819d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    }
1820d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1821d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    /**
1822d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * A text track line represents a single line of text within a cue.
1823d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * <p>
1824d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * A single line may contain multiple spans, each representing a section of
1825d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     * text that may be enabled or disabled at a particular time.
1826d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette     */
1827d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette    private static class SpanLayout extends SubtitleView {
1828d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        private final SpannableStringBuilder mBuilder = new SpannableStringBuilder();
1829d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        private final TextTrackCueSpan[] mSpans;
1830d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1831d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public SpanLayout(Context context, TextTrackCueSpan[] spans) {
1832d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            super(context);
1833d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1834d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            mSpans = spans;
1835d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1836d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            update();
1837d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1838d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1839d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public void update() {
1840d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final SpannableStringBuilder builder = mBuilder;
1841d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final TextTrackCueSpan[] spans = mSpans;
1842d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1843d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            builder.clear();
1844d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            builder.clearSpans();
1845d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1846d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            final int spanCount = spans.length;
1847d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            for (int i = 0; i < spanCount; i++) {
1848d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                final TextTrackCueSpan span = spans[i];
1849d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                if (span.mEnabled) {
1850d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                    builder.append(spans[i].mText);
1851d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette                }
1852d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            }
1853d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1854d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            setText(builder);
1855d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        }
1856d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette
1857d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette        public void setCaptionStyle(CaptionStyle captionStyle, float fontSize) {
1858d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            setBackgroundColor(captionStyle.backgroundColor);
1859d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            setForegroundColor(captionStyle.foregroundColor);
1860d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            setEdgeColor(captionStyle.edgeColor);
1861d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            setEdgeType(captionStyle.edgeType);
1862d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            setTypeface(captionStyle.getTypeface());
1863d43daf361e993457e64eeeddab6d1a0ebc828c99Alan Viverette            setTextSize(fontSize);
186476935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar        }
186576935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar    }
186676935a5890ed8dd9d47f76f7593cc1c14c3844dbLajos Molnar}
1867