Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Subtitle parsing broken in 1.4.0-alpha02+ #1516

Open
1 task
JonWatson opened this issue Jul 1, 2024 · 17 comments
Open
1 task

Subtitle parsing broken in 1.4.0-alpha02+ #1516

JonWatson opened this issue Jul 1, 2024 · 17 comments
Assignees
Labels

Comments

@JonWatson
Copy link

JonWatson commented Jul 1, 2024

Version

Media3 main branch

More version details

Subtitle parsing for a given DASH asset (DRM protected) works fine in 1.4.0-alpha01 but in 1.4.0-alpha02 and 1.4.0-beta01 the playback fails with the following exception. Without provding the asset, can you help figure this out? Any info from the DASH manifest that I could provide to help? Again, same asset plays fine and displays subtitles in 1.4.0-alpha01

androidx.media3.exoplayer.ExoPlaybackException: Source error
      at androidx.media3.exoplayer.ExoPlayerImplInternal.handleIoException(ExoPlayerImplInternal.java:741)
      at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:711)
      at android.os.Handler.dispatchMessage(Handler.java:102)
      at android.os.Looper.loop(Looper.java:223)
      at android.os.HandlerThread.run(HandlerThread.java:67)
  Caused by: androidx.media3.common.ParserException: SubtitleParser failed.{contentIsMalformed=true, dataType=1}
      at androidx.media3.extractor.text.SubtitleExtractor.parseAndWriteToOutput(SubtitleExtractor.java:258)
      at androidx.media3.extractor.text.SubtitleExtractor.read(SubtitleExtractor.java:161)
      at androidx.media3.exoplayer.source.chunk.BundledChunkExtractor.read(BundledChunkExtractor.java:241)
      at androidx.media3.exoplayer.source.chunk.ContainerMediaChunk.load(ContainerMediaChunk.java:132)
      at androidx.media3.exoplayer.upstream.Loader$LoadTask.run(Loader.java:421)
      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
      at java.lang.Thread.run(Thread.java:923)
  Caused by: java.lang.IllegalArgumentException
      at androidx.media3.common.util.Assertions.checkArgument(Assertions.java:40)
      at androidx.media3.extractor.text.webvtt.WebvttSubtitle.getEventTime(WebvttSubtitle.java:63)
      at androidx.media3.extractor.text.LegacySubtitleUtil.toCuesWithTiming(LegacySubtitleUtil.java:44)
      at androidx.media3.extractor.text.webvtt.WebvttParser.parse(WebvttParser.java:108)
      at androidx.media3.extractor.text.SubtitleParser.parse(SubtitleParser.java:152)
      at androidx.media3.extractor.text.SubtitleExtractor.parseAndWriteToOutput(SubtitleExtractor.java:238)
      at androidx.media3.extractor.text.SubtitleExtractor.read(SubtitleExtractor.java:161) 
      at androidx.media3.exoplayer.source.chunk.BundledChunkExtractor.read(BundledChunkExtractor.java:241) 
      at androidx.media3.exoplayer.source.chunk.ContainerMediaChunk.load(ContainerMediaChunk.java:132) 
      at androidx.media3.exoplayer.upstream.Loader$LoadTask.run(Loader.java:421) 
      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) 
      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) 
      at java.lang.Thread.run(Thread.java:923) 

Devices that reproduce the issue

Nvidia Shield TV
Pixel 7 Pro

Devices that do not reproduce the issue

None known

Reproducible in the demo app?

Yes

Reproduction steps

player.trackSelectionParameters =
  player.trackSelectionParameters.buildUpon()
    .setTrackTypeDisabled(TRACK_TYPE_TEXT, !showSubtitles)
    .setPreferredTextLanguage("eng")
    .build()

Expected result

Playback succeeds even when displaying subtitles

Actual result

Receive ERROR_CODE_PARSING_CONTAINER_MALFORMED 3001 during playback when attempting to display subtitles

Media

Private URL. Please let me know if there is some info I can provide from the DASH manifest

Bug Report

@icbaker
Copy link
Collaborator

icbaker commented Jul 1, 2024

