\documentclass{article}
\renewcommand{\c}[1]{\texttt{#1}}
\begin{document}

There are two sources of video gaps:

\begin{enumerate}
\item Discontiguous outputs from \c{FFmpegDecoder} --- it makes sense
  to fix these when the frame after the gap is seen, as that's the
  first time we know about the gap.  For example, it emits frame~1
  then frame~3; when we see 3 we should emit 2 to fill the gap.
\item Periods where there is no video content --- these could be long,
  so you can't just wait for the frame after the gap.
\end{enumerate}

Two solutions suggest themselves for the period of no video content:

\begin{enumerate}
\item Create `black' \c{Piece}s to act as dummy content and emit black
  frames as required.
\item Fix it in \c{Player::pass()}.
\end{enumerate}

Dummy pieces feels like a nice solution but quite wordy as you need a
hierarchy of \c{Piece}s with virtual functions and so on.

If we can trust \c{Decoder::position()} we know the earliest time that
a decoder will emit data when it is next \c{pass()}ed.  If this is
more than one frame since the last video frame emitted we know we need
to emit a black frame.  This much seems straightforward.

Things appear to get harder with seeking.  There are two paths here:

\begin{enumerate}
\item Seeking into the middle of some video content.
\item Seeking into some empty space.
\end{enumerate}

and also differences between accurate and inaccurate seek.

Let's take them case-by-case:

\begin{enumerate}
  \item \emph{Accurate seek into content} --- we should not fill
    anything since the correct data will be generated, in time, by
    \c{pass()}.
  \item \emph{Accurate seek into space} --- we should fill up to the
    earliest decoder position.
  \item \emph{Inaccurate seek into content} --- we should not fill
    anything since \c{pass()} will generate something at some
    unknown time.
  \item \emph{Inaccurate seek into space} --- we should fill up to
    the earliest decoder position from the seek time.
\end{enumerate}

\end{document}
