1134545d2d8429615552931ef50a15ba2994d03f8Insun Kang/*
2134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * Copyright 2018 The Android Open Source Project
3134545d2d8429615552931ef50a15ba2994d03f8Insun Kang *
4134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * Licensed under the Apache License, Version 2.0 (the "License");
5134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * you may not use this file except in compliance with the License.
6134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * You may obtain a copy of the License at
7134545d2d8429615552931ef50a15ba2994d03f8Insun Kang *
8134545d2d8429615552931ef50a15ba2994d03f8Insun Kang *      http://www.apache.org/licenses/LICENSE-2.0
9134545d2d8429615552931ef50a15ba2994d03f8Insun Kang *
10134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * Unless required by applicable law or agreed to in writing, software
11134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * distributed under the License is distributed on an "AS IS" BASIS,
12134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * See the License for the specific language governing permissions and
14134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * limitations under the License.
15134545d2d8429615552931ef50a15ba2994d03f8Insun Kang */
16134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
17134545d2d8429615552931ef50a15ba2994d03f8Insun Kangpackage androidx.media.subtitle;
18134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
19134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
21134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport android.graphics.Canvas;
22134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport android.media.MediaFormat;
23134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport android.media.MediaPlayer.TrackInfo;
24134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport android.media.SubtitleData;
25134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport android.os.Handler;
26134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport android.util.Log;
27134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport android.util.LongSparseArray;
28134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport android.util.Pair;
29134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
30134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport androidx.annotation.RequiresApi;
31134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport androidx.annotation.RestrictTo;
32134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
33134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport java.util.ArrayList;
34134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport java.util.Iterator;
35134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport java.util.NoSuchElementException;
36134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport java.util.SortedMap;
37134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport java.util.TreeMap;
38134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
39134545d2d8429615552931ef50a15ba2994d03f8Insun Kang// Note: This is forked from android.media.SubtitleTrack since P
40134545d2d8429615552931ef50a15ba2994d03f8Insun Kang/**
41134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * A subtitle track abstract base class that is responsible for parsing and displaying
42134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * an instance of a particular type of subtitle.
43134545d2d8429615552931ef50a15ba2994d03f8Insun Kang *
44134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * @hide
45134545d2d8429615552931ef50a15ba2994d03f8Insun Kang */
46134545d2d8429615552931ef50a15ba2994d03f8Insun Kang@RequiresApi(28)
47134545d2d8429615552931ef50a15ba2994d03f8Insun Kang@RestrictTo(LIBRARY_GROUP)
48134545d2d8429615552931ef50a15ba2994d03f8Insun Kangpublic abstract class SubtitleTrack implements MediaTimeProvider.OnMediaTimeListener {
49134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final String TAG = "SubtitleTrack";
50134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private long mLastUpdateTimeMs;
51134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private long mLastTimeMs;
52134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
53134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private Runnable mRunnable;
54134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
55134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private final LongSparseArray<Run> mRunsByEndTime = new LongSparseArray<Run>();
56134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private final LongSparseArray<Run> mRunsByID = new LongSparseArray<Run>();
57134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
58134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private CueList mCues;
59134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private final ArrayList<Cue> mActiveCues = new ArrayList<Cue>();
60134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    protected boolean mVisible;
61134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
62134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    public boolean DEBUG = false;
63134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
64134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    protected Handler mHandler = new Handler();
65134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
66134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private MediaFormat mFormat;
67134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
68134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    public SubtitleTrack(MediaFormat format) {
69134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        mFormat = format;
70134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        mCues = new CueList();
71134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        clearActiveCues();
72134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        mLastTimeMs = -1;
73134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
74134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
75134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    public final MediaFormat getFormat() {
76134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        return mFormat;
77134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
78134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
79134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private long mNextScheduledTimeMs = -1;
80134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
81134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    /**
82134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * Called when there is input data for the subtitle track.
83134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     */
84134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    public void onData(SubtitleData data) {
85134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        long runID = data.getStartTimeUs() + 1;
86134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        onData(data.getData(), true /* eos */, runID);
87134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        setRunDiscardTimeMs(
88134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                runID,
89134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                (data.getStartTimeUs() + data.getDurationUs()) / 1000);
90134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
91134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
92134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    /**
93134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * Called when there is input data for the subtitle track.  The
94134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * complete subtitle for a track can include multiple whole units
95134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * (runs).  Each of these units can have multiple sections.  The
96134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * contents of a run are submitted in sequential order, with eos
97134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * indicating the last section of the run.  Calls from different
98134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * runs must not be intermixed.
99134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     *
100134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * @param data subtitle data byte buffer
101134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * @param eos true if this is the last section of the run.
102134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * @param runID mostly-unique ID for this run of data.  Subtitle cues
103134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     *              with runID of 0 are discarded immediately after
104134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     *              display.  Cues with runID of ~0 are discarded
105134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     *              only at the deletion of the track object.  Cues
106134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     *              with other runID-s are discarded at the end of the
107134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     *              run, which defaults to the latest timestamp of
108134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     *              any of its cues (with this runID).
109134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     */
110134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    protected abstract void onData(byte[] data, boolean eos, long runID);
111134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
112134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    /**
113134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * Called when adding the subtitle rendering widget to the view hierarchy,
114134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * as well as when showing or hiding the subtitle track, or when the video
115134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * surface position has changed.
116134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     *
117134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * @return the widget that renders this subtitle track. For most renderers
118134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     *         there should be a single shared instance that is used for all
119134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     *         tracks supported by that renderer, as at most one subtitle track
120134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     *         is visible at one time.
121134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     */
122134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    public abstract RenderingWidget getRenderingWidget();
123134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
124134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    /**
125134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * Called when the active cues have changed, and the contents of the subtitle
126134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * view should be updated.
127134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     */
128134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    public abstract void updateView(ArrayList<Cue> activeCues);
129134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
130134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    protected synchronized void updateActiveCues(boolean rebuild, long timeMs) {
131134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        // out-of-order times mean seeking or new active cues being added
132134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        // (during their own timespan)
133134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (rebuild || mLastUpdateTimeMs > timeMs) {
134134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            clearActiveCues();
135134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
136134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
137134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        for (Iterator<Pair<Long, Cue>> it =
138134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mCues.entriesBetween(mLastUpdateTimeMs, timeMs).iterator(); it.hasNext(); ) {
139134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            Pair<Long, Cue> event = it.next();
140134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            Cue cue = event.second;
141134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
142134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (cue.mEndTimeMs == event.first) {
143134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // remove past cues
144134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (DEBUG) Log.v(TAG, "Removing " + cue);
145134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mActiveCues.remove(cue);
146134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (cue.mRunID == 0) {
147134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    it.remove();
148134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
149134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            } else if (cue.mStartTimeMs == event.first) {
150134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // add new cues
151134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // TRICKY: this will happen in start order
152134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (DEBUG) Log.v(TAG, "Adding " + cue);
153134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (cue.mInnerTimesMs != null) {
154134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    cue.onTime(timeMs);
155134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
156134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mActiveCues.add(cue);
157134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            } else if (cue.mInnerTimesMs != null) {
158134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // cue is modified
159134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                cue.onTime(timeMs);
160134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
161134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
162134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
163134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        /* complete any runs */
164134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        while (mRunsByEndTime.size() > 0 && mRunsByEndTime.keyAt(0) <= timeMs) {
165134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            removeRunsByEndTimeIndex(0); // removes element
166134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
167134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        mLastUpdateTimeMs = timeMs;
168134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
169134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
170134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private void removeRunsByEndTimeIndex(int ix) {
171134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        Run run = mRunsByEndTime.valueAt(ix);
172134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        while (run != null) {
173134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            Cue cue = run.mFirstCue;
174134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            while (cue != null) {
175134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mCues.remove(cue);
176134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                Cue nextCue = cue.mNextInRun;
177134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                cue.mNextInRun = null;
178134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                cue = nextCue;
179134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
180134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mRunsByID.remove(run.mRunID);
181134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            Run nextRun = run.mNextRunAtEndTimeMs;
182134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            run.mPrevRunAtEndTimeMs = null;
183134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            run.mNextRunAtEndTimeMs = null;
184134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            run = nextRun;
185134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
186134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        mRunsByEndTime.removeAt(ix);
187134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
188134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
189134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    @Override
190134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    protected void finalize() throws Throwable {
191134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        /* remove all cues (untangle all cross-links) */
192134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        int size = mRunsByEndTime.size();
193134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        for (int ix = size - 1; ix >= 0; ix--) {
194134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            removeRunsByEndTimeIndex(ix);
195134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
196134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
197134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        super.finalize();
198134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
199134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
200134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private synchronized void takeTime(long timeMs) {
201134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        mLastTimeMs = timeMs;
202134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
203134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
204134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    protected synchronized void clearActiveCues() {
205134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (DEBUG) Log.v(TAG, "Clearing " + mActiveCues.size() + " active cues");
206134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        mActiveCues.clear();
207134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        mLastUpdateTimeMs = -1;
208134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
209134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
210134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    protected void scheduleTimedEvents() {
211134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        /* get times for the next event */
212134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (mTimeProvider != null) {
213134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mNextScheduledTimeMs = mCues.nextTimeAfter(mLastTimeMs);
214134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (DEBUG) Log.d(TAG, "sched @" + mNextScheduledTimeMs + " after " + mLastTimeMs);
215134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mTimeProvider.notifyAt(mNextScheduledTimeMs >= 0
216134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    ? (mNextScheduledTimeMs * 1000) : MediaTimeProvider.NO_TIME, this);
217134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
218134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
219134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
220134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    @Override
221134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    public void onTimedEvent(long timeUs) {
222134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (DEBUG) Log.d(TAG, "onTimedEvent " + timeUs);
223134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        synchronized (this) {
224134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            long timeMs = timeUs / 1000;
225134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            updateActiveCues(false, timeMs);
226134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            takeTime(timeMs);
227134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
228134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        updateView(mActiveCues);
229134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        scheduleTimedEvents();
230134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
231134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
232134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    @Override
233134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    public void onSeek(long timeUs) {
234134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (DEBUG) Log.d(TAG, "onSeek " + timeUs);
235134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        synchronized (this) {
236134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            long timeMs = timeUs / 1000;
237134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            updateActiveCues(true, timeMs);
238134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            takeTime(timeMs);
239134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
240134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        updateView(mActiveCues);
241134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        scheduleTimedEvents();
242134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
243134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
244134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    @Override
245134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    public void onStop() {
246134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        synchronized (this) {
247134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (DEBUG) Log.d(TAG, "onStop");
248134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            clearActiveCues();
249134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mLastTimeMs = -1;
250134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
251134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        updateView(mActiveCues);
252134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        mNextScheduledTimeMs = -1;
253134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        mTimeProvider.notifyAt(MediaTimeProvider.NO_TIME, this);
254134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
255134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
256134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    protected MediaTimeProvider mTimeProvider;
257134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
258134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    /**
259134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * Shows subtitle rendering widget
260134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     */
261134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    public void show() {
262134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (mVisible) {
263134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return;
264134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
265134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
266134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        mVisible = true;
267134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        RenderingWidget renderingWidget = getRenderingWidget();
268134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (renderingWidget != null) {
269134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            renderingWidget.setVisible(true);
270134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
271134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (mTimeProvider != null) {
272134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mTimeProvider.scheduleUpdate(this);
273134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
274134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
275134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
276134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    /**
277134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * Hides subtitle rendering widget
278134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     */
279134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    public void hide() {
280134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (!mVisible) {
281134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return;
282134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
283134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
284134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (mTimeProvider != null) {
285134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mTimeProvider.cancelNotifications(this);
286134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
287134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        RenderingWidget renderingWidget = getRenderingWidget();
288134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (renderingWidget != null) {
289134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            renderingWidget.setVisible(false);
290134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
291134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        mVisible = false;
292134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
293134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
294134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    protected synchronized boolean addCue(Cue cue) {
295134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        mCues.add(cue);
296134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
297134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (cue.mRunID != 0) {
298134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            Run run = mRunsByID.get(cue.mRunID);
299134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (run == null) {
300134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                run = new Run();
301134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mRunsByID.put(cue.mRunID, run);
302134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                run.mEndTimeMs = cue.mEndTimeMs;
303134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            } else if (run.mEndTimeMs < cue.mEndTimeMs) {
304134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                run.mEndTimeMs = cue.mEndTimeMs;
305134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
306134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
307134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            // link-up cues in the same run
308134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            cue.mNextInRun = run.mFirstCue;
309134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            run.mFirstCue = cue;
310134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
311134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
312134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        // if a cue is added that should be visible, need to refresh view
313134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        long nowMs = -1;
314134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (mTimeProvider != null) {
315134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            try {
316134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                nowMs = mTimeProvider.getCurrentTimeUs(
317134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        false /* precise */, true /* monotonic */) / 1000;
318134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            } catch (IllegalStateException e) {
319134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // handle as it we are not playing
320134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
321134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
322134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
323134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (DEBUG) {
324134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            Log.v(TAG, "mVisible=" + mVisible + ", "
325134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    + cue.mStartTimeMs + " <= " + nowMs + ", "
326134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    + cue.mEndTimeMs + " >= " + mLastTimeMs);
327134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
328134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
329134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (mVisible && cue.mStartTimeMs <= nowMs
330134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // we don't trust nowMs, so check any cue since last callback
331134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                && cue.mEndTimeMs >= mLastTimeMs) {
332134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (mRunnable != null) {
333134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mHandler.removeCallbacks(mRunnable);
334134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
335134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            final SubtitleTrack track = this;
336134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            final long thenMs = nowMs;
337134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mRunnable = new Runnable() {
338134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                @Override
339134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                public void run() {
340134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    // even with synchronized, it is possible that we are going
341134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    // to do multiple updates as the runnable could be already
342134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    // running.
343134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    synchronized (track) {
344134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        mRunnable = null;
345134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        updateActiveCues(true, thenMs);
346134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        updateView(mActiveCues);
347134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    }
348134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
349134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            };
350134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            // delay update so we don't update view on every cue.  TODO why 10?
351134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (mHandler.postDelayed(mRunnable, 10 /* delay */)) {
352134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (DEBUG) Log.v(TAG, "scheduling update");
353134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            } else {
354134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (DEBUG) Log.w(TAG, "failed to schedule subtitle view update");
355134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
356134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return true;
357134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
358134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
359134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (mVisible && cue.mEndTimeMs >= mLastTimeMs
360134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                && (cue.mStartTimeMs < mNextScheduledTimeMs || mNextScheduledTimeMs < 0)) {
361134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            scheduleTimedEvents();
362134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
363134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
364134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        return false;
365134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
366134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
367134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    /**
368134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * Sets MediaTimeProvider
369134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     */
370134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    public synchronized void setTimeProvider(MediaTimeProvider timeProvider) {
371134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (mTimeProvider == timeProvider) {
372134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return;
373134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
374134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (mTimeProvider != null) {
375134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mTimeProvider.cancelNotifications(this);
376134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
377134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        mTimeProvider = timeProvider;
378134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (mTimeProvider != null) {
379134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mTimeProvider.scheduleUpdate(this);
380134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
381134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
382134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
383134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
384134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    static class CueList {
385134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private static final String TAG = "CueList";
386134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        // simplistic, inefficient implementation
387134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private SortedMap<Long, ArrayList<Cue>> mCues;
388134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public boolean DEBUG = false;
389134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
390134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private boolean addEvent(Cue cue, long timeMs) {
391134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            ArrayList<Cue> cues = mCues.get(timeMs);
392134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (cues == null) {
393134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                cues = new ArrayList<Cue>(2);
394134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mCues.put(timeMs, cues);
395134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            } else if (cues.contains(cue)) {
396134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // do not duplicate cues
397134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return false;
398134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
399134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
400134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            cues.add(cue);
401134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return true;
402134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
403134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
404134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private void removeEvent(Cue cue, long timeMs) {
405134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            ArrayList<Cue> cues = mCues.get(timeMs);
406134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (cues != null) {
407134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                cues.remove(cue);
408134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (cues.size() == 0) {
409134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    mCues.remove(timeMs);
410134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
411134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
412134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
413134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
414134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public void add(Cue cue) {
415134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            // ignore non-positive-duration cues
416134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (cue.mStartTimeMs >= cue.mEndTimeMs) return;
417134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
418134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (!addEvent(cue, cue.mStartTimeMs)) {
419134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return;
420134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
421134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
422134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            long lastTimeMs = cue.mStartTimeMs;
423134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (cue.mInnerTimesMs != null) {
424134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                for (long timeMs: cue.mInnerTimesMs) {
425134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    if (timeMs > lastTimeMs && timeMs < cue.mEndTimeMs) {
426134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        addEvent(cue, timeMs);
427134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        lastTimeMs = timeMs;
428134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    }
429134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
430134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
431134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
432134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            addEvent(cue, cue.mEndTimeMs);
433134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
434134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
435134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public void remove(Cue cue) {
436134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            removeEvent(cue, cue.mStartTimeMs);
437134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (cue.mInnerTimesMs != null) {
438134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                for (long timeMs: cue.mInnerTimesMs) {
439134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    removeEvent(cue, timeMs);
440134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
441134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
442134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            removeEvent(cue, cue.mEndTimeMs);
443134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
444134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
445134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public Iterable<Pair<Long, Cue>> entriesBetween(
446134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                final long lastTimeMs, final long timeMs) {
447134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return new Iterable<Pair<Long, Cue>>() {
448134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                @Override
449134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                public Iterator<Pair<Long, Cue>> iterator() {
450134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    if (DEBUG) Log.d(TAG, "slice (" + lastTimeMs + ", " + timeMs + "]=");
451134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    try {
452134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        return new EntryIterator(
453134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                                mCues.subMap(lastTimeMs + 1, timeMs + 1));
454134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    } catch (IllegalArgumentException e) {
455134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        return new EntryIterator(null);
456134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    }
457134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
458134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            };
459134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
460134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
461134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public long nextTimeAfter(long timeMs) {
462134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            SortedMap<Long, ArrayList<Cue>> tail = null;
463134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            try {
464134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                tail = mCues.tailMap(timeMs + 1);
465134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (tail != null) {
466134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    return tail.firstKey();
467134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                } else {
468134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    return -1;
469134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
470134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            } catch (IllegalArgumentException e) {
471134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return -1;
472134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            } catch (NoSuchElementException e) {
473134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return -1;
474134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
475134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
476134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
477134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        class EntryIterator implements Iterator<Pair<Long, Cue>> {
478134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            @Override
479134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            public boolean hasNext() {
480134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return !mDone;
481134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
482134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
483134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            @Override
484134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            public Pair<Long, Cue> next() {
485134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (mDone) {
486134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    throw new NoSuchElementException("");
487134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
488134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mLastEntry = new Pair<Long, Cue>(
489134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        mCurrentTimeMs, mListIterator.next());
490134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mLastListIterator = mListIterator;
491134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (!mListIterator.hasNext()) {
492134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    nextKey();
493134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
494134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return mLastEntry;
495134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
496134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
497134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            @Override
498134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            public void remove() {
499134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // only allow removing end tags
500134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (mLastListIterator == null
501134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        || mLastEntry.second.mEndTimeMs != mLastEntry.first) {
502134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    throw new IllegalStateException("");
503134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
504134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
505134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // remove end-cue
506134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mLastListIterator.remove();
507134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mLastListIterator = null;
508134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (mCues.get(mLastEntry.first).size() == 0) {
509134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    mCues.remove(mLastEntry.first);
510134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
511134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
512134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // remove rest of the cues
513134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                Cue cue = mLastEntry.second;
514134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                removeEvent(cue, cue.mStartTimeMs);
515134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (cue.mInnerTimesMs != null) {
516134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    for (long timeMs: cue.mInnerTimesMs) {
517134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        removeEvent(cue, timeMs);
518134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    }
519134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
520134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
521134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
522134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            EntryIterator(SortedMap<Long, ArrayList<Cue>> cues) {
523134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (DEBUG) Log.v(TAG, cues + "");
524134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mRemainingCues = cues;
525134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mLastListIterator = null;
526134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                nextKey();
527134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
528134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
529134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            private void nextKey() {
530134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                do {
531134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    try {
532134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        if (mRemainingCues == null) {
533134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                            throw new NoSuchElementException("");
534134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        }
535134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        mCurrentTimeMs = mRemainingCues.firstKey();
536134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        mListIterator =
537134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                            mRemainingCues.get(mCurrentTimeMs).iterator();
538134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        try {
539134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                            mRemainingCues =
540134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                                mRemainingCues.tailMap(mCurrentTimeMs + 1);
541134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        } catch (IllegalArgumentException e) {
542134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                            mRemainingCues = null;
543134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        }
544134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        mDone = false;
545134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    } catch (NoSuchElementException e) {
546134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        mDone = true;
547134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        mRemainingCues = null;
548134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        mListIterator = null;
549134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        return;
550134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    }
551134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                } while (!mListIterator.hasNext());
552134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
553134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
554134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            private long mCurrentTimeMs;
555134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            private Iterator<Cue> mListIterator;
556134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            private boolean mDone;
557134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            private SortedMap<Long, ArrayList<Cue>> mRemainingCues;
558134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            private Iterator<Cue> mLastListIterator;
559134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            private Pair<Long, Cue> mLastEntry;
560134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
561134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
562134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        CueList() {
563134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mCues = new TreeMap<Long, ArrayList<Cue>>();
564134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
565134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
566134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
567134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    static class Cue {
568134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public long mStartTimeMs;
569134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public long mEndTimeMs;
570134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public long[] mInnerTimesMs;
571134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public long mRunID;
572134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
573134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public Cue mNextInRun;
574134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
575134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        /**
576134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         * Called to inform current timeMs to the cue
577134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         */
578134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public void onTime(long timeMs) { }
579134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
580134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
581134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    /** update mRunsByEndTime (with default end time) */
582134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    protected void finishedRun(long runID) {
583134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (runID != 0 && runID != ~0) {
584134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            Run run = mRunsByID.get(runID);
585134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (run != null) {
586134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                run.storeByEndTimeMs(mRunsByEndTime);
587134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
588134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
589134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
590134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
591134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    /** update mRunsByEndTime with given end time */
592134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    public void setRunDiscardTimeMs(long runID, long timeMs) {
593134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (runID != 0 && runID != ~0) {
594134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            Run run = mRunsByID.get(runID);
595134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (run != null) {
596134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                run.mEndTimeMs = timeMs;
597134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                run.storeByEndTimeMs(mRunsByEndTime);
598134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
599134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
600134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
601134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
602134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    /** whether this is a text track who fires events instead getting rendered */
603134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    public int getTrackType() {
604134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        return getRenderingWidget() == null
605134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                ? TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT
606134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                : TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE;
607134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
608134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
609134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
610134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static class Run {
611134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public Cue mFirstCue;
612134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public Run mNextRunAtEndTimeMs;
613134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public Run mPrevRunAtEndTimeMs;
614134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public long mEndTimeMs = -1;
615134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public long mRunID = 0;
616134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private long mStoredEndTimeMs = -1;
617134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
618134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public void storeByEndTimeMs(LongSparseArray<Run> runsByEndTime) {
619134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            // remove old value if any
620134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            int ix = runsByEndTime.indexOfKey(mStoredEndTimeMs);
621134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (ix >= 0) {
622134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (mPrevRunAtEndTimeMs == null) {
623134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    assert (this == runsByEndTime.valueAt(ix));
624134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    if (mNextRunAtEndTimeMs == null) {
625134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        runsByEndTime.removeAt(ix);
626134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    } else {
627134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        runsByEndTime.setValueAt(ix, mNextRunAtEndTimeMs);
628134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    }
629134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
630134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                removeAtEndTimeMs();
631134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
632134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
633134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            // add new value
634134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (mEndTimeMs >= 0) {
635134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mPrevRunAtEndTimeMs = null;
636134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mNextRunAtEndTimeMs = runsByEndTime.get(mEndTimeMs);
637134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (mNextRunAtEndTimeMs != null) {
638134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    mNextRunAtEndTimeMs.mPrevRunAtEndTimeMs = this;
639134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
640134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                runsByEndTime.put(mEndTimeMs, this);
641134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mStoredEndTimeMs = mEndTimeMs;
642134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
643134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
644134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
645134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public void removeAtEndTimeMs() {
646134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            Run prev = mPrevRunAtEndTimeMs;
647134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
648134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (mPrevRunAtEndTimeMs != null) {
649134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mPrevRunAtEndTimeMs.mNextRunAtEndTimeMs = mNextRunAtEndTimeMs;
650134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mPrevRunAtEndTimeMs = null;
651134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
652134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (mNextRunAtEndTimeMs != null) {
653134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mNextRunAtEndTimeMs.mPrevRunAtEndTimeMs = prev;
654134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mNextRunAtEndTimeMs = null;
655134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
656134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
657134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
658134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
659134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    /**
660134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * Interface for rendering subtitles onto a Canvas.
661134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     */
662134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    public interface RenderingWidget {
663134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        /**
664134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         * Sets the widget's callback, which is used to send updates when the
665134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         * rendered data has changed.
666134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         *
667134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         * @param callback update callback
668134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         */
669134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        void setOnChangedListener(OnChangedListener callback);
670134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
671134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        /**
672134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         * Sets the widget's size.
673134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         *
674134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         * @param width width in pixels
675134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         * @param height height in pixels
676134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         */
677134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        void setSize(int width, int height);
678134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
679134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        /**
680134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         * Sets whether the widget should draw subtitles.
681134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         *
682134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         * @param visible true if subtitles should be drawn, false otherwise
683134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         */
684134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        void setVisible(boolean visible);
685134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
686134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        /**
687134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         * Renders subtitles onto a {@link Canvas}.
688134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         *
689134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         * @param c canvas on which to render subtitles
690134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         */
691134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        void draw(Canvas c);
692134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
693134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        /**
694134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         * Called when the widget is attached to a window.
695134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         */
696134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        void onAttachedToWindow();
697134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
698134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        /**
699134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         * Called when the widget is detached from a window.
700134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         */
701134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        void onDetachedFromWindow();
702134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
703134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        /**
704134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         * Callback used to send updates about changes to rendering data.
705134545d2d8429615552931ef50a15ba2994d03f8Insun Kang         */
706134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public interface OnChangedListener {
707134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            /**
708134545d2d8429615552931ef50a15ba2994d03f8Insun Kang             * Called when the rendering data has changed.
709134545d2d8429615552931ef50a15ba2994d03f8Insun Kang             *
710134545d2d8429615552931ef50a15ba2994d03f8Insun Kang             * @param renderingWidget the widget whose data has changed
711134545d2d8429615552931ef50a15ba2994d03f8Insun Kang             */
712134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            void onChanged(RenderingWidget renderingWidget);
713134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
714134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
715134545d2d8429615552931ef50a15ba2994d03f8Insun Kang}
716