diff --git a/.gitignore b/.gitignore index f284de99..14e500c6 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ .gdb_history node_modules + +re_trace.json diff --git a/Makefile b/Makefile index b319b365..dcd50b9e 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,7 @@ external: mkdir -p external git clone --depth 1 -b main \ https://github.com/baresip/re.git external/re - git clone --depth 1 -b main \ + git clone --depth 1 -b playout_time \ https://github.com/baresip/baresip.git external/baresip cd external/re && \ patch -p1 < ../../patches/re_aubuf_timestamp_order_fix.patch diff --git a/include/mix.h b/include/mix.h index 9a3f25ed..78851927 100644 --- a/include/mix.h +++ b/include/mix.h @@ -61,6 +61,7 @@ struct user { bool video; bool audio; bool hand; + bool solo; }; struct chat { @@ -158,6 +159,7 @@ int slmix_http_listen(struct http_sock **sock, struct mix *mix); void pc_close(struct session *sess); int slmix_session_user_updated(struct session *sess); void slmix_session_video(struct session *sess, bool enable); +void slmix_session_video_solo(struct user *user, bool enable); int slmix_session_speaker(struct session *sess, bool enable); int slmix_session_new(struct mix *mix, struct session **sessp, const struct http_msg *msg); diff --git a/modules/vmix/codec.c b/modules/vmix/codec.c index bb783ca1..6c4c459f 100644 --- a/modules/vmix/codec.c +++ b/modules/vmix/codec.c @@ -12,6 +12,20 @@ static viddec_update_h *decupdh; static struct hash *dec_list; static mtx_t *dec_mtx; +struct enc_pkt { + struct le le; + bool marker; + uint64_t rtp_ts; + struct mbuf *hdr; + size_t hdr_len; + struct mbuf *pld; + size_t pld_len; +}; + +static struct list enc_pktl = LIST_INIT; +static RE_ATOMIC bool last_keyframe = false; +static RE_ATOMIC bool force_keyframe = false; + enum { MAX_PKT_TIME = 50, /**< in [ms] */ }; @@ -34,7 +48,7 @@ struct viddec_state { const char *dev; }; -struct pkt { +struct dec_pkt { uint64_t id; struct le le; struct mbuf *mb; @@ -69,6 +83,53 @@ static void viddec_deref(void *arg) } +static void enc_pkt_deref(void *arg) +{ + struct enc_pkt *pkt = arg; + + mem_deref(pkt->hdr); + mem_deref(pkt->pld); + list_unlink(&pkt->le); +} + + +/* Copy encoded and packetized codec */ +static int enc_packet_h(bool marker, uint64_t rtp_ts, const uint8_t *hdr, + size_t hdr_len, const uint8_t *pld, size_t pld_len, + const struct video *vid) +{ + (void)vid; + int err; + + if (!hdr || !pld) + return EINVAL; + + struct enc_pkt *pkt = + mem_zalloc(sizeof(struct enc_pkt), enc_pkt_deref); + if (!pkt) + return ENOMEM; + + pkt->marker = marker; + pkt->rtp_ts = rtp_ts; + + pkt->hdr = mbuf_alloc(hdr_len); + pkt->pld = mbuf_alloc(pld_len); + + err = mbuf_write_mem(pkt->hdr, hdr, hdr_len); + err |= mbuf_write_mem(pkt->pld, pld, pld_len); + + if (err) + return err; + + mbuf_set_pos(pkt->hdr, 0); + mbuf_set_pos(pkt->pld, 0); + + list_append(&enc_pktl, &pkt->le, pkt); + + return 0; +} + + static int enc_update(struct videnc_state **vesp, const struct vidcodec *vc, struct videnc_param *prm, const char *fmtp, videnc_packet_h *pkth, const struct video *vid) @@ -86,8 +147,8 @@ static int enc_update(struct videnc_state **vesp, const struct vidcodec *vc, ves->vid = vid; ves->pkth = pkth; - err = encupdh((struct videnc_state **)&ves->vesp, vc, prm, fmtp, pkth, - vid); + err = encupdh((struct videnc_state **)&ves->vesp, vc, prm, fmtp, + enc_packet_h, vid); if (err) { mem_deref(ves); return err; @@ -102,18 +163,41 @@ static int enc_update(struct videnc_state **vesp, const struct vidcodec *vc, static int encode(struct videnc_state *ves, bool update, const struct vidframe *frame, uint64_t timestamp) { - return ench(ves->vesp, update, frame, timestamp); + bool keyframe = update; + + if (re_atomic_rlx(&force_keyframe)) { + keyframe = true; + re_atomic_rlx_set(&force_keyframe, false); + } + + re_atomic_rlx_set(&last_keyframe, keyframe); + + return ench(ves->vesp, keyframe, frame, timestamp); } static int packetize(struct videnc_state *ves, const struct vidpacket *vpkt) { + if (ves->is_pkt_src) { uint64_t ts = video_calc_rtp_timestamp_fix(vpkt->timestamp); return ves->pkth(vpkt->keyframe, ts, NULL, 0, vpkt->buf, vpkt->size, ves->vid); } - return packetizeh(ves->vesp, vpkt); + + struct le *le; + LIST_FOREACH(&enc_pktl, le) + { + struct enc_pkt *p = le->data; + + int err = ves->pkth(p->marker, p->rtp_ts, mbuf_buf(p->hdr), + mbuf_get_left(p->hdr), mbuf_buf(p->pld), + mbuf_get_left(p->pld), ves->vid); + if (err) + return err; + } + + return 0; } @@ -151,14 +235,15 @@ static int dec_update(struct viddec_state **vdsp, const struct vidcodec *vc, return 0; } - -static void pkt_deref(void *arg) +#if 0 +static void dec_pkt_deref(void *arg) { - struct pkt *pkt = arg; + struct dec_pkt *pkt = arg; mem_deref(pkt->mb); list_unlink(&pkt->le); } +#endif static int decode(struct viddec_state *vds, struct vidframe *frame, @@ -166,8 +251,9 @@ static int decode(struct viddec_state *vds, struct vidframe *frame, { if (!vds || !frame || !vpkt || !vpkt->mb) return EINVAL; - - struct pkt *pkt = mem_zalloc(sizeof(struct pkt), pkt_deref); +#if 0 + struct dec_pkt *pkt = + mem_zalloc(sizeof(struct dec_pkt), dec_pkt_deref); if (!pkt) return ENOMEM; @@ -187,17 +273,13 @@ static int decode(struct viddec_state *vds, struct vidframe *frame, le = le->next; - /* FIXME: Keep SPS/PPS workaround */ - if (pkt->id < 200) - continue; - if (pkt->ts > pkt->ts_eol) mem_deref(pkt); else break; } mtx_unlock(vds->mtx); - +#endif return dech(vds->vdsp, frame, vpkt); } @@ -228,7 +310,7 @@ static bool list_apply_handler(struct le *le, void *arg) { struct vidsrc_st *st = arg; struct viddec_state *vds = le->data; - struct pkt *pkt = NULL; + struct dec_pkt *pkt = NULL; if (0 != str_cmp(vds->dev, st->device + sizeof("pktsrc") - 1)) return false; @@ -268,6 +350,25 @@ void vmix_codec_pkt(struct vidsrc_st *st) } +bool vmix_last_keyframe(void) +{ + return re_atomic_rlx(&last_keyframe); +} + + +void vmix_request_keyframe(void) +{ + re_atomic_rlx_set(&force_keyframe, true); +} + + +void vmix_encode_flush(void) +{ + + list_flush(&enc_pktl); +} + + int vmix_codec_init(void) { const struct vidcodec *v; @@ -317,6 +418,8 @@ void vmix_codec_close(void) vidcodec_unregister(&h264_0); vidcodec_unregister(&h264_1); + list_flush(&enc_pktl); + dec_list = mem_deref(dec_list); dec_mtx = mem_deref(dec_mtx); } diff --git a/modules/vmix/disp.c b/modules/vmix/disp.c index e53cd4b1..26287371 100644 --- a/modules/vmix/disp.c +++ b/modules/vmix/disp.c @@ -52,7 +52,6 @@ int vmix_disp_alloc(struct vidisp_st **stp, const struct vidisp *vd, } st->vidsrc->vidisp = st; - /* vidmix_source_enable(st->vidsrc->vidmix_src, false); */ hash_append(vmix_disp, hash_joaat_str(dev), &st->le, st); out: diff --git a/modules/vmix/record.c b/modules/vmix/record.c index 2dee544b..2dd80720 100644 --- a/modules/vmix/record.c +++ b/modules/vmix/record.c @@ -1,7 +1,7 @@ /** * @file vidmix/record.c vidmix recording * - * Copyright (C) 2023 Sebastian Reimers + * Copyright (C) 2023-2024 Sebastian Reimers */ #include #include @@ -25,7 +25,7 @@ static struct { RE_ATOMIC bool run; RE_ATOMIC bool run_stream; - struct list bufs; + struct list vframes; mtx_t *lock; thrd_t thread; struct aubuf *ab; @@ -38,17 +38,16 @@ static struct { AVStream *videoStreamStream; AVStream *audioStream; AVStream *audioStreamStream; - AVCodecContext *videoCodecContext; - AVCodecContext *audioCodecContext; + AVFrame *videoFrame; + AVCodecContext *videoCodecCtx; + AVCodecContext *audioCodecCtx; SwrContext *resample_context; -} record = {0}; +} rec = {.run = false}; struct record_entry { struct le le; - struct mbuf *mb; - size_t size; uint64_t ts; - bool keyframe; + struct vidframe *frame; }; #if STREAM @@ -62,21 +61,20 @@ static int init_resampler(void) { int ret; - ret = swr_alloc_set_opts2(&record.resample_context, - &record.audioCodecContext->ch_layout, - record.audioCodecContext->sample_fmt, - record.audioCodecContext->sample_rate, - &(AVChannelLayout)AV_CHANNEL_LAYOUT_MONO, - AV_SAMPLE_FMT_S16, 48000, 0, NULL); + ret = swr_alloc_set_opts2( + &rec.resample_context, &rec.audioCodecCtx->ch_layout, + rec.audioCodecCtx->sample_fmt, rec.audioCodecCtx->sample_rate, + &(AVChannelLayout)AV_CHANNEL_LAYOUT_MONO, AV_SAMPLE_FMT_S16, + 48000, 0, NULL); if (ret < 0) { warning("Could not allocate resample context\n"); return EINVAL; } - ret = swr_init(record.resample_context); + ret = swr_init(rec.resample_context); if (ret < 0) { warning("Could not init resample context\n"); - swr_free(&record.resample_context); + swr_free(&rec.resample_context); return EINVAL; } @@ -90,7 +88,7 @@ static int init_stream(void) /* av_log_set_level(AV_LOG_DEBUG); */ - ret = avformat_alloc_output_context2(&record.streamFormatContext, NULL, + ret = avformat_alloc_output_context2(&rec.streamFormatContext, NULL, "flv", stream_url); if (ret < 0) return EINVAL; @@ -98,92 +96,89 @@ static int init_stream(void) const AVCodec *videoCodec = avcodec_find_encoder(AV_CODEC_ID_H264); AVStream *videoStream = - avformat_new_stream(record.streamFormatContext, videoCodec); + avformat_new_stream(rec.streamFormatContext, videoCodec); if (!videoStream) { warning("video stream error\n"); return ENOMEM; } - AVCodecContext *videoCodecContext = avcodec_alloc_context3(videoCodec); - if (!videoCodecContext) { + AVCodecContext *videoCodecCtx = avcodec_alloc_context3(videoCodec); + if (!videoCodecCtx) { warning("videocontext error\n"); return ENOMEM; } - videoCodecContext->width = 1920; - videoCodecContext->height = 1080; - videoCodecContext->time_base.num = 1; - videoCodecContext->time_base.den = 30; - videoCodecContext->pix_fmt = AV_PIX_FMT_YUV420P; - videoCodecContext->bit_rate = 4000000; + videoCodecCtx->width = 1920; + videoCodecCtx->height = 1080; + videoCodecCtx->time_base.num = 1; + videoCodecCtx->time_base.den = 30; + videoCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P; + videoCodecCtx->bit_rate = 4000000; - if (record.streamFormatContext->oformat->flags & AVFMT_GLOBALHEADER) - videoCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + if (rec.streamFormatContext->oformat->flags & AVFMT_GLOBALHEADER) + videoCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; - ret = avcodec_open2(videoCodecContext, videoCodec, NULL); + ret = avcodec_open2(videoCodecCtx, videoCodec, NULL); if (ret < 0) { warning("avcodec_open2 video error\n"); return EINVAL; } - avcodec_parameters_from_context(videoStream->codecpar, - videoCodecContext); + avcodec_parameters_from_context(videoStream->codecpar, videoCodecCtx); const AVCodec *audioCodec = avcodec_find_encoder(AV_CODEC_ID_AAC); AVStream *audioStream = - avformat_new_stream(record.streamFormatContext, audioCodec); + avformat_new_stream(rec.streamFormatContext, audioCodec); if (!audioStream) { warning("audiostream error\n"); return ENOMEM; } - AVCodecContext *audioCodecContext = avcodec_alloc_context3(audioCodec); - if (!audioCodecContext) + AVCodecContext *audioCodecCtx = avcodec_alloc_context3(audioCodec); + if (!audioCodecCtx) return ENOMEM; - audioCodecContext->codec_id = AV_CODEC_ID_AAC; - audioCodecContext->codec_type = AVMEDIA_TYPE_AUDIO; - audioCodecContext->sample_rate = 48000; - audioCodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP; - audioCodecContext->ch_layout = - (AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO; - audioCodecContext->bit_rate = 96000; - if (record.streamFormatContext->oformat->flags & AVFMT_GLOBALHEADER) - audioCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + audioCodecCtx->codec_id = AV_CODEC_ID_AAC; + audioCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO; + audioCodecCtx->sample_rate = 48000; + audioCodecCtx->sample_fmt = AV_SAMPLE_FMT_FLTP; + audioCodecCtx->ch_layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO; + audioCodecCtx->bit_rate = 96000; + if (rec.streamFormatContext->oformat->flags & AVFMT_GLOBALHEADER) + audioCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; - avcodec_open2(audioCodecContext, audioCodec, NULL); + avcodec_open2(audioCodecCtx, audioCodec, NULL); if (ret < 0) { warning("avcodec_open2 audio error\n"); return EINVAL; } - avcodec_parameters_from_context(audioStream->codecpar, - audioCodecContext); + avcodec_parameters_from_context(audioStream->codecpar, audioCodecCtx); - ret = avio_open(&record.streamFormatContext->pb, stream_url, + ret = avio_open(&rec.streamFormatContext->pb, stream_url, AVIO_FLAG_WRITE); if (ret < 0) { warning("avio_open stream error\n"); return EINVAL; } - av_dump_format(record.streamFormatContext, 0, stream_url, 1); + av_dump_format(rec.streamFormatContext, 0, stream_url, 1); AVDictionary *opts = NULL; av_dict_set(&opts, "flvflags", "no_duration_filesize", 0); - ret = avformat_write_header(record.streamFormatContext, &opts); + ret = avformat_write_header(rec.streamFormatContext, &opts); if (ret < 0) { warning("avformat_write_header stream error\n"); return EINVAL; } - re_atomic_rlx_set(&record.run_stream, true); + re_atomic_rlx_set(&rec.run_stream, true); - record.videoStreamStream = videoStream; - record.audioStreamStream = audioStream; + rec.videoStreamStream = videoStream; + rec.audioStreamStream = audioStream; return 0; } @@ -191,22 +186,22 @@ static int init_stream(void) static void close_stream(void) { - if (re_atomic_rlx(&record.run_stream)) - av_write_trailer(record.streamFormatContext); + if (re_atomic_rlx(&rec.run_stream)) + av_write_trailer(rec.streamFormatContext); - if (record.streamFormatContext) { - avio_close(record.streamFormatContext->pb); - avformat_free_context(record.streamFormatContext); + if (rec.streamFormatContext) { + avio_close(rec.streamFormatContext->pb); + avformat_free_context(rec.streamFormatContext); } - re_atomic_rlx_set(&record.run_stream, false); + re_atomic_rlx_set(&rec.run_stream, false); } static int write_stream(AVPacket *pkt, AVRational *time_base_src, AVRational *time_base_dst) { - if (!re_atomic_rlx(&record.run_stream)) + if (!re_atomic_rlx(&rec.run_stream)) return 0; AVPacket *packet = av_packet_clone(pkt); @@ -217,8 +212,7 @@ static int write_stream(AVPacket *pkt, AVRational *time_base_src, packet->dts = av_rescale_q(packet->dts, *time_base_src, *time_base_dst); - int ret = - av_interleaved_write_frame(record.streamFormatContext, packet); + int ret = av_interleaved_write_frame(rec.streamFormatContext, packet); if (ret < 0) { warning("av write stream error (%s)\n", av_err2str(ret)); return EPIPE; @@ -238,7 +232,7 @@ static int record_thread(void *arg) struct le *le; int64_t audio_pts = 0; #if STREAM - int err = 0; + int err = 0; #endif (void)arg; @@ -249,9 +243,9 @@ static int record_thread(void *arg) if (!videoPacket || !audioPacket || !audioFrame) return ENOMEM; - audioFrame->nb_samples = record.audioCodecContext->frame_size; - audioFrame->format = record.audioCodecContext->sample_fmt; - audioFrame->ch_layout = record.audioCodecContext->ch_layout; + audioFrame->nb_samples = rec.audioCodecCtx->frame_size; + audioFrame->format = rec.audioCodecCtx->sample_fmt; + audioFrame->ch_layout = rec.audioCodecCtx->ch_layout; ret = av_frame_get_buffer(audioFrame, 0); if (ret < 0) { @@ -260,67 +254,96 @@ static int record_thread(void *arg) } int16_t *sampv; - sampv = mem_zalloc( - record.audioCodecContext->frame_size * sizeof(int16_t), NULL); + sampv = mem_zalloc(rec.audioCodecCtx->frame_size * sizeof(int16_t), + NULL); if (!sampv) return ENOMEM; - auframe_init(&af, AUFMT_S16LE, sampv, - record.audioCodecContext->frame_size, 48000, 1); + auframe_init(&af, AUFMT_S16LE, sampv, rec.audioCodecCtx->frame_size, + 48000, 1); + + while (re_atomic_rlx(&rec.run)) { + sys_usleep(2000); - while (re_atomic_rlx(&record.run)) { - sys_msleep(2); + mtx_lock(rec.lock); + uint32_t count = list_count(&rec.vframes); + if (count > 10) + warning("list count %u\n", count); + le = list_head(&rec.vframes); + mtx_unlock(rec.lock); - mtx_lock(record.lock); - le = list_head(&record.bufs); - mtx_unlock(record.lock); while (le) { struct record_entry *e = le->data; - videoPacket->data = e->mb->buf; - videoPacket->size = (int)e->size; - videoPacket->stream_index = record.videoStream->index; - videoPacket->pos = -1; + for (int i = 0; i < 4; i++) { + rec.videoFrame->data[i] = e->frame->data[i]; + rec.videoFrame->linesize[i] = + e->frame->linesize[i]; + } + + rec.videoFrame->pts = av_rescale_q( + e->ts - re_atomic_rlx(&rec.video_start_time), + timebase_video, rec.videoStream->time_base); + + ret = avcodec_send_frame(rec.videoCodecCtx, + rec.videoFrame); + if (ret < 0) { + warning("rec: Error sending the video frame " + "to the encoder\n"); + return ENOMEM; + } - videoPacket->dts = videoPacket->pts = av_rescale_q( - e->ts - re_atomic_rlx( - &record.video_start_time), - timebase_video, record.videoStream->time_base); + while (ret >= 0) { + ret = avcodec_receive_packet(rec.videoCodecCtx, + videoPacket); + if (ret == AVERROR(EAGAIN) || + ret == AVERROR_EOF) + break; + else if (ret < 0) { + warning("Error encoding video " + "frame\n"); + return ENOMEM; + } + + videoPacket->stream_index = + rec.videoStream->index; #if 0 - warning("ts: %llu, %lld %d/%d\n", - e->ts - re_atomic_rlx( - &record.video_start_time), - videoPacket->pts, - record.videoStream->time_base.num, - record.videoStream->time_base.den); + warning("ts: %llu, %lld %d/%d\n", + e->ts - re_atomic_rlx( + &rec.video_start_time), + videoPacket->pts, + rec.videoStream->time_base.num, + rec.videoStream->time_base.den); #endif #if STREAM - err = write_stream( - videoPacket, &record.videoStream->time_base, - &record.videoStreamStream->time_base); - if (err) - return err; + err = write_stream( + videoPacket, + &rec.videoStream->time_base, + &rec.videoStreamStream->time_base); + if (err) + return err; #endif - ret = av_interleaved_write_frame( - record.outputFormatContext, videoPacket); - if (ret < 0) { - warning("av_frame_write video stream error " - "%s\n", - av_err2str(ret)); - return EINVAL; + int ret2 = av_interleaved_write_frame( + rec.outputFormatContext, videoPacket); + if (ret2 < 0) { + warning("av_frame_write video stream " + "error " + "%s\n", + av_err2str(ret)); + return EINVAL; + } } - mtx_lock(record.lock); + mtx_lock(rec.lock); le = le->next; + mtx_unlock(rec.lock); mem_deref(e); - mtx_unlock(record.lock); } - while (aubuf_cur_size(record.ab) >= auframe_size(&af)) { - audioFrame->nb_samples = - record.audioCodecContext->frame_size; + while (aubuf_cur_size(rec.ab) >= auframe_size(&af)) { + audioFrame->nb_samples = rec.audioCodecCtx->frame_size; ret = av_frame_make_writable(audioFrame); if (ret < 0) { @@ -328,28 +351,27 @@ static int record_thread(void *arg) return ENOMEM; } - aubuf_read_auframe(record.ab, &af); + aubuf_read_auframe(rec.ab, &af); - swr_convert(record.resample_context, + swr_convert(rec.resample_context, audioFrame->extended_data, audioFrame->nb_samples, (const uint8_t **)&af.sampv, (int)af.sampc); - #if 0 audioFrame->pts = av_rescale_q( af.timestamp - (re_atomic_rlx( - &record.video_start_time) / + &rec.video_start_time) / 1000), timebase_audio, - record.audioStreamStream->time_base); + rec.audioStreamStream->time_base); #else audioFrame->pts = audio_pts; audio_pts += audioFrame->nb_samples; #endif - ret = avcodec_send_frame(record.audioCodecContext, + ret = avcodec_send_frame(rec.audioCodecCtx, audioFrame); if (ret < 0) { warning("Error sending the frame to the " @@ -358,9 +380,8 @@ static int record_thread(void *arg) } while (ret >= 0) { - int ret2; - ret = avcodec_receive_packet( - record.audioCodecContext, audioPacket); + ret = avcodec_receive_packet(rec.audioCodecCtx, + audioPacket); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; @@ -373,27 +394,26 @@ static int record_thread(void *arg) warning("audio ts: %llu, %lld %lld %d/%d\n", af.timestamp - (re_atomic_rlx( - &record.video_start_time) / + &rec.video_start_time) / 1000), audioFrame->pts, audioPacket->pts, - record.audioStreamStream->time_base + rec.audioStreamStream->time_base .num, - record.audioStreamStream->time_base + rec.audioStreamStream->time_base .den); #endif audioPacket->stream_index = - record.audioStream->index; + rec.audioStream->index; #if STREAM err = write_stream( audioPacket, &timebase_audio, - &record.audioStreamStream->time_base); + &rec.audioStreamStream->time_base); if (err) return err; #endif - ret2 = av_interleaved_write_frame( - record.outputFormatContext, - audioPacket); + int ret2 = av_interleaved_write_frame( + rec.outputFormatContext, audioPacket); if (ret2 < 0) { warning("av_frame_write audio " "error\n"); @@ -417,27 +437,27 @@ int vmix_record_start(const char *record_folder) int err; int ret; - if (re_atomic_rlx(&record.run)) + if (re_atomic_rlx(&rec.run)) return EALREADY; - re_snprintf(record.filename, sizeof(record.filename), "%s/video.mp4", + re_snprintf(rec.filename, sizeof(rec.filename), "%s/video.mp4", record_folder); - err = mutex_alloc(&record.lock); + err = mutex_alloc(&rec.lock); if (err) { return err; } - err = aubuf_alloc(&record.ab, 0, 0); + err = aubuf_alloc(&rec.ab, 0, 0); if (err) { - mem_deref(record.lock); + mem_deref(rec.lock); return err; } - aubuf_set_live(record.ab, false); + aubuf_set_live(rec.ab, false); - ret = avformat_alloc_output_context2(&record.outputFormatContext, NULL, - NULL, record.filename); + ret = avformat_alloc_output_context2(&rec.outputFormatContext, NULL, + NULL, rec.filename); if (ret < 0) { warning("avformat_alloc error\n"); return EINVAL; @@ -445,197 +465,198 @@ int vmix_record_start(const char *record_folder) /* VIDEO */ const AVCodec *videoCodec = avcodec_find_encoder(AV_CODEC_ID_H264); - record.videoStream = - avformat_new_stream(record.outputFormatContext, videoCodec); - if (!record.videoStream) + rec.videoStream = + avformat_new_stream(rec.outputFormatContext, videoCodec); + if (!rec.videoStream) return ENOMEM; - record.videoCodecContext = avcodec_alloc_context3(videoCodec); - if (!record.videoCodecContext) + rec.videoCodecCtx = avcodec_alloc_context3(videoCodec); + if (!rec.videoCodecCtx) return ENOMEM; - record.videoCodecContext->width = 1920; - record.videoCodecContext->height = 1080; - record.videoCodecContext->time_base.num = 1; - record.videoCodecContext->time_base.den = 30; - record.videoCodecContext->pix_fmt = AV_PIX_FMT_YUV420P; + struct config *conf = conf_config(); + + rec.videoCodecCtx->width = conf->video.width; + rec.videoCodecCtx->height = conf->video.height; + rec.videoCodecCtx->time_base.num = 1; + rec.videoCodecCtx->time_base.den = conf->video.fps; + rec.videoCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P; + rec.videoCodecCtx->bit_rate = 8000000; + rec.videoCodecCtx->thread_count = 1; + + av_opt_set(rec.videoCodecCtx->priv_data, "profile", "baseline", 0); + av_opt_set(rec.videoCodecCtx->priv_data, "preset", "ultrafast", 0); + av_opt_set(rec.videoCodecCtx->priv_data, "tune", "zerolatency", 0); /* Some formats want stream headers to be separate. */ - if (record.outputFormatContext->oformat->flags & AVFMT_GLOBALHEADER) - record.videoCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + if (rec.outputFormatContext->oformat->flags & AVFMT_GLOBALHEADER) + rec.videoCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; - ret = avcodec_open2(record.videoCodecContext, videoCodec, NULL); + ret = avcodec_open2(rec.videoCodecCtx, videoCodec, NULL); if (ret < 0) { warning("avcodec_open2 video error\n"); return EINVAL; } - avcodec_parameters_from_context(record.videoStream->codecpar, - record.videoCodecContext); + avcodec_parameters_from_context(rec.videoStream->codecpar, + rec.videoCodecCtx); /* AUDIO */ const AVCodec *audioCodec = avcodec_find_encoder(AV_CODEC_ID_AAC); - record.audioStream = - avformat_new_stream(record.outputFormatContext, audioCodec); - if (!record.audioStream) + rec.audioStream = + avformat_new_stream(rec.outputFormatContext, audioCodec); + if (!rec.audioStream) return ENOMEM; - record.audioCodecContext = avcodec_alloc_context3(audioCodec); - if (!record.audioCodecContext) + rec.audioCodecCtx = avcodec_alloc_context3(audioCodec); + if (!rec.audioCodecCtx) return ENOMEM; - record.audioCodecContext->codec_id = AV_CODEC_ID_AAC; - record.audioCodecContext->codec_type = AVMEDIA_TYPE_AUDIO; - record.audioCodecContext->sample_rate = 48000; - record.audioCodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP; - record.audioCodecContext->ch_layout = + rec.audioCodecCtx->codec_id = AV_CODEC_ID_AAC; + rec.audioCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO; + rec.audioCodecCtx->sample_rate = 48000; + rec.audioCodecCtx->sample_fmt = AV_SAMPLE_FMT_FLTP; + rec.audioCodecCtx->ch_layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO; - record.audioCodecContext->bit_rate = 96000; + rec.audioCodecCtx->bit_rate = 128000; /* Some formats want stream headers to be separate. */ - if (record.outputFormatContext->oformat->flags & AVFMT_GLOBALHEADER) - record.audioCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + if (rec.outputFormatContext->oformat->flags & AVFMT_GLOBALHEADER) + rec.audioCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; - avcodec_open2(record.audioCodecContext, audioCodec, NULL); + avcodec_open2(rec.audioCodecCtx, audioCodec, NULL); if (ret < 0) { warning("avcodec_open2 audio error\n"); return EINVAL; } - avcodec_parameters_from_context(record.audioStream->codecpar, - record.audioCodecContext); + avcodec_parameters_from_context(rec.audioStream->codecpar, + rec.audioCodecCtx); - ret = avio_open(&record.outputFormatContext->pb, record.filename, + ret = avio_open(&rec.outputFormatContext->pb, rec.filename, AVIO_FLAG_WRITE); if (ret < 0) { warning("avio_open error\n"); return EINVAL; } - ret = avformat_write_header(record.outputFormatContext, NULL); + ret = avformat_write_header(rec.outputFormatContext, NULL); if (ret < 0) { warning("avformat_write_header error\n"); return EINVAL; } + rec.videoFrame = av_frame_alloc(); + rec.videoFrame->width = conf->video.width; + rec.videoFrame->height = conf->video.height; + rec.videoFrame->format = AV_PIX_FMT_YUV420P; + init_resampler(); #if STREAM init_stream(); #endif - re_atomic_rlx_set(&record.run, true); + re_atomic_rlx_set(&rec.run, true); info("vidmix: record started\n"); - thread_create_name(&record.thread, "vidrec", record_thread, NULL); + thread_create_name(&rec.thread, "vidrec", record_thread, NULL); return 0; } -static void entry_destruct(void *arg) -{ - struct record_entry *e = arg; - mem_deref(e->mb); - - list_unlink(&e->le); -} - - void vmix_audio_record(struct auframe *af); void vmix_audio_record(struct auframe *af) { - if (!re_atomic_rlx(&record.run) || - !re_atomic_rlx(&record.video_start_time)) + if (!re_atomic_rlx(&rec.run) || !re_atomic_rlx(&rec.video_start_time)) return; - if (!re_atomic_rlx(&record.audio_start_time)) - re_atomic_rlx_set(&record.audio_start_time, af->timestamp); + if (!re_atomic_rlx(&rec.audio_start_time)) + re_atomic_rlx_set(&rec.audio_start_time, af->timestamp); - aubuf_write_auframe(record.ab, af); + aubuf_write_auframe(rec.ab, af); } -int vmix_record(struct vidpacket *vp, RE_ATOMIC bool *update) +static void record_destroy(void *arg) { - struct record_entry *e; - int err; - (void)update; + struct record_entry *e = arg; - if (!re_atomic_rlx(&record.run)) - return ESHUTDOWN; + mem_deref(e->frame); - if (!vp->buf || !vp->size) - return EINVAL; + mtx_lock(rec.lock); + list_unlink(&e->le); + mtx_unlock(rec.lock); +} - if (!re_atomic_rlx(&record.video_start_time)) { - /* wait until keyframe */ - if (!vp->keyframe) { - /* FIXME: update request disabled for hardware enc */ - /* re_atomic_rlx_set(update, true); */ - return 0; - } - re_atomic_rlx_set(&record.video_start_time, vp->timestamp); - } - e = mem_zalloc(sizeof(struct record_entry), entry_destruct); +int vmix_record(const struct vidframe *frame, uint64_t ts) +{ + if (!re_atomic_rlx(&rec.run)) + return 0; + + if (!re_atomic_rlx(&rec.video_start_time)) + re_atomic_rlx_set(&rec.video_start_time, ts); + + struct record_entry *e = + mem_zalloc(sizeof(struct record_entry), record_destroy); if (!e) return ENOMEM; - e->mb = mbuf_alloc(vp->size); - if (!e->mb) { - err = ENOMEM; - goto out; - } - - err = mbuf_write_mem(e->mb, vp->buf, vp->size); - if (err) - goto out; + struct vidsz vsz = {.w = 1920, .h = 1080}; - e->size = vp->size; - e->ts = vp->timestamp; - e->keyframe = vp->keyframe; + int err = vidframe_alloc(&e->frame, VID_FMT_YUV420P, &vsz); + if (unlikely(err)) { + mem_deref(e); + return ENOMEM; + } - mtx_lock(record.lock); - list_append(&record.bufs, &e->le, e); - mtx_unlock(record.lock); + vidframe_copy(e->frame, frame); + e->ts = ts; -out: - if (err) - mem_deref(e); + mtx_lock(rec.lock); + list_append(&rec.vframes, &e->le, e); + mtx_unlock(rec.lock); - return err; + return 0; } int vmix_record_close(void) { - if (!re_atomic_rlx(&record.run)) + if (!re_atomic_rlx(&rec.run)) return EINVAL; - re_atomic_rlx_set(&record.run, false); - thrd_join(record.thread, NULL); + re_atomic_rlx_set(&rec.run, false); + thrd_join(rec.thread, NULL); - re_atomic_rlx_set(&record.audio_start_time, 0); - re_atomic_rlx_set(&record.video_start_time, 0); + re_atomic_rlx_set(&rec.audio_start_time, 0); + re_atomic_rlx_set(&rec.video_start_time, 0); #if STREAM close_stream(); #endif /* Write the trailer and close the output file */ - av_write_trailer(record.outputFormatContext); + av_write_trailer(rec.outputFormatContext); + avio_close(rec.outputFormatContext->pb); /* Clean up */ - avcodec_close(record.videoCodecContext); - avcodec_close(record.audioCodecContext); - avformat_free_context(record.outputFormatContext); - swr_free(&record.resample_context); + avcodec_close(rec.videoCodecCtx); + avcodec_close(rec.audioCodecCtx); + avformat_free_context(rec.outputFormatContext); + swr_free(&rec.resample_context); + + uint32_t count = list_count(&rec.vframes); + if (count) + warning("rec/close: drop %u frames\n", count); - list_flush(&record.bufs); + list_flush(&rec.vframes); - record.ab = mem_deref(record.ab); + rec.ab = mem_deref(rec.ab); - mem_deref(record.lock); + mem_deref(rec.lock); + av_frame_free(&rec.videoFrame); - chmod(record.filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + chmod(rec.filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); return 0; } diff --git a/modules/vmix/src.c b/modules/vmix/src.c index cfeb2ee7..690b415d 100644 --- a/modules/vmix/src.c +++ b/modules/vmix/src.c @@ -10,10 +10,7 @@ #include #include "vmix.h" -static struct vidpacket vp = { - .buf = NULL, .size = 0, .timestamp = 0, .keyframe = false}; -static struct mbuf *packet_dup = NULL; -static RE_ATOMIC bool reset = false; +static struct vidpacket dummy_vp = {.timestamp = 0}; static mtx_t *vmix_mutex; @@ -88,72 +85,32 @@ static void frame_handler(uint64_t ts, const struct vidframe *frame, void *arg) if (!re_atomic_rlx(&st->run)) return; - /* frameh can return without calling dup_handler if not all network - * packets are send */ st->frameh((struct vidframe *)frame, ts, st->arg); - if (!vp.buf) - return; - - if (!vmix_srcl.head) - return; + dummy_vp.keyframe = vmix_last_keyframe(); vmix_lock(); - le = vmix_srcl.head->next; + le = vmix_srcl.head; while (le) { st = le->data; if (!st) break; /* wait until keyframe arrive if src is not running */ - if (!re_atomic_rlx(&st->run) && !vp.keyframe) { + if (!re_atomic_rlx(&st->run) && !dummy_vp.keyframe) { le = le->next; continue; } re_atomic_rlx_set(&st->run, true); - st->packeth(&vp, st->arg); + st->packeth(&dummy_vp, st->arg); le = le->next; } vmix_unlock(); - vmix_record(&vp, &reset); - - /* prevent sending packets multiple times */ - vp.buf = NULL; -} - - -int packet_dup_handler(uint64_t ts, uint8_t *buf, size_t size, bool keyframe); -int packet_dup_handler(uint64_t ts, uint8_t *buf, size_t size, bool keyframe) -{ - int err = 0; - - if (!buf) - return 0; - - if (re_atomic_rlx(&reset)) { - re_atomic_rlx_set(&reset, false); - err = ECONNRESET; - goto out; - } - - packet_dup->pos = 0; - packet_dup->end = 0; - - err = mbuf_write_mem(packet_dup, buf, size); - if (err) { - warning("packet_dup_handler %m", err); - return 0; - } - - vp.timestamp = ts; - vp.size = size; - vp.buf = packet_dup->buf; - vp.keyframe = keyframe; + vmix_encode_flush(); -out: - return err; + vmix_record(frame, ts); } @@ -204,7 +161,7 @@ int vmix_src_alloc(struct vidsrc_st **stp, const struct vidsrc *vs, vidmix_source_toggle_selfview(st->vidmix_src); - re_atomic_rlx_set(&reset, true); + vmix_request_keyframe(); /* only start once */ if (vmix_srcl.head == &st->le) { @@ -251,11 +208,6 @@ void vmix_src_input(struct vidsrc_st *st, const struct vidframe *frame, int vmix_src_init(void) { - packet_dup = mbuf_alloc(1024); - if (!packet_dup) { - return ENOMEM; - } - return mutex_alloc(&vmix_mutex); } @@ -263,5 +215,4 @@ int vmix_src_init(void) void vmix_src_close(void) { vmix_mutex = mem_deref(vmix_mutex); - packet_dup = mem_deref(packet_dup); } diff --git a/modules/vmix/vmix.h b/modules/vmix/vmix.h index d0ac6092..4372dfba 100644 --- a/modules/vmix/vmix.h +++ b/modules/vmix/vmix.h @@ -56,12 +56,15 @@ void vmix_src_input(struct vidsrc_st *st, const struct vidframe *frame, int vmix_record_start(const char *record_folder); -int vmix_record(struct vidpacket *vp, RE_ATOMIC bool *update); +int vmix_record(const struct vidframe *frame, uint64_t ts); int vmix_record_close(void); void vmix_codec_pkt(struct vidsrc_st *st); int vmix_codec_init(void); void vmix_codec_close(void); +bool vmix_last_keyframe(void); +void vmix_request_keyframe(void); +void vmix_encode_flush(void); int vmix_pktsrc_init(void); void vmix_pktsrc_close(void); diff --git a/src/http.c b/src/http.c index f5331cb9..bf697527 100644 --- a/src/http.c +++ b/src/http.c @@ -193,6 +193,9 @@ static void http_req_handler(struct http_conn *conn, if (err) goto err; + /* generate new session - ensures rooms load new session */ + rand_str(sess->id, sizeof(sess->id)); + slmix_session_user_updated(sess); slmix_session_save(sess); @@ -342,7 +345,7 @@ static void http_req_handler(struct http_conn *conn, 0 == pl_strcasecmp(&msg->met, "PUT")) { /* check permission */ if (!sess->user || !sess->user->host) - goto err; + goto auth; slmix_record(mix, REC_AUDIO_VIDEO); @@ -354,7 +357,7 @@ static void http_req_handler(struct http_conn *conn, 0 == pl_strcasecmp(&msg->met, "PUT")) { /* check permission */ if (!sess->user || !sess->user->host) - goto err; + goto auth; slmix_record(mix, REC_AUDIO); @@ -366,7 +369,7 @@ static void http_req_handler(struct http_conn *conn, 0 == pl_strcasecmp(&msg->met, "PUT")) { /* check permission */ if (!sess->user || !sess->user->host) - goto err; + goto auth; slmix_record(mix, REC_DISABLED); @@ -379,9 +382,9 @@ static void http_req_handler(struct http_conn *conn, struct pl user_id = PL_INIT; struct session *sess_speaker; - /* check permission */ - if (!sess->user || !sess->user->host) - goto err; + /* check host permission if show */ + if (!sess->user || (!sess->user->host && sess->mix->show)) + goto auth; err = re_regex((char *)mbuf_buf(msg->mb), mbuf_get_left(msg->mb), "[a-zA-Z0-9]+", @@ -394,6 +397,10 @@ static void http_req_handler(struct http_conn *conn, if (!sess_speaker) goto err; + /* if not a show, allow only user itself */ + if (!sess->mix->show && sess != sess_speaker) + goto auth; + err = slmix_session_speaker(sess_speaker, true); if (err) goto err; @@ -525,7 +532,7 @@ static void http_req_handler(struct http_conn *conn, return; } - if (0 == pl_strcasecmp(&msg->path, "/api/v1/webrtc/solo") && + if (0 == pl_strcasecmp(&msg->path, "/api/v1/webrtc/solo/enable") && 0 == pl_strcasecmp(&msg->met, "PUT")) { char user[512] = {0}; struct pl user_id = PL_INIT; @@ -542,7 +549,24 @@ static void http_req_handler(struct http_conn *conn, pl_strcpy(&user_id, user, sizeof(user)); - vmix_disp_solo(user); + struct session *sess_speaker = + slmix_session_lookup_user_id(&mix->sessl, &user_id); + if (!sess_speaker) + goto err; + + slmix_session_video_solo(sess_speaker->user, true); + + http_sreply(conn, 204, "OK", "text/html", "", 0, sess); + return; + } + + if (0 == pl_strcasecmp(&msg->path, "/api/v1/webrtc/solo/disable") && + 0 == pl_strcasecmp(&msg->met, "PUT")) { + /* check permission */ + if (!sess->user->host) + goto err; + + slmix_session_video_solo(NULL, false); http_sreply(conn, 204, "OK", "text/html", "", 0, sess); return; @@ -695,8 +719,13 @@ static void http_req_handler(struct http_conn *conn, http_sreply(conn, 404, "Not found", "text/html", "", 0, sess); return; +auth: + http_sreply(conn, 401, "Unauthorized", "text/html", "", 0, sess); + return; + err: http_sreply(conn, 500, "Error", "text/html", "", 0, sess); + return; } diff --git a/src/main.c b/src/main.c index 5f9ce4d7..7c791e2d 100644 --- a/src/main.c +++ b/src/main.c @@ -115,22 +115,24 @@ int main(int argc, char *const argv[]) struct mix *mix = slmix(); const char *conf = +/* "sip_listen 0.0.0.0:5060\n" */ "call_max_calls 10\n" /* SIP incoming only */ - "sip_listen 0.0.0.0:5060\n" "sip_verify_server yes\n" - "audio_buffer 40-300\n" - "audio_buffer_mode adaptive\n" + "audio_buffer 40-100\n" + "audio_buffer_mode fixed\n" "audio_silence -35.0\n" - "audio_jitter_buffer_type off\n" + "audio_jitter_buffer_type adaptive\n" + "audio_jitter_buffer_ms 60-300\n" "video_jitter_buffer_type fixed\n" - "video_jitter_buffer_delay 1-500\n" + "video_jitter_buffer_ms 100-300\n" + "video_jitter_buffer_size 1000\n" "opus_bitrate 64000\n" - "ice_policy all\n" + "ice_policy relay\n" "video_size 1920x1080\n" "video_bitrate 4000000\n" "video_sendrate 10000000\n" /* max burst send */ "video_burst_bit 1000000\n" /* max burst send */ - "video_fps 30\n" + "video_fps 24\n" "avcodec_keyint 10\n" "avcodec_h265enc nil\n" "avcodec_h265dec nil\n" @@ -151,12 +153,18 @@ int main(int argc, char *const argv[]) if (err) return err; +#ifdef RE_TRACE_ENABLED + err = re_trace_init("re_trace.json"); + if (err) + return err; +#endif + err = slmix_getopt(argc, argv); if (err) return err; fd_setsize(-1); - re_thread_async_init(4); + re_thread_async_init(8); (void)sys_coredump_set(true); @@ -231,6 +239,9 @@ int main(int argc, char *const argv[]) mod_close(); re_thread_async_close(); +#ifdef RE_TRACE_ENABLED + re_trace_close(); +#endif tmr_debug(); libre_close(); mem_debug(); diff --git a/src/mix.c b/src/mix.c index 20d698da..25b1abb8 100644 --- a/src/mix.c +++ b/src/mix.c @@ -81,8 +81,8 @@ void slmix_refresh_rooms(void *arg) mbuf_printf(&mjson, "\"%s\": %s,", mbkey.buf, val.buf); - mbuf_set_posend(&mbkey, 0, 0); - mbuf_set_posend(&val, 0, 0); + mbuf_rewind(&mbkey); + mbuf_rewind(&val); } re_sdprintf(&json, "{\"type\": \"rooms\", \"rooms\": {%b}}", mjson.buf, @@ -126,7 +126,7 @@ int slmix_update_room(void) int slmix_init(void) { int err; -#if 0 +#if 1 struct pl srv; pl_set_str(&srv, "turn:195.201.63.86:3478"); diff --git a/src/sess.c b/src/sess.c index c23f8065..82a61737 100644 --- a/src/sess.c +++ b/src/sess.c @@ -147,6 +147,8 @@ static void pc_estab_handler(struct media_track *media, void *arg) return; } + sess->connected = true; + slmix_session_speaker(sess, sess->user->speaker); } @@ -233,15 +235,10 @@ int slmix_session_alloc(struct session **sessp, struct mix *mix, pl_strcpy(name, user->name, sizeof(user->name)); } - user->host = host; - - if (mix->show) - user->speaker = speaker; - else - user->speaker = true; - - sess->user = user; - sess->mix = mix; + user->host = host; + user->speaker = speaker; + sess->user = user; + sess->mix = mix; list_append(&mix->sessl, &sess->le, sess); @@ -341,25 +338,26 @@ struct session *slmix_session_lookup(const struct list *sessl, mbuf_init(&mb); err = slmix_db_get(slmix_db_sess(), sessid, &mb); if (err) - goto out; + goto err; err = re_regex((const char *)mb.buf, mb.end - 1, "[^;]+;[^;]+;[^;]+;[^;]+;[^;]+", NULL, &pl_user_id, &pl_name, &pl_host, &pl_speaker); if (err) - goto out; + goto err; pl_bool(&host, &pl_host); pl_bool(&speaker, &pl_speaker); err = slmix_session_alloc(&sess, slmix(), sessid, &pl_user_id, &pl_name, host, speaker); - if (!err) { - mbuf_reset(&mb); - return sess; - } + if (err) + goto err; -out: + mbuf_reset(&mb); + return sess; + +err: mbuf_reset(&mb); warning("mix: session not found (%r)\n", sessid); @@ -432,17 +430,61 @@ void slmix_session_video(struct session *sess, bool enable) sess->user->video = enable; - sess->user->pidx = - slmix_disp_enable(sess->mix, sess->user->id, enable); -#if 0 - if (enable) - stream_flush(media_get_stream(sess->mvideo)); -#endif - slmix_session_user_updated(sess); } +void slmix_session_video_solo(struct user *user, bool enable) +{ + struct mix *mix = slmix(); + struct le *le; + + if (!enable) { + LIST_FOREACH(&mix->sessl, le) + { + struct session *sess = le->data; + + if (!sess->user->speaker) + continue; + + if (!sess->connected) + continue; + + sess->user->solo = false; + sess->user->pidx = + slmix_disp_enable(mix, sess->user->id, true); + slmix_session_user_updated(sess); + } + return; + } + + if (!user) + return; + + + LIST_FOREACH(&mix->sessl, le) + { + struct session *sess = le->data; + + if (!sess->user->speaker) + continue; + + if (!sess->connected) + continue; + + if (sess->user == user) { + sess->user->solo = true; + slmix_session_user_updated(sess); + } + else { + sess->user->pidx = 0; + slmix_disp_enable(mix, sess->user->id, false); + slmix_session_user_updated(sess); + } + } +} + + int slmix_session_speaker(struct session *sess, bool enable) { if (!sess || !sess->mix || !sess->user) @@ -453,16 +495,15 @@ int slmix_session_speaker(struct session *sess, bool enable) sess->user->speaker = enable; amix_mute(sess->user->id, !enable, sess->user->speaker_id); + sess->user->pidx = + slmix_disp_enable(sess->mix, sess->user->id, enable); stream_enable(media_get_stream(sess->mvideo), enable); stream_enable_tx(media_get_stream(sess->mvideo), true); sess->user->hand = false; /* only allow disable for privacy reasons */ - if (!enable) { + if (!enable) sess->user->video = false; - sess->user->pidx = - slmix_disp_enable(sess->mix, sess->user->id, false); - } return slmix_session_user_updated(sess); } diff --git a/src/users.c b/src/users.c index 74f6d5c3..a535b778 100644 --- a/src/users.c +++ b/src/users.c @@ -2,7 +2,7 @@ #include #include "mix.h" -enum { MAX_LISTENERS = 30 }; +enum { MAX_LISTENERS = 40 }; int users_json(char **json, struct mix *mix) @@ -56,6 +56,7 @@ int users_json(char **json, struct mix *mix) odict_entry_add(o_user, "audio", ODICT_BOOL, sess->user->audio); odict_entry_add(o_user, "hand", ODICT_BOOL, sess->user->hand); + odict_entry_add(o_user, "solo", ODICT_BOOL, sess->user->solo); odict_entry_add(o_user, "webrtc", ODICT_BOOL, sess->pc ? true : false); @@ -110,6 +111,7 @@ int user_event_json(char **json, enum user_event event, struct session *sess) odict_entry_add(o, "video", ODICT_BOOL, sess->user->video); odict_entry_add(o, "audio", ODICT_BOOL, sess->user->audio); odict_entry_add(o, "hand", ODICT_BOOL, sess->user->hand); + odict_entry_add(o, "solo", ODICT_BOOL, sess->user->solo); odict_entry_add(o, "webrtc", ODICT_BOOL, sess->pc ? true : false); err = re_sdprintf(json, "%H", json_encode_odict, o); diff --git a/webui/index.html b/webui/index.html index abb1a1e7..298a3169 100644 --- a/webui/index.html +++ b/webui/index.html @@ -5,6 +5,7 @@ Studio Link - Mix +
diff --git a/webui/package-lock.json b/webui/package-lock.json index 61ba9e3f..b421df86 100644 --- a/webui/package-lock.json +++ b/webui/package-lock.json @@ -11,8 +11,10 @@ "@headlessui/vue": "^1.7.4", "@heroicons/vue": "^2.0.13", "@tailwindcss/forms": "^0.5.3", + "@vueuse/components": "^10.8.0", "@vueuse/core": "^10.1.2", "cropperjs": "^1.5.13", + "markdown-it": "^14.0.0", "vue": "^3.2.41", "vue-router": "^4.1.6", "vue3-avataaars": "^1.0.12", @@ -20,6 +22,8 @@ }, "devDependencies": { "@rushstack/eslint-patch": "^1.1.4", + "@tailwindcss/typography": "^0.5.10", + "@types/markdown-it": "^13.0.7", "@types/node": "^20.1.1", "@vitejs/plugin-vue": "^5.0.0", "@vue/eslint-config-prettier": "^9.0.0", @@ -735,9 +739,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.11.0.tgz", - "integrity": "sha512-BV+u2QSfK3i1o6FucqJh5IK9cjAU6icjFFhvknzFgu472jzl0bBojfDAkJLBEsHFMo+YZg6rthBvBBt8z12IBQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", + "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", "cpu": [ "arm" ], @@ -748,9 +752,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.11.0.tgz", - "integrity": "sha512-0ij3iw7sT5jbcdXofWO2NqDNjSVVsf6itcAkV2I6Xsq4+6wjW1A8rViVB67TfBEan7PV2kbLzT8rhOVWLI2YXw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz", + "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", "cpu": [ "arm64" ], @@ -761,9 +765,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.11.0.tgz", - "integrity": "sha512-yPLs6RbbBMupArf6qv1UDk6dzZvlH66z6NLYEwqTU0VHtss1wkI4UYeeMS7TVj5QRVvaNAWYKP0TD/MOeZ76Zg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz", + "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", "cpu": [ "arm64" ], @@ -774,9 +778,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.11.0.tgz", - "integrity": "sha512-OvqIgwaGAwnASzXaZEeoJY3RltOFg+WUbdkdfoluh2iqatd090UeOG3A/h0wNZmE93dDew9tAtXgm3/+U/B6bw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz", + "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", "cpu": [ "x64" ], @@ -787,9 +791,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.11.0.tgz", - "integrity": "sha512-X17s4hZK3QbRmdAuLd2EE+qwwxL8JxyVupEqAkxKPa/IgX49ZO+vf0ka69gIKsaYeo6c1CuwY3k8trfDtZ9dFg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz", + "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", "cpu": [ "arm" ], @@ -800,9 +804,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.11.0.tgz", - "integrity": "sha512-673Lu9EJwxVB9NfYeA4AdNu0FOHz7g9t6N1DmT7bZPn1u6bTF+oZjj+fuxUcrfxWXE0r2jxl5QYMa9cUOj9NFg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz", + "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", "cpu": [ "arm64" ], @@ -813,9 +817,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.11.0.tgz", - "integrity": "sha512-yFW2msTAQNpPJaMmh2NpRalr1KXI7ZUjlN6dY/FhWlOclMrZezm5GIhy3cP4Ts2rIAC+IPLAjNibjp1BsxCVGg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz", + "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", "cpu": [ "arm64" ], @@ -826,9 +830,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.11.0.tgz", - "integrity": "sha512-kKT9XIuhbvYgiA3cPAGntvrBgzhWkGpBMzuk1V12Xuoqg7CI41chye4HU0vLJnGf9MiZzfNh4I7StPeOzOWJfA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz", + "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", "cpu": [ "riscv64" ], @@ -839,9 +843,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.11.0.tgz", - "integrity": "sha512-6q4ESWlyTO+erp1PSCmASac+ixaDv11dBk1fqyIuvIUc/CmRAX2Zk+2qK1FGo5q7kyDcjHCFVwgGFCGIZGVwCA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz", + "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", "cpu": [ "x64" ], @@ -852,9 +856,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.11.0.tgz", - "integrity": "sha512-vIAQUmXeMLmaDN78HSE4Kh6xqof2e3TJUKr+LPqXWU4NYNON0MDN9h2+t4KHrPAQNmU3w1GxBQ/n01PaWFwa5w==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz", + "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", "cpu": [ "x64" ], @@ -865,9 +869,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.11.0.tgz", - "integrity": "sha512-LVXo9dDTGPr0nezMdqa1hK4JeoMZ02nstUxGYY/sMIDtTYlli1ZxTXBYAz3vzuuvKO4X6NBETciIh7N9+abT1g==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz", + "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", "cpu": [ "arm64" ], @@ -878,9 +882,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.11.0.tgz", - "integrity": "sha512-xZVt6K70Gr3I7nUhug2dN6VRR1ibot3rXqXS3wo+8JP64t7djc3lBFyqO4GiVrhNaAIhUCJtwQ/20dr0h0thmQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz", + "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", "cpu": [ "ia32" ], @@ -891,9 +895,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.11.0.tgz", - "integrity": "sha512-f3I7h9oTg79UitEco9/2bzwdciYkWr8pITs3meSDSlr1TdvQ7IxkQaaYN2YqZXX5uZhiYL+VuYDmHwNzhx+HOg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz", + "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==", "cpu": [ "x64" ], @@ -920,21 +924,36 @@ "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1" } }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.10.tgz", + "integrity": "sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw==", + "dev": true, + "dependencies": { + "lodash.castarray": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, "node_modules/@tanstack/virtual-core": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.0.0.tgz", - "integrity": "sha512-SYXOBTjJb05rXa2vl55TTwO40A6wKu0R5i1qQwhJYNDIqaIGF7D0HsLw+pJAyi2OvntlEIVusx3xtbbgSUi6zg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.1.2.tgz", + "integrity": "sha512-DATZJs8iejkIUqXZe6ruDAnjFo78BKnIIgqQZrc7CmEFqfLEN/TPD91n4hRfo6hpRB6xC00bwKxv7vdjFNEmOg==", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" } }, "node_modules/@tanstack/vue-virtual": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.0.4.tgz", - "integrity": "sha512-SDk3n+dMZnAXEAx+HZ0wQwBN00Ne7Qk4z29QnawJy6jzOOpoajt7Mm6gIEERin1D4ALxW5WpuDqhlnV2TNFQ+A==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.1.2.tgz", + "integrity": "sha512-RmUnhsFtRw9p4Ti/+rG2Hr3y4yFhs8Xdsn7x9tkPoKINbVya/5RSCoNUCCAg2iXNjOI5a55iBNzNV0SVwxMwKA==", "dependencies": { - "@tanstack/virtual-core": "3.0.0" + "@tanstack/virtual-core": "3.1.2" }, "funding": { "type": "github", @@ -956,6 +975,28 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/linkify-it": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", + "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", + "dev": true + }, + "node_modules/@types/markdown-it": { + "version": "13.0.7", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.7.tgz", + "integrity": "sha512-U/CBi2YUUcTHBt5tjO2r5QV/x0Po6nsYwQU4Y04fBS6vfoImaiZ6f8bi3CjTCxBPQSO1LMyUqkByzi8AidyxfA==", + "dev": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", + "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", + "dev": true + }, "node_modules/@types/node": { "version": "20.11.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", @@ -1372,15 +1413,50 @@ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz", "integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==" }, + "node_modules/@vueuse/components": { + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/@vueuse/components/-/components-10.8.0.tgz", + "integrity": "sha512-5k/4Cxgt+aoxeHIOWSet6kkHXY+96QuPkJzGpOHaCj9DD0ASBni6L/wHQUWL118Ac9xq5+QQJuK5VvFs/yBAEw==", + "dependencies": { + "@vueuse/core": "10.8.0", + "@vueuse/shared": "10.8.0", + "vue-demi": ">=0.14.7" + } + }, + "node_modules/@vueuse/components/node_modules/vue-demi": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/@vueuse/core": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.7.2.tgz", - "integrity": "sha512-AOyAL2rK0By62Hm+iqQn6Rbu8bfmbgaIMXcE3TSr7BdQ42wnSFlwIdPjInO62onYsEMK/yDMU8C6oGfDAtZ2qQ==", + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.8.0.tgz", + "integrity": "sha512-G9Ok9fjx10TkNIPn8V1dJmK1NcdJCtYmDRyYiTMUyJ1p0Tywc1zmOoCQ2xhHYyz8ULBU4KjIJQ9n+Lrty74iVw==", "dependencies": { "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "10.7.2", - "@vueuse/shared": "10.7.2", - "vue-demi": ">=0.14.6" + "@vueuse/metadata": "10.8.0", + "@vueuse/shared": "10.8.0", + "vue-demi": ">=0.14.7" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -1412,19 +1488,19 @@ } }, "node_modules/@vueuse/metadata": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.7.2.tgz", - "integrity": "sha512-kCWPb4J2KGrwLtn1eJwaJD742u1k5h6v/St5wFe8Quih90+k2a0JP8BS4Zp34XUuJqS2AxFYMb1wjUL8HfhWsQ==", + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.8.0.tgz", + "integrity": "sha512-Nim/Vle5OgXcXhAvGOgkJQXB1Yb+Kq/fMbLuv3YYDYbiQrwr39ljuD4k9fPeq4yUyokYRo2RaNQmbbIMWB/9+w==", "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/shared": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.7.2.tgz", - "integrity": "sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==", + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.8.0.tgz", + "integrity": "sha512-dUdy6zwHhULGxmr9YUg8e+EnB39gcM4Fe2oKBSrh3cOsV30JcMPtsyuspgFCUo5xxFNaeMf/W2yyKfST7Bg8oQ==", "dependencies": { - "vue-demi": ">=0.14.6" + "vue-demi": ">=0.14.7" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -1540,9 +1616,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "peer": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/array-union": { "version": "2.1.0", @@ -1679,9 +1753,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001587", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz", - "integrity": "sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA==", + "version": "1.0.30001589", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001589.tgz", + "integrity": "sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg==", "dev": true, "funding": [ { @@ -1907,9 +1981,9 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/electron-to-chromium": { - "version": "1.4.671", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.671.tgz", - "integrity": "sha512-UUlE+/rWbydmp+FW8xlnnTA5WNA0ZZd2XL8CuMS72rh+k4y1f8+z6yk3UQhEwqHQWj6IBdL78DwWOdGMvYfQyA==", + "version": "1.4.679", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.679.tgz", + "integrity": "sha512-NhQMsz5k0d6m9z3qAxnsOR/ebal4NAGsrNVRwcDo4Kc/zQ7KdsTKZUxZoygHcVRb0QDW3waEDIcE3isZ79RP6g==", "dev": true }, "node_modules/emoji-regex": { @@ -2107,6 +2181,19 @@ "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/eslint-plugin-vue/node_modules/postcss-selector-parser": { + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", + "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -2342,9 +2429,9 @@ } }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true, "peer": true }, @@ -2759,6 +2846,14 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2781,12 +2876,23 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.castarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", + "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/lru-cache": { "version": "6.0.0", @@ -2811,6 +2917,22 @@ "node": ">=12" } }, + "node_modules/markdown-it": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.0.0.tgz", + "integrity": "sha512-seFjF0FIcPt4P9U39Bq1JYblX0KZCjDLFFQPHpL5AzHpqPEKtosxmdq/LTVZnjfH7tjt9BxStm+wXcDBNuYmzw==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.0.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, "node_modules/md5": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", @@ -2821,6 +2943,11 @@ "is-buffer": "~1.1.6" } }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3239,9 +3366,9 @@ } }, "node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.0.tgz", - "integrity": "sha512-p3cz0JV5vw/XeouBU3Ldnp+ZkBjE+n8ydJ4mcwBrOiXXPqNlrzGBqWs9X4MWF7f+iKUBu794Y8Hh8yawiJbCjw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", + "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", "engines": { "node": ">=14" }, @@ -3267,7 +3394,7 @@ "postcss": "^8.2.14" } }, - "node_modules/postcss-selector-parser": { + "node_modules/postcss-nested/node_modules/postcss-selector-parser": { "version": "6.0.15", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", @@ -3279,6 +3406,19 @@ "node": ">=4" } }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", @@ -3332,6 +3472,14 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "engines": { + "node": ">=6" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3422,9 +3570,9 @@ } }, "node_modules/rollup": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.11.0.tgz", - "integrity": "sha512-2xIbaXDXjf3u2tajvA5xROpib7eegJ9Y/uPlSFhXLNpK9ampCczXAhLEb5yLzJyG3LAdI1NWtNjDXiLyniNdjQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -3437,19 +3585,19 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.11.0", - "@rollup/rollup-android-arm64": "4.11.0", - "@rollup/rollup-darwin-arm64": "4.11.0", - "@rollup/rollup-darwin-x64": "4.11.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.11.0", - "@rollup/rollup-linux-arm64-gnu": "4.11.0", - "@rollup/rollup-linux-arm64-musl": "4.11.0", - "@rollup/rollup-linux-riscv64-gnu": "4.11.0", - "@rollup/rollup-linux-x64-gnu": "4.11.0", - "@rollup/rollup-linux-x64-musl": "4.11.0", - "@rollup/rollup-win32-arm64-msvc": "4.11.0", - "@rollup/rollup-win32-ia32-msvc": "4.11.0", - "@rollup/rollup-win32-x64-msvc": "4.11.0", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, @@ -3756,6 +3904,18 @@ "node": ">=14.0.0" } }, + "node_modules/tailwindcss/node_modules/postcss-selector-parser": { + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", + "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -3855,6 +4015,11 @@ "node": ">=14.17" } }, + "node_modules/uc.micro": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.0.0.tgz", + "integrity": "sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig==" + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -3907,9 +4072,9 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/vite": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", - "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.4.tgz", + "integrity": "sha512-n+MPqzq+d9nMVTKyewqw6kSt+R3CkvF9QAKY8obiQn8g1fwTscKxyfaYnC632HtBXAQGc1Yjomphwn1dtwGAHg==", "dev": true, "dependencies": { "esbuild": "^0.19.3", @@ -4006,11 +4171,11 @@ } }, "node_modules/vue-router": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.5.tgz", - "integrity": "sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.0.tgz", + "integrity": "sha512-dqUcs8tUeG+ssgWhcPbjHvazML16Oga5w34uCUmsk7i0BcnskoLGwjpa15fqMr2Fa5JgVBrdL2MEgqz6XZ/6IQ==", "dependencies": { - "@vue/devtools-api": "^6.5.0" + "@vue/devtools-api": "^6.5.1" }, "funding": { "url": "https://github.com/sponsors/posva" diff --git a/webui/package.json b/webui/package.json index 284e621e..b3b8ece5 100644 --- a/webui/package.json +++ b/webui/package.json @@ -14,8 +14,10 @@ "@headlessui/vue": "^1.7.4", "@heroicons/vue": "^2.0.13", "@tailwindcss/forms": "^0.5.3", + "@vueuse/components": "^10.8.0", "@vueuse/core": "^10.1.2", "cropperjs": "^1.5.13", + "markdown-it": "^14.0.0", "vue": "^3.2.41", "vue-router": "^4.1.6", "vue3-avataaars": "^1.0.12", @@ -23,6 +25,8 @@ }, "devDependencies": { "@rushstack/eslint-patch": "^1.1.4", + "@tailwindcss/typography": "^0.5.10", + "@types/markdown-it": "^13.0.7", "@types/node": "^20.1.1", "@vitejs/plugin-vue": "^5.0.0", "@vue/eslint-config-prettier": "^9.0.0", diff --git a/webui/src/api.ts b/webui/src/api.ts index 493746f9..3c91d932 100644 --- a/webui/src/api.ts +++ b/webui/src/api.ts @@ -8,6 +8,7 @@ interface Session { id: string auth: boolean user_id: string | null + user_name: string } let sess: Session = JSON.parse(window.localStorage.getItem('sess')!) @@ -58,7 +59,17 @@ export default { if (!token) return - await api_fetch('POST', '/client/reauth', token) + const resp = await api_fetch('POST', '/client/reauth', token) + const session_id = resp?.headers.get('Session-ID') + if (!session_id) { + window.localStorage.removeItem('sess') + window.localStorage.removeItem('token') + router.push({ name: 'Login' }) + return + } + + sess.id = session_id + window.localStorage.setItem('sess', JSON.stringify(sess)) }, async connect(token?: string | null) { @@ -77,7 +88,7 @@ export default { } /* Readonly! Use ws/users for updated states */ - sess = { id: session_id, auth: false, user_id: null } + sess = { id: session_id, auth: false, user_id: null, user_name: '' } window.localStorage.setItem('sess', JSON.stringify(sess)) }, @@ -94,6 +105,7 @@ export default { const user = JSON.parse(await resp?.text()) sess.user_id = user.id + sess.user_name = name window.localStorage.setItem('sess', JSON.stringify(sess)) router.push({ name: 'Home' }) @@ -107,12 +119,14 @@ export default { return await api_fetch('GET', '/chat', null) }, - async speaker(user_id: string) { - await api_fetch('POST', '/client/speaker', user_id) + async speaker(user_id: string | null) { + if (user_id) + await api_fetch('POST', '/client/speaker', user_id) }, - async listener(user_id: string) { - await api_fetch('POST', '/client/listener', user_id) + async listener(user_id: string | null) { + if (user_id) + await api_fetch('POST', '/client/listener', user_id) }, async websocket() { @@ -120,8 +134,8 @@ export default { }, async hangup() { - Webrtc.hangup() - await api_fetch('POST', '/client/hangup', null) + Webrtc.hangup() + await api_fetch('POST', '/client/hangup', null) }, async logout(force: boolean) { @@ -142,8 +156,7 @@ export default { return await api_fetch('PUT', '/webrtc/sdp/answer?id=' + id, desc) }, - async sdp_candidate(cand: RTCIceCandidate | null, id: number) - { + async sdp_candidate(cand: RTCIceCandidate | null, id: number) { return await api_fetch('PUT', '/webrtc/sdp/candidate?id=' + id, cand) }, @@ -165,6 +178,13 @@ export default { await api_fetch('PUT', '/webrtc/solo', dev) }, + async video_solo(dev: string, enable: boolean) { + if (enable) + await api_fetch('PUT', '/webrtc/solo/enable', dev) + else + await api_fetch('PUT', '/webrtc/solo/disable', dev) + }, + async record_switch(type: RecordType) { if (!Users.host_status.value) return diff --git a/webui/src/components/BottomActions.vue b/webui/src/components/BottomActions.vue index 96cbd793..10b3f022 100644 --- a/webui/src/components/BottomActions.vue +++ b/webui/src/components/BottomActions.vue @@ -32,6 +32,7 @@ Logout @@ -57,7 +58,7 @@ -
+
-
+
-
    +
    • -
      +
      @@ -34,20 +34,20 @@ /> -
      +
      {{ item.user_name }} {{ formatTimeAgo(new Date(parseInt(item.time) * 1000)) }}
      -

      {{ item.msg }}

      +
    • -
      +
      @@ -60,20 +60,30 @@ /> -
      +
      {{ item.user_name }} {{ formatTimeAgo(new Date(parseInt(item.time) * 1000)) }}
      -

      {{ item.msg }}

      +
    -
    +
    + +
    +
    @@ -84,18 +94,18 @@
    - + >
    diff --git a/webui/src/components/SettingsModal.vue b/webui/src/components/SettingsModal.vue index 18b61d45..82778d69 100644 --- a/webui/src/components/SettingsModal.vue +++ b/webui/src/components/SettingsModal.vue @@ -158,13 +158,20 @@
    -
    +
    +
    @@ -178,10 +185,11 @@ diff --git a/webui/src/components/StudioNav.vue b/webui/src/components/StudioNav.vue index 2cdbd2b3..01c4ac23 100644 --- a/webui/src/components/StudioNav.vue +++ b/webui/src/components/StudioNav.vue @@ -1,7 +1,7 @@