Methods Summary |
---|
protected synchronized boolean | addCue(android.media.SubtitleTrack$Cue cue)
mCues.add(cue);
if (cue.mRunID != 0) {
Run run = mRunsByID.get(cue.mRunID);
if (run == null) {
run = new Run();
mRunsByID.put(cue.mRunID, run);
run.mEndTimeMs = cue.mEndTimeMs;
} else if (run.mEndTimeMs < cue.mEndTimeMs) {
run.mEndTimeMs = cue.mEndTimeMs;
}
// link-up cues in the same run
cue.mNextInRun = run.mFirstCue;
run.mFirstCue = cue;
}
// if a cue is added that should be visible, need to refresh view
long nowMs = -1;
if (mTimeProvider != null) {
try {
nowMs = mTimeProvider.getCurrentTimeUs(
false /* precise */, true /* monotonic */) / 1000;
} catch (IllegalStateException e) {
// handle as it we are not playing
}
}
if (DEBUG) Log.v(TAG, "mVisible=" + mVisible + ", " +
cue.mStartTimeMs + " <= " + nowMs + ", " +
cue.mEndTimeMs + " >= " + mLastTimeMs);
if (mVisible &&
cue.mStartTimeMs <= nowMs &&
// we don't trust nowMs, so check any cue since last callback
cue.mEndTimeMs >= mLastTimeMs) {
if (mRunnable != null) {
mHandler.removeCallbacks(mRunnable);
}
final SubtitleTrack track = this;
final long thenMs = nowMs;
mRunnable = new Runnable() {
@Override
public void run() {
// even with synchronized, it is possible that we are going
// to do multiple updates as the runnable could be already
// running.
synchronized (track) {
mRunnable = null;
updateActiveCues(true, thenMs);
updateView(mActiveCues);
}
}
};
// delay update so we don't update view on every cue. TODO why 10?
if (mHandler.postDelayed(mRunnable, 10 /* delay */)) {
if (DEBUG) Log.v(TAG, "scheduling update");
} else {
if (DEBUG) Log.w(TAG, "failed to schedule subtitle view update");
}
return true;
}
if (mVisible &&
cue.mEndTimeMs >= mLastTimeMs &&
(cue.mStartTimeMs < mNextScheduledTimeMs ||
mNextScheduledTimeMs < 0)) {
scheduleTimedEvents();
}
return false;
|
protected synchronized void | clearActiveCues()
if (DEBUG) Log.v(TAG, "Clearing " + mActiveCues.size() + " active cues");
mActiveCues.clear();
mLastUpdateTimeMs = -1;
|
protected void | finalize()
/* remove all cues (untangle all cross-links) */
int size = mRunsByEndTime.size();
for(int ix = size - 1; ix >= 0; ix--) {
removeRunsByEndTimeIndex(ix);
}
super.finalize();
|
protected void | finishedRun(long runID)
if (runID != 0 && runID != ~0) {
Run run = mRunsByID.get(runID);
if (run != null) {
run.storeByEndTimeMs(mRunsByEndTime);
}
}
|
public final MediaFormat | getFormat()
return mFormat;
|
public abstract android.media.SubtitleTrack$RenderingWidget | getRenderingWidget()Called when adding the subtitle rendering widget to the view hierarchy,
as well as when showing or hiding the subtitle track, or when the video
surface position has changed.
|
public void | hide()
if (!mVisible) {
return;
}
if (mTimeProvider != null) {
mTimeProvider.cancelNotifications(this);
}
RenderingWidget renderingWidget = getRenderingWidget();
if (renderingWidget != null) {
renderingWidget.setVisible(false);
}
mVisible = false;
|
public boolean | isTimedText()
return getRenderingWidget() == null;
|
protected void | onData(SubtitleData data)
long runID = data.getStartTimeUs() + 1;
onData(data.getData(), true /* eos */, runID);
setRunDiscardTimeMs(
runID,
(data.getStartTimeUs() + data.getDurationUs()) / 1000);
|
public abstract void | onData(byte[] data, boolean eos, long runID)Called when there is input data for the subtitle track. The
complete subtitle for a track can include multiple whole units
(runs). Each of these units can have multiple sections. The
contents of a run are submitted in sequential order, with eos
indicating the last section of the run. Calls from different
runs must not be intermixed.
|
public void | onSeek(long timeUs)
if (DEBUG) Log.d(TAG, "onSeek " + timeUs);
synchronized (this) {
long timeMs = timeUs / 1000;
updateActiveCues(true, timeMs);
takeTime(timeMs);
}
updateView(mActiveCues);
scheduleTimedEvents();
|
public void | onStop()
synchronized (this) {
if (DEBUG) Log.d(TAG, "onStop");
clearActiveCues();
mLastTimeMs = -1;
}
updateView(mActiveCues);
mNextScheduledTimeMs = -1;
mTimeProvider.notifyAt(MediaTimeProvider.NO_TIME, this);
|
public void | onTimedEvent(long timeUs)
if (DEBUG) Log.d(TAG, "onTimedEvent " + timeUs);
synchronized (this) {
long timeMs = timeUs / 1000;
updateActiveCues(false, timeMs);
takeTime(timeMs);
}
updateView(mActiveCues);
scheduleTimedEvents();
|
private void | removeRunsByEndTimeIndex(int ix)
Run run = mRunsByEndTime.valueAt(ix);
while (run != null) {
Cue cue = run.mFirstCue;
while (cue != null) {
mCues.remove(cue);
Cue nextCue = cue.mNextInRun;
cue.mNextInRun = null;
cue = nextCue;
}
mRunsByID.remove(run.mRunID);
Run nextRun = run.mNextRunAtEndTimeMs;
run.mPrevRunAtEndTimeMs = null;
run.mNextRunAtEndTimeMs = null;
run = nextRun;
}
mRunsByEndTime.removeAt(ix);
|
protected void | scheduleTimedEvents()
/* get times for the next event */
if (mTimeProvider != null) {
mNextScheduledTimeMs = mCues.nextTimeAfter(mLastTimeMs);
if (DEBUG) Log.d(TAG, "sched @" + mNextScheduledTimeMs + " after " + mLastTimeMs);
mTimeProvider.notifyAt(
mNextScheduledTimeMs >= 0 ?
(mNextScheduledTimeMs * 1000) : MediaTimeProvider.NO_TIME,
this);
}
|
public void | setRunDiscardTimeMs(long runID, long timeMs)
if (runID != 0 && runID != ~0) {
Run run = mRunsByID.get(runID);
if (run != null) {
run.mEndTimeMs = timeMs;
run.storeByEndTimeMs(mRunsByEndTime);
}
}
|
public synchronized void | setTimeProvider(MediaTimeProvider timeProvider)
if (mTimeProvider == timeProvider) {
return;
}
if (mTimeProvider != null) {
mTimeProvider.cancelNotifications(this);
}
mTimeProvider = timeProvider;
if (mTimeProvider != null) {
mTimeProvider.scheduleUpdate(this);
}
|
public void | show()
if (mVisible) {
return;
}
mVisible = true;
RenderingWidget renderingWidget = getRenderingWidget();
if (renderingWidget != null) {
renderingWidget.setVisible(true);
}
if (mTimeProvider != null) {
mTimeProvider.scheduleUpdate(this);
}
|
private synchronized void | takeTime(long timeMs)
mLastTimeMs = timeMs;
|
protected synchronized void | updateActiveCues(boolean rebuild, long timeMs)
// out-of-order times mean seeking or new active cues being added
// (during their own timespan)
if (rebuild || mLastUpdateTimeMs > timeMs) {
clearActiveCues();
}
for(Iterator<Pair<Long, Cue> > it =
mCues.entriesBetween(mLastUpdateTimeMs, timeMs).iterator(); it.hasNext(); ) {
Pair<Long, Cue> event = it.next();
Cue cue = event.second;
if (cue.mEndTimeMs == event.first) {
// remove past cues
if (DEBUG) Log.v(TAG, "Removing " + cue);
mActiveCues.remove(cue);
if (cue.mRunID == 0) {
it.remove();
}
} else if (cue.mStartTimeMs == event.first) {
// add new cues
// TRICKY: this will happen in start order
if (DEBUG) Log.v(TAG, "Adding " + cue);
if (cue.mInnerTimesMs != null) {
cue.onTime(timeMs);
}
mActiveCues.add(cue);
} else if (cue.mInnerTimesMs != null) {
// cue is modified
cue.onTime(timeMs);
}
}
/* complete any runs */
while (mRunsByEndTime.size() > 0 &&
mRunsByEndTime.keyAt(0) <= timeMs) {
removeRunsByEndTimeIndex(0); // removes element
}
mLastUpdateTimeMs = timeMs;
|
public abstract void | updateView(java.util.Vector activeCues)Called when the active cues have changed, and the contents of the subtitle
view should be updated.
|