1.4.0-alpha02 moved subtitle parsing to happen during extraction by default (instead of during rendering). I wonder if your problem is resolved by moving it back to during rendering? You can do this by calling both MediaSource.Factory.experimentalParseSubtitlesDuringExtraction(false) and TextRenderer.experimentalSetLegacyDecodingEnabled(true) (see also the release notes: https://github.com/androidx/media/releases/tag/1.4.0-alpha02).

Looking at the code, it seems the exception you're seeing is basically an index out of bounds issue:

public long getEventTime(int index) {
Assertions.checkArgument(index >= 0);
Assertions.checkArgument(index < sortedCueTimesUs.length);
return sortedCueTimesUs[index];
}

I think I can see the issue with the logic here - we need to either not return subtitle.getEventTimeCount() from getStartIndex(...) or check that startIndex < subtitle.getEventTimeCount() before calling long firstEventTimeUs = subtitle.getEventTime(startIndex). I will have a go at writing a test case to reproduce the stack trace you're seeing, and see if this change resolves it.

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));

@icbaker
Copy link
Collaborator

icbaker commented Jul 1, 2024

I've added a test that creates the same stack trace, and sent a change the fixes the code to not crash in this case - I hope to include this in 1.4.0-rc01. Thanks for raising this, and providing stack trace details so we could work out the issue.

copybara-service bot pushed a commit that referenced this issue Jul 1, 2024
Issue: #1516

#cherrypick

PiperOrigin-RevId: 648416119
@icbaker
Copy link
Collaborator

icbaker commented Jul 2, 2024

This should be fixed by the commit above.

@icbaker icbaker closed this as completed Jul 2, 2024
tianyif pushed a commit that referenced this issue Jul 2, 2024
Issue: #1516

#cherrypick

PiperOrigin-RevId: 648416119
(cherry picked from commit 711d18d)
@JonWatson
Copy link
Author

Hello @icbaker

I'm testing this out in 1.4.0 and still see the same problem. You are correct in that this is the place the IllegalArgumentException is getting thrown:

 public long getEventTime(int index) { 
   Assertions.checkArgument(index >= 0); 
   Assertions.checkArgument(index < sortedCueTimesUs.length);  <-------------- HERE
   return sortedCueTimesUs[index]; 
 } 

and it is indeed because the nextEventTimeIndex is C.INDEX_UNSET, which causes the startIndex to be set to the length of the array

   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();        <---------- HERE
    }
    if (nextEventTimeIndex > 0
        && subtitle.getEventTime(nextEventTimeIndex - 1) == outputOptions.startTimeUs) {
      nextEventTimeIndex--;
    }
    return nextEventTimeIndex;
  }

I'm working on changing the setup to try the legacy way and will report on the results there

@JonWatson
Copy link
Author

The array of sortedCueTimesUs is like this from our VTT:

[0, 241000, 241000, 1409000, 1409000, 1442000, 1442000, 1475000, 1475000, 1509000, 1509000, 1576000, 1576000, 1642000, 1642000, 1676000, 1676000, 1742000, 1742000, 1776000, 1776000, 1809000, 1809000, 1843000, 1843000, 2009000, 2009000, 2043000, 2043000, 2076000, 2076000, 2109000, 2109000, 2210000, 2210000, 2243000, 2243000, 2276000, 2276000, 2310000, 2310000, 2610000, 2610000, 2677000, 2677000, 2810000, 2810000, 2844000, 2844000, 2877000, 2877000, 3211000, 3211000, 3244000, 3244000, 3277000, 3277000, 3511000, 3511000, 3544000, 3544000, 3578000, 3578000, 3644000, 3644000, 3678000, 3678000, 3711000, 3711000, 4004000]

But the value we are searching for in Util.binarySearchCeil() is 458500275832 so it seems like our third-party VTTs are not in the expected format somehow...

@JonWatson
Copy link
Author

I'd like to point out that ExoPlayer doesn't crash, everything is caught.

It's just that our playback is broken (we get a 3001) when trying to play with subtitles on

@icbaker
Copy link
Collaborator

icbaker commented Jul 29, 2024

Please can you give us a way to play a stream that reproduces this issue? Without that I'm afraid we're just guessing at the possible cause (and it seems that last time we guessed wrong).

@icbaker
Copy link
Collaborator

icbaker commented Jul 29, 2024

Please either upload it here or send it to android-media-github@google.com with the subject Issue #1516. Please also update this issue to indicate you’ve done this.

@JonWatson
Copy link
Author

Our videos are locked with DRM and our DRM tokens are only good for 10 minutes. I cannot share our source so I think we may be stuck here. It may be that I can provide you with a dynamic URL that will return DRM tokens you can use(along with our custom DRM implementation) but I need to consider this..

