Class SubtitleCues

java.lang.Object
limn.video.ffmpeg.SubtitleCues

public final class SubtitleCues extends Object
The cues of a container's selected subtitle track, asked for by position: what is on screen at this moment.

This is the whole of the subtitle SPI. It hands over text and timing and nothing else: no widget, no style, no placement, and no opinion about whether a viewer wants subtitles at all. Drawing a cue is the application's, with whatever text stack it already uses:


 media.selectSubtitles(0);
 // ... and in the paint, with the time the player is showing:
 for (SubtitleCues.Cue cue : media.subtitles().activeAt(player.positionMicros())) {
     drawCentredNearTheBottom(cue.text());
 }
 

Why by position, and what follows from it

A picture is presented at an instant; a cue occupies an interval, and "what is on screen at t" is the only question an application actually has. Reading cues the way pictures are read would hand every caller the same thirty lines (hold them, drop them on a seek, pick the current one), and the seek half is the half that is easy to get wrong.

Cues follow the pictures. This object never demultiplexes: it decodes what the container has already read on the video track's behalf. A subtitle track is silent between lines, so reading forward to find the next cue would read through the whole gap (minutes of a film) to answer a question about now. The consequence is worth stating plainly: a container whose video nobody is reading produces no cues. A player running normally reads far enough ahead that a cue is decoded well before the picture it belongs to is shown.

A seek empties the window. Whenever the container is really repositioned the cues held here stop describing the film, and they are dropped before the next answer rather than lingering over the new position. One artefact survives that and cannot be removed: a cue that straddles the target (begins before it and ends after it) is only recovered if its packet lies after the point the container landed on. A seek lands at or before its target and decodes forward, so it usually is; when it is not, the next cue is the first one seen.

Costs

activeAt(long) returns the same list instance for as long as the active set has not changed, so a paint loop polling every frame allocates nothing in the steady state. The list is unmodifiable and is never mutated in place; a new one appears only when a cue starts or ends.

Cues that ended well before the last time asked about are discarded, so a two-hour film does not accumulate its whole script. Asking about a time far behind the one asked about last is therefore answered from what is still held, which is what a seek is for.

Threads

Any thread, one at a time, because every entry point is synchronised on this object. In practice that is whichever thread paints. This is not the video decode thread's: the pictures and the cues are pulled by different callers, and the container is what makes that safe.

  • Field Details

    • END_UNKNOWN

      public static final long END_UNKNOWN
      What SubtitleCues.Cue.endMicros() answers for a cue whose container stated no duration: it is shown until the next cue begins, and no next cue has been read yet. Deliberately the largest long, so that an ordinary micros < cue.endMicros() treats it as still on screen rather than as already gone.
      See Also:
  • Method Details

    • activeAt

      public List<SubtitleCues.Cue> activeAt(long micros)
      The cues on screen at micros.

      Usually empty or one. More than one is a container showing two lines that were authored separately; they arrive in the order the file states them, which is the order to draw them in.

      Parameters:
      micros - a presentation time, on the same timeline as VideoFrame.ptsMicros()
      Returns:
      an unmodifiable list, never null. The same instance as the last call returned whenever the active set has not changed, so this may be called every frame
    • held

      public List<SubtitleCues.Cue> held()
      Returns:
      every cue currently held, in start order: those still to come, the ones on screen, and the recent past that has not been discarded yet. For a diagnostic and for a caller that wants to draw a strip of what is ahead; activeAt(long) is what a paint asks