+
+void Player::override_angle(unsigned stream_idx)
+{
+ // Corner case: If a new clip is waiting to be played, change its stream and then we're done.
+ {
+ unique_lock<mutex> lock(queue_state_mu);
+ if (new_clip_ready) {
+ lock_guard<mutex> lock2(mu);
+ current_stream_idx = stream_idx;
+ return;
+ }
+ }
+
+ // If we are playing a clip, set override_stream_idx, and the player thread will
+ // pick it up and change its internal index.
+ {
+ unique_lock<mutex> lock(queue_state_mu);
+ if (playing) {
+ override_stream_idx = stream_idx;
+ new_clip_changed.notify_all();
+ }
+ }
+
+ // OK, so we're standing still, presumably at the end of a clip.
+ // Look at the current pts_out (if it exists), and show the closest
+ // thing we've got.
+ int64_t pts_out;
+ {
+ lock_guard<mutex> lock(mu);
+ if (current_clip.pts_out < 0) {
+ return;
+ }
+ pts_out = current_clip.pts_out;
+ }
+
+ lock_guard<mutex> lock(frame_mu);
+ auto it = upper_bound(frames[stream_idx].begin(), frames[stream_idx].end(), pts_out);
+ if (it == frames[stream_idx].end()) {
+ return;
+ }
+ destination->setFrame(stream_idx, *it);
+}
+
+void Player::open_output_stream()
+{
+ AVFormatContext *avctx = avformat_alloc_context();
+ avctx->oformat = av_guess_format("nut", nullptr, nullptr);
+
+ uint8_t *buf = (uint8_t *)av_malloc(MUX_BUFFER_SIZE);
+ avctx->pb = avio_alloc_context(buf, MUX_BUFFER_SIZE, 1, this, nullptr, nullptr, nullptr);
+ avctx->pb->write_data_type = &Player::write_packet2_thunk;
+ avctx->pb->ignore_boundary_point = 1;
+
+ Mux::Codec video_codec = Mux::CODEC_MJPEG;
+
+ avctx->flags = AVFMT_FLAG_CUSTOM_IO;
+
+ string video_extradata;
+
+ constexpr int width = 1280, height = 720; // Doesn't matter for MJPEG.
+ stream_mux.reset(new Mux(avctx, width, height, video_codec, video_extradata, /*audio_codec_parameters=*/nullptr, COARSE_TIMEBASE,
+ /*write_callback=*/nullptr, Mux::WRITE_FOREGROUND, {}));
+}
+
+int Player::write_packet2_thunk(void *opaque, uint8_t *buf, int buf_size, AVIODataMarkerType type, int64_t time)
+{
+ Player *player = (Player *)opaque;
+ return player->write_packet2(buf, buf_size, type, time);
+}
+
+int Player::write_packet2(uint8_t *buf, int buf_size, AVIODataMarkerType type, int64_t time)
+{
+ if (type == AVIO_DATA_MARKER_SYNC_POINT || type == AVIO_DATA_MARKER_BOUNDARY_POINT) {
+ seen_sync_markers = true;
+ } else if (type == AVIO_DATA_MARKER_UNKNOWN && !seen_sync_markers) {
+ // We don't know if this is a keyframe or not (the muxer could
+ // avoid marking it), so we just have to make the best of it.
+ type = AVIO_DATA_MARKER_SYNC_POINT;
+ }
+
+ if (type == AVIO_DATA_MARKER_HEADER) {
+ stream_mux_header.append((char *)buf, buf_size);
+ global_httpd->set_header(stream_mux_header);
+ } else {
+ global_httpd->add_data((char *)buf, buf_size, type == AVIO_DATA_MARKER_SYNC_POINT, time, AVRational{ AV_TIME_BASE, 1 });
+ }
+ return buf_size;
+}