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