Skip to content

Commit

Permalink
Suppress (and log) subtitle errors in SubtitleTranscodingTrackOutput
Browse files Browse the repository at this point in the history
This is equivalent to the error suppression for legacy subtitles in
`TextRenderer`:
https://github.com/androidx/media/blob/76088cd6af7f263aba238b7a48d64bd4f060cb8b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/TextRenderer.java#L357-L359

This new suppression only affects errors thrown from files with
subtitles muxed together with audio/video. Standalone subtitle
files, and containers containing only text tracks, are handled
by the existing error suppression/reporting added in
49dec5d.

Issue: #2052
PiperOrigin-RevId: 718930243
  • Loading branch information
icbaker authored and copybara-github committed Jan 23, 2025
1 parent 6b54372 commit a7a5d6e
Show file tree
Hide file tree
Showing 5 changed files with 602 additions and 6 deletions.
3 changes: 3 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@
* Fix `IllegalStateException` when an SSA file contains a cue with zero
duration (start and end time equal)
([#2052](https://github.com/androidx/media/issues/2052)).
* Suppress (and log) subtitle parsing errors when subtitles are muxed into
the same container as audio and video
([#2052](https://github.com/androidx/media/issues/2052)).
* Metadata:
* Image:
* DataSource:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,40 @@ public void onLoadError(
applicationContext, playbackOutput, "playbackdumps/subtitles/sideloaded-error.mp4.dump");
}

// TODO: b/391362063 - Assert that this error gets propagated out after that is implemented.
@Test
public void muxedSubtitleParsingError_playbackContinues() throws Exception {
Context applicationContext = ApplicationProvider.getApplicationContext();
CapturingRenderersFactory capturingRenderersFactory =
new CapturingRenderersFactory(applicationContext);
ExoPlayer player =
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
.setMediaSourceFactory(
new DefaultMediaSourceFactory(applicationContext)
.setSubtitleParserFactory(
new ThrowingSubtitleParserFactory(
() -> new IllegalStateException("test subtitle parsing error"))))
.build();
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
player.setVideoSurface(surface);
PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
MediaItem mediaItem =
new MediaItem.Builder().setUri("asset:///media/mkv/sample_with_srt.mkv").build();

player.setMediaItem(mediaItem);
player.prepare();
run(player).untilState(Player.STATE_READY);
run(player).untilFullyBuffered();
player.play();
run(player).untilState(Player.STATE_ENDED);
player.release();
surface.release();

DumpFileAsserts.assertOutput(
applicationContext, playbackOutput, "playbackdumps/subtitles/muxed-parsing-error.mkv.dump");
}

/**
* An {@link ExtractorsFactory} which creates a {@link FragmentedMp4Extractor} configured to
* extract a single additional caption track.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public final class SubtitleTranscodingExtractorOutput implements ExtractorOutput
private final SubtitleParser.Factory subtitleParserFactory;
private final SparseArray<SubtitleTranscodingTrackOutput> textTrackOutputs;

private boolean hasNonTextTracks;

public SubtitleTranscodingExtractorOutput(
ExtractorOutput delegate, SubtitleParser.Factory subtitleParserFactory) {
this.delegate = delegate;
Expand All @@ -64,6 +66,7 @@ public void resetSubtitleParsers() {
@Override
public TrackOutput track(int id, @C.TrackType int type) {
if (type != C.TRACK_TYPE_TEXT) {
hasNonTextTracks = true;
return delegate.track(id, type);
}
SubtitleTranscodingTrackOutput existingTrackOutput = textTrackOutputs.get(id);
Expand All @@ -79,6 +82,11 @@ public TrackOutput track(int id, @C.TrackType int type) {
@Override
public void endTracks() {
delegate.endTracks();
if (hasNonTextTracks) {
for (int i = 0; i < textTrackOutputs.size(); i++) {
textTrackOutputs.valueAt(i).shouldSuppressParsingErrors(true);
}
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import androidx.media3.common.DataReader;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.Util;
import androidx.media3.extractor.TrackOutput;
Expand All @@ -40,6 +41,8 @@
*/
/* package */ final class SubtitleTranscodingTrackOutput implements TrackOutput {

private static final String TAG = "SubtitleTranscodingTO";

private final TrackOutput delegate;
private final SubtitleParser.Factory subtitleParserFactory;
private final CueEncoder cueEncoder;
Expand All @@ -50,6 +53,7 @@
private byte[] sampleData;
@Nullable private SubtitleParser currentSubtitleParser;
private @MonotonicNonNull Format currentFormat;
private boolean shouldSuppressParsingErrors;

public SubtitleTranscodingTrackOutput(
TrackOutput delegate, SubtitleParser.Factory subtitleParserFactory) {
Expand All @@ -68,6 +72,16 @@ public void resetSubtitleParser() {
}
}

/**
* Sets whether to suppress parsing errors thrown during the transcoding in {@link
* #sampleMetadata}.
*
* <p>Defaults to {@code false}.
*/
public void shouldSuppressParsingErrors(boolean shouldSuppressParsingErrors) {
this.shouldSuppressParsingErrors = shouldSuppressParsingErrors;
}

// TrackOutput implementation

@Override
Expand Down Expand Up @@ -143,12 +157,21 @@ public void sampleMetadata(
checkArgument(cryptoData == null, "DRM on subtitles is not supported");

int sampleStart = sampleDataEnd - offset - size;
currentSubtitleParser.parse(
sampleData,
sampleStart,
size,
SubtitleParser.OutputOptions.allCues(),
cuesWithTiming -> outputSample(cuesWithTiming, timeUs, flags));
try {
currentSubtitleParser.parse(
sampleData,
sampleStart,
size,
SubtitleParser.OutputOptions.allCues(),
cuesWithTiming -> outputSample(cuesWithTiming, timeUs, flags));
} catch (RuntimeException e) {
if (shouldSuppressParsingErrors) {
// TODO: b/391362063 - Propagate this error out in a non-fatal way.
Log.w(TAG, "Parsing subtitles failed, ignoring sample.", e);
} else {
throw e;
}
}
sampleDataStart = sampleStart + size;
if (sampleDataStart == sampleDataEnd) {
// The array is now empty, so we can move the start and end pointers back to the start.
Expand Down
Loading

0 comments on commit a7a5d6e

Please sign in to comment.