@JonWatson
Copy link
Author

Confirmed our subtitles show properly and no playback errors if we use:

MediaSource.Factory.experimentalParseSubtitlesDuringExtraction(false) TextRenderer.experimentalSetLegacyDecodingEnabled(true)

@icbaker
Copy link
Collaborator

icbaker commented Jul 31, 2024

@JonWatson Are you able to share the DASH manifest URL via email, without the DRM license/token info? That may be enough for us to reproduce the issue (since it only concerns subtitles, which presumably aren't encrypted).

@JonWatson
Copy link
Author

Yes I can do that! An example manifest URL has been sent to android-media-github@google.com

copybara-service bot pushed a commit that referenced this issue Aug 7, 2024
This is caused when the requested "output start time" is equal to or
larger than the last event time in a `Subtitle` object.

This resolves the error in Issue: #1516, but subtitles are still not
renderered (probably because the timestamps aren't what we expect
somewhere, but I need to investigate this part further).

#cherrypick

PiperOrigin-RevId: 660462720
@icbaker
Copy link
Collaborator

icbaker commented Aug 8, 2024

Thanks for the test stream, it's been really helpful to look into this issue. I've fixed the IndexOutOfBoundsException with the commit linked above, which solves the playback error, but subtitles are still not shown on your stream.

Looking in your DASH manifest it seems that your subtitles are raw WebVTT (not encapsulated in ISOBMFF / MP4) but are segmented. I believe this combination is not supported by the DASH-IF IOP 5.2.10 (emphasis mine):

Some services store text adaptation sets in stand-alone IMSC1 or WebVTT files, without segmentation or [ISOBMFF] encapsulation.
This document requires:

  • Timecodes in stand-alone text files SHALL be relative to the period start point.
  • @presentationTimeOffset SHALL NOT be present and SHALL be ignored by clients if present.

I appreciate that this stream works on 1.3.1, and not on 1.4.0, so even if this content isn't fully spec compliant it still seems like a regression in the library.

I'm still working on plumbing the right timestamps around to get these subtitles showing up again - I haven't quite got it working yet.

@icbaker
Copy link
Collaborator

icbaker commented Aug 8, 2024

@JonWatson Ah I think the stream stopped working today - would you be able to email a new link so I can continue debugging? Thanks!

@JonWatson
Copy link
Author

Done! Thank you

copybara-service bot pushed a commit that referenced this issue Aug 9, 2024
If the length of the `ExtractorInput` is not known then the
`subtitleData` field is re-sized by 1kB each time
(`SubtitleExtractor.DEFAULT_BUFFER_SIZE`), so the end of the array is
often not populated. This change ensures that `length` is propagated to
`SubtitleParser`, so that implementations don't try and parse the
garbage/zero bytes at the end of the array.

Discovered while investigating Issue: #1516

#cherrypick

PiperOrigin-RevId: 661195634
@icbaker
Copy link
Collaborator

icbaker commented Aug 16, 2024

@JonWatson I've made some hacky local changes which get subtitles showing for your stream, but I need to clean them up before being able to submit (because at the moment they will break other use-cases). Unfortunately I think the stream stopped working again today - would you be able to send a new one?

@JonWatson
Copy link
Author

Thanks @icbaker , new manifest has been emailed

tianyif pushed a commit that referenced this issue Aug 22, 2024
This is caused when the requested "output start time" is equal to or
larger than the last event time in a `Subtitle` object.

This resolves the error in Issue: #1516, but subtitles are still not
renderered (probably because the timestamps aren't what we expect
somewhere, but I need to investigate this part further).

#cherrypick

PiperOrigin-RevId: 660462720
(cherry picked from commit 3763e5b)
tianyif pushed a commit that referenced this issue Aug 22, 2024
If the length of the `ExtractorInput` is not known then the
`subtitleData` field is re-sized by 1kB each time
(`SubtitleExtractor.DEFAULT_BUFFER_SIZE`), so the end of the array is
often not populated. This change ensures that `length` is propagated to
`SubtitleParser`, so that implementations don't try and parse the
garbage/zero bytes at the end of the array.

Discovered while investigating Issue: #1516

#cherrypick

PiperOrigin-RevId: 661195634
(cherry picked from commit f37f969)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants