+ if (video_stream == nullptr) {
+ // No queue, just wait until the right time and then show the frame.
+ new_clip_changed.wait_until(lock, next_frame_start, [this]{
+ return new_clip_ready || override_stream_idx != -1;
+ });
+ } else {
+ // If the queue is full (which is really the state we'd like to be in),
+ // wait until there's room for one more frame (ie., one was output from
+ // VideoStream), or until or until there's a new clip we're supposed to play.
+ //
+ // In this case, we don't sleep until next_frame_start; the displaying is
+ // done by the queue.
+ new_clip_changed.wait(lock, [this]{
+ if (num_queued_frames < max_queued_frames) {
+ return true;
+ }
+ return new_clip_ready || override_stream_idx != -1;
+ });
+ }
+ if (new_clip_ready) {
+ if (video_stream != nullptr) {
+ lock.unlock(); // Urg.
+ video_stream->clear_queue();
+ lock.lock();
+ }
+ got_next_clip = false;
+ goto wait_for_clip;
+ }
+ if (override_stream_idx != -1) {
+ stream_idx = override_stream_idx;
+ override_stream_idx = -1;
+ continue;
+ }
+ }
+
+ if (frame_lower.pts == frame_upper.pts) {
+ auto display_func = [this, primary_stream_idx, frame_lower, secondary_frame, fade_alpha]{
+ destination->setFrame(primary_stream_idx, frame_lower, secondary_frame, fade_alpha);
+ };
+ if (video_stream == nullptr) {
+ display_func();
+ } else {
+ if (secondary_stream_idx == -1) {
+ video_stream->schedule_original_frame(
+ next_frame_start, pts, display_func, QueueSpotHolder(this),
+ frame_lower);
+ } else {
+ assert(secondary_frame.pts != -1);
+ video_stream->schedule_faded_frame(next_frame_start, pts, display_func,
+ QueueSpotHolder(this), frame_lower,
+ secondary_frame, fade_alpha);
+ }
+ }
+ continue;
+ }
+
+ // Snap to input frame: If we can do so with less than 1% jitter
+ // (ie., move less than 1% of an _output_ frame), do so.
+ // TODO: Snap secondary (fade-to) clips in the same fashion.
+ bool snapped = false;
+ for (int64_t snap_pts : { frame_lower.pts, frame_upper.pts }) {
+ double snap_pts_as_frameno = (snap_pts - in_pts_origin) * output_framerate / TIMEBASE / speed;
+ if (fabs(snap_pts_as_frameno - frameno) < 0.01) {
+ FrameOnDisk snap_frame = frame_lower;
+ snap_frame.pts = snap_pts;
+ auto display_func = [this, primary_stream_idx, snap_frame, secondary_frame, fade_alpha]{
+ destination->setFrame(primary_stream_idx, snap_frame, secondary_frame, fade_alpha);
+ };
+ if (video_stream == nullptr) {
+ display_func();
+ } else {
+ if (secondary_stream_idx == -1) {
+ video_stream->schedule_original_frame(
+ next_frame_start, pts, display_func,
+ QueueSpotHolder(this), snap_frame);
+ } else {
+ assert(secondary_frame.pts != -1);
+ video_stream->schedule_faded_frame(
+ next_frame_start, pts, display_func, QueueSpotHolder(this),
+ snap_frame, secondary_frame, fade_alpha);
+ }
+ }
+ in_pts_origin += snap_pts - in_pts;
+ snapped = true;
+ break;
+ }
+ }
+ if (snapped) {
+ continue;
+ }
+
+ if (time_behind >= milliseconds(100)) {
+ fprintf(stderr, "WARNING: %ld ms behind, dropping an interpolated frame.\n",
+ lrint(1e3 * duration<double>(time_behind).count()));
+ continue;