LegacySubtitleUtil.java
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.extractor.text;
import androidx.media3.common.C;
import androidx.media3.common.text.Cue;
import androidx.media3.common.util.Consumer;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.extractor.text.SubtitleParser.OutputOptions;
import java.util.List;
/** Utility methods for working with legacy {@link Subtitle} objects. */
@UnstableApi
public class LegacySubtitleUtil {
private LegacySubtitleUtil() {}
/**
* Converts a {@link Subtitle} to a list of {@link CuesWithTiming} representing it, emitted to
* {@code output}.
*
* <p>This may only be called with {@link Subtitle} instances where the first event is non-empty
* and the last event is an empty cue list.
*/
public static void toCuesWithTiming(
Subtitle subtitle, OutputOptions outputOptions, Consumer<CuesWithTiming> output) {
int startIndex = getStartIndex(subtitle, outputOptions);
boolean startedInMiddleOfCue = false;
if (outputOptions.startTimeUs != C.TIME_UNSET) {
List<Cue> cuesAtStartTime = subtitle.getCues(outputOptions.startTimeUs);
long firstEventTimeUs = subtitle.getEventTime(startIndex);
if (!cuesAtStartTime.isEmpty()
&& startIndex < subtitle.getEventTimeCount()
&& outputOptions.startTimeUs < firstEventTimeUs) {
output.accept(
new CuesWithTiming(
cuesAtStartTime,
outputOptions.startTimeUs,
firstEventTimeUs - outputOptions.startTimeUs));
startedInMiddleOfCue = true;
}
}
for (int i = startIndex; i < subtitle.getEventTimeCount(); i++) {
outputSubtitleEvent(subtitle, i, output);
}
if (outputOptions.outputAllCues) {
int endIndex = startedInMiddleOfCue ? startIndex - 1 : startIndex;
for (int i = 0; i < endIndex; i++) {
outputSubtitleEvent(subtitle, i, output);
}
if (startedInMiddleOfCue) {
output.accept(
new CuesWithTiming(
subtitle.getCues(outputOptions.startTimeUs),
subtitle.getEventTime(endIndex),
outputOptions.startTimeUs - subtitle.getEventTime(endIndex)));
}
}
}
private static int getStartIndex(Subtitle subtitle, OutputOptions outputOptions) {
if (outputOptions.startTimeUs == C.TIME_UNSET) {
return 0;
}
int nextEventTimeIndex = subtitle.getNextEventTimeIndex(outputOptions.startTimeUs);
if (nextEventTimeIndex == C.INDEX_UNSET) {
return subtitle.getEventTimeCount();
}
if (nextEventTimeIndex > 0
&& subtitle.getEventTime(nextEventTimeIndex - 1) == outputOptions.startTimeUs) {
nextEventTimeIndex--;
}
return nextEventTimeIndex;
}
private static void outputSubtitleEvent(
Subtitle subtitle, int eventIndex, Consumer<CuesWithTiming> output) {
long startTimeUs = subtitle.getEventTime(eventIndex);
List<Cue> cuesForThisStartTime = subtitle.getCues(startTimeUs);
if (cuesForThisStartTime.isEmpty()) {
// An empty cue list has already been implicitly encoded in the duration of the previous
// sample.
return;
} else if (eventIndex == subtitle.getEventTimeCount() - 1) {
// The last cue list must be empty
throw new IllegalStateException();
}
// It's safe to inspect element i+1, because we already exited the loop above if
// i == getEventTimeCount() - 1.
long durationUs = subtitle.getEventTime(eventIndex + 1) - subtitle.getEventTime(eventIndex);
if (durationUs > 0) {
output.accept(new CuesWithTiming(cuesForThisStartTime, startTimeUs, durationUs));
}
}
}