From d18bd918455d7984acc9fa24aba14fbe7d10133a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20T=C3=B6rnblom?= Date: Mon, 7 Jan 2013 19:52:48 +0100 Subject: [PATCH] dvr: added initial support for libavformat muxing --- Makefile | 4 + configure | 27 ++ src/libav.c | 104 ++++++++ src/libav.h | 31 +++ src/main.c | 7 + src/muxer.c | 48 +++- src/muxer.h | 4 + src/muxer/muxer_libav.c | 508 ++++++++++++++++++++++++++++++++++++ src/muxer/muxer_libav.h | 26 ++ src/webui/extjs.c | 42 +++ src/webui/static/app/dvr.js | 21 +- 11 files changed, 814 insertions(+), 8 deletions(-) create mode 100644 src/libav.c create mode 100644 src/libav.h create mode 100644 src/muxer/muxer_libav.c create mode 100644 src/muxer/muxer_libav.h diff --git a/Makefile b/Makefile index d8841397..d8d6197e 100644 --- a/Makefile +++ b/Makefile @@ -171,6 +171,10 @@ SRCS-${CONFIG_V4L} += \ # Avahi SRCS-$(CONFIG_AVAHI) += src/avahi.c +# libav +SRCS-$(CONFIG_LIBAV) += src/libav.c \ + src/muxer/muxer_libav.c + # CWC SRCS-${CONFIG_CWC} += src/cwc.c \ src/capmt.c diff --git a/configure b/configure index 765a9898..5a5ce0ee 100755 --- a/configure +++ b/configure @@ -23,6 +23,7 @@ OPTIONS=( "imagecache:auto" "avahi:auto" "zlib:auto" + "libav:auto" "bundle:no" "dvbcsa:no" ) @@ -100,6 +101,32 @@ if enabled_or_auto avahi; then fi fi +# +# libav +# +if enabled_or_auto libav; then + has_libav=true + + if $has_libav && ! check_pkg libavutil ">=50.43.0"; then + has_libav=false + fi + + if $has_libav && ! check_pkg libavformat "<=55.0.0"; then + has_libav=false + fi + + if $has_libav && ! check_pkg libavformat ">=50.43.0"; then + has_libav=false + fi + + if $has_libav; then + enable libav + elif enabled libav; then + die "libav development support not found (use --disable-libav)" + fi +fi + + # # DVB scan # diff --git a/src/libav.c b/src/libav.c new file mode 100644 index 00000000..e867eea4 --- /dev/null +++ b/src/libav.c @@ -0,0 +1,104 @@ +#include "libav.h" + +/** + * + */ +static void +libav_log_callback(void *ptr, int level, const char *fmt, va_list vl) +{ + char message[8192]; + char *nl; + char *l; + + memset(message, 0, sizeof(message)); + vsnprintf(message, sizeof(message), fmt, vl); + + l = message; + + if(level == AV_LOG_DEBUG) + level = LOG_DEBUG; + else if(level == AV_LOG_VERBOSE) + level = LOG_INFO; + else if(level == AV_LOG_INFO) + level = LOG_NOTICE; + else if(level == AV_LOG_WARNING) + level = LOG_WARNING; + else if(level == AV_LOG_ERROR) + level = LOG_ERR; + else if(level == AV_LOG_FATAL) + level = LOG_CRIT; + else if(level == AV_LOG_PANIC) + level = LOG_EMERG; + + while(l < message + sizeof(message)) { + nl = strstr(l, "\n"); + if(nl) + *nl = '\0'; + + if(!strlen(l)) + break; + + tvhlog(level, "libav", "%s", l); + + l += strlen(message); + + if(!nl) + break; + } +} + +/** + * Translate a component type to a libavcodec id + */ +enum CodecID +streaming_component_type2codec_id(streaming_component_type_t type) +{ + enum CodecID codec_id = CODEC_ID_NONE; + + switch(type) { + case SCT_H264: + codec_id = CODEC_ID_H264; + break; + case SCT_MPEG2VIDEO: + codec_id = CODEC_ID_MPEG2VIDEO; + break; + case SCT_AC3: + codec_id = CODEC_ID_AC3; + break; + case SCT_EAC3: + codec_id = CODEC_ID_EAC3; + break; + case SCT_AAC: + codec_id = CODEC_ID_AAC; + break; + case SCT_MPEG2AUDIO: + codec_id = CODEC_ID_MP2; + break; + case SCT_DVBSUB: + codec_id = CODEC_ID_DVB_SUBTITLE; + break; + case SCT_TEXTSUB: + codec_id = CODEC_ID_TEXT; + break; + case SCT_TELETEXT: + codec_id = CODEC_ID_DVB_TELETEXT; + break; + default: + codec_id = CODEC_ID_NONE; + break; + } + + return codec_id; +} + +/** + * + */ +void +libav_init(void) +{ + av_log_set_callback(libav_log_callback); + av_log_set_level(AV_LOG_VERBOSE); + av_register_all(); +} + diff --git a/src/libav.h b/src/libav.h new file mode 100644 index 00000000..c8a6ed7a --- /dev/null +++ b/src/libav.h @@ -0,0 +1,31 @@ +/* + * tvheadend, libav utils + * Copyright (C) 2012 John Törnblom + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIBAV_H_ +#define LIBAV_H_ + + +#include +#include "tvheadend.h" + +enum CodecID streaming_component_type2codec_id(streaming_component_type_t type); + +void libav_init(void); + +#endif + diff --git a/src/main.c b/src/main.c index 704f6e4b..f0a58be5 100644 --- a/src/main.c +++ b/src/main.c @@ -61,6 +61,9 @@ #include "muxes.h" #include "config2.h" #include "imagecache.h" +#if ENABLE_LIBAV +#include "libav.h" +#endif int running; time_t dispatch_clock; @@ -466,6 +469,10 @@ main(int argc, char **argv) * Initialize subsystems */ +#if ENABLE_LIBAV + libav_init(); +#endif + config_init(); imagecache_init(); diff --git a/src/muxer.c b/src/muxer.c index 6ec3a8a1..2de07edf 100644 --- a/src/muxer.c +++ b/src/muxer.c @@ -23,7 +23,9 @@ #include "muxer.h" #include "muxer/muxer_tvh.h" #include "muxer/muxer_pass.h" - +#if CONFIG_LIBAV +#include "muxer/muxer_libav.h" +#endif /** * Mime type for containers containing only audio @@ -140,6 +142,45 @@ muxer_container_type2txt(muxer_container_type_t mc) } +/** + * Get a list of supported containers + */ +int +muxer_container_list(htsmsg_t *array) +{ + htsmsg_t *mc; + int c = 0; + + mc = htsmsg_create_map(); + htsmsg_add_str(mc, "name", muxer_container_type2txt(MC_MATROSKA)); + htsmsg_add_str(mc, "description", "Matroska"); + htsmsg_add_msg(array, NULL, mc); + c++; + + mc = htsmsg_create_map(); + htsmsg_add_str(mc, "name", muxer_container_type2txt(MC_PASS)); + htsmsg_add_str(mc, "description", "Same as source (pass through)"); + htsmsg_add_msg(array, NULL, mc); + c++; + +#if ENABLE_LIBAV + mc = htsmsg_create_map(); + htsmsg_add_str(mc, "name", muxer_container_type2txt(MC_MPEGTS)); + htsmsg_add_str(mc, "description", "MPEG-TS"); + htsmsg_add_msg(array, NULL, mc); + c++; + + mc = htsmsg_create_map(); + htsmsg_add_str(mc, "name", muxer_container_type2txt(MC_MPEGPS)); + htsmsg_add_str(mc, "description", "MPEG-PS (DVD)"); + htsmsg_add_msg(array, NULL, mc); + c++; +#endif + + return c; +} + + /** * Convert a container name to a container type */ @@ -194,6 +235,11 @@ muxer_create(muxer_container_type_t mc) if(!m) m = tvh_muxer_create(mc); +#if CONFIG_LIBAV + if(!m) + m = lav_muxer_create(mc); +#endif + if(!m) tvhlog(LOG_ERR, "mux", "Can't find a muxer that supports '%s' container", muxer_container_type2txt(mc)); diff --git a/src/muxer.h b/src/muxer.h index 766c5dd4..6d57cd7f 100644 --- a/src/muxer.h +++ b/src/muxer.h @@ -19,6 +19,8 @@ #ifndef MUXER_H_ #define MUXER_H_ +#include "htsmsg.h" + typedef enum { MC_UNKNOWN = 0, MC_MATROSKA = 1, @@ -65,6 +67,8 @@ muxer_container_type_t muxer_container_mime2type (const char *str); const char* muxer_container_suffix(muxer_container_type_t mc, int video); +int muxer_container_list(htsmsg_t *array); + // Muxer factory muxer_t *muxer_create(muxer_container_type_t mc); diff --git a/src/muxer/muxer_libav.c b/src/muxer/muxer_libav.c new file mode 100644 index 00000000..7837afe6 --- /dev/null +++ b/src/muxer/muxer_libav.c @@ -0,0 +1,508 @@ +/* + * tvheadend, libavformat based muxer + * Copyright (C) 2012 John Törnblom + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include "tvheadend.h" +#include "streaming.h" +#include "epg.h" +#include "channels.h" +#include "libav.h" +#include "muxer_libav.h" + +typedef struct lav_muxer { + muxer_t; + AVFormatContext *lm_oc; + AVBitStreamFilterContext *lm_h264_filter; + int lm_fd; +} lav_muxer_t; + +#define MUX_BUF_SIZE 4096 + +static const AVRational mpeg_tc = {1, 90000}; + + +/** + * Callback function for libavformat + */ +static int +lav_muxer_write(void *opaque, uint8_t *buf, int buf_size) +{ + int r; + lav_muxer_t *lm = (lav_muxer_t*)opaque; + + r = write(lm->lm_fd, buf, buf_size); + lm->m_errors += (r != buf_size); + + return r; +} + + +/** + * Add a stream to the muxer + */ +static int +lav_muxer_add_stream(lav_muxer_t *lm, + const streaming_start_component_t *ssc) +{ + AVStream *st; + AVCodecContext *c; + + st = avformat_new_stream(lm->lm_oc, NULL); + if (!st) + return -1; + + st->id = ssc->ssc_index; + c = st->codec; + c->codec_id = streaming_component_type2codec_id(ssc->ssc_type); + + switch(lm->m_container) { + case MC_MATROSKA: + st->time_base.num = 1000000; + st->time_base.den = 1; + break; + + case MC_MPEGPS: + c->rc_buffer_size = 224*1024*8; + //Fall-through + case MC_MPEGTS: + st->time_base.num = 90000; + st->time_base.den = 1; + break; + + default: + st->time_base = AV_TIME_BASE_Q; + break; + } + + + + if(ssc->ssc_gh) { + c->extradata_size = pktbuf_len(ssc->ssc_gh); + c->extradata = av_malloc(c->extradata_size); + memcpy(c->extradata, pktbuf_ptr(ssc->ssc_gh), + pktbuf_len(ssc->ssc_gh)); + } + + if(SCT_ISAUDIO(ssc->ssc_type)) { + c->codec_type = AVMEDIA_TYPE_AUDIO; + c->sample_fmt = AV_SAMPLE_FMT_S16; + + c->sample_rate = sri_to_rate(ssc->ssc_sri); + c->channels = ssc->ssc_channels; + + c->time_base.num = 1; + c->time_base.den = c->sample_rate; + + av_dict_set(&st->metadata, "language", ssc->ssc_lang, 0); + + } else if(SCT_ISVIDEO(ssc->ssc_type)) { + c->codec_type = AVMEDIA_TYPE_VIDEO; + c->width = ssc->ssc_width; + c->height = ssc->ssc_height; + + c->time_base.num = 1; + c->time_base.den = 25; + + c->sample_aspect_ratio.num = ssc->ssc_aspect_num; + c->sample_aspect_ratio.den = ssc->ssc_aspect_den; + + st->sample_aspect_ratio.num = c->sample_aspect_ratio.num; + st->sample_aspect_ratio.den = c->sample_aspect_ratio.den; + + } else if(SCT_ISSUBTITLE(ssc->ssc_type)) { + c->codec_type = AVMEDIA_TYPE_SUBTITLE; + av_dict_set(&st->metadata, "language", ssc->ssc_lang, 0); + } + + if(lm->lm_oc->oformat->flags & AVFMT_GLOBALHEADER) + c->flags |= CODEC_FLAG_GLOBAL_HEADER; + + return 0; +} + + +/** + * Check if a container supports a given streaming component + */ +static int +lav_muxer_support_stream(muxer_container_type_t mc, + streaming_component_type_t type) +{ + int ret = 0; + + switch(mc) { + case MC_MATROSKA: + ret |= SCT_ISAUDIO(type); + ret |= SCT_ISVIDEO(type); + ret |= SCT_ISSUBTITLE(type); + break; + + case MC_MPEGTS: + ret |= (type == SCT_MPEG2VIDEO); + ret |= (type == SCT_H264); + + ret |= (type == SCT_MPEG2AUDIO); + ret |= (type == SCT_AC3); + ret |= (type == SCT_AAC); + ret |= (type == SCT_MP4A); + ret |= (type == SCT_EAC3); + + //Some pids lack pts, disable for now + //ret |= (type == SCT_TELETEXT); + ret |= (type == SCT_DVBSUB); + break; + + case MC_MPEGPS: + ret |= (type == SCT_MPEG2VIDEO); + ret |= (type == SCT_MPEG2AUDIO); + ret |= (type == SCT_AC3); + + default: + break; + } + + return ret; +} + + +/** + * Figure out the mime-type for the muxed data stream + */ +static const char* +lav_muxer_mime(muxer_t* m, const struct streaming_start *ss) +{ + int i; + int has_audio; + int has_video; + const streaming_start_component_t *ssc; + + has_audio = 0; + has_video = 0; + + for(i=0; i < ss->ss_num_components; i++) { + ssc = &ss->ss_components[i]; + + if(ssc->ssc_disabled) + continue; + + if(!lav_muxer_support_stream(m->m_container, ssc->ssc_type)) + continue; + + has_video |= SCT_ISVIDEO(ssc->ssc_type); + has_audio |= SCT_ISAUDIO(ssc->ssc_type); + } + + if(has_video) + return muxer_container_type2mime(m->m_container, 1); + else if(has_audio) + return muxer_container_type2mime(m->m_container, 0); + else + return muxer_container_type2mime(MC_UNKNOWN, 0); +} + + +/** + * Init the muxer with streams + */ +static int +lav_muxer_init(muxer_t* m, const struct streaming_start *ss, const char *name) +{ + int i; + const streaming_start_component_t *ssc; + AVFormatContext *oc; + lav_muxer_t *lm = (lav_muxer_t*)m; + char app[128]; + + snprintf(app, sizeof(app), "Tvheadend %s", tvheadend_version); + + oc = lm->lm_oc; + + av_dict_set(&oc->metadata, "title", name, 0); + av_dict_set(&oc->metadata, "service_name", name, 0); + av_dict_set(&oc->metadata, "service_provider", app, 0); + + if(lm->m_container == MC_MPEGTS) + lm->lm_h264_filter = av_bitstream_filter_init("h264_mp4toannexb"); + + oc->max_delay = 0.7 * AV_TIME_BASE; + + for(i=0; i < ss->ss_num_components; i++) { + ssc = &ss->ss_components[i]; + + if(ssc->ssc_disabled) + continue; + + if(!lav_muxer_support_stream(lm->m_container, ssc->ssc_type)) { + tvhlog(LOG_WARNING, "libav", "%s is not supported in %s", + streaming_component_type2txt(ssc->ssc_type), + muxer_container_type2txt(lm->m_container)); + continue; + } + + if(lav_muxer_add_stream(lm, ssc)) { + tvhlog(LOG_ERR, "libav", "Failed to add %s stream", + streaming_component_type2txt(ssc->ssc_type)); + continue; + } + } + + if(!lm->lm_oc->nb_streams) { + tvhlog(LOG_ERR, "libav", "No supported streams available"); + lm->m_errors++; + return -1; + } else if(avformat_write_header(lm->lm_oc, NULL) < 0) { + tvhlog(LOG_ERR, "libav", "Failed to write %s header", + muxer_container_type2txt(lm->m_container)); + lm->m_errors++; + return -1; + } + + return 0; +} + + +/** + * Handle changes to the streams (usually PMT updates) + */ +static int +lav_muxer_reconfigure(muxer_t* m, const struct streaming_start *ss) +{ + lav_muxer_t *lm = (lav_muxer_t*)m; + + lm->m_errors++; + + return -1; +} + + +/** + * Open the muxer and write the header + */ +static int +lav_muxer_open_stream(muxer_t *m, int fd) +{ + uint8_t *buf; + AVIOContext *pb; + lav_muxer_t *lm = (lav_muxer_t*)m; + + buf = av_malloc(MUX_BUF_SIZE); + pb = avio_alloc_context(buf, MUX_BUF_SIZE, 1, lm, NULL, + lav_muxer_write, NULL); + pb->seekable = 0; + lm->lm_oc->pb = pb; + lm->lm_fd = fd; + + return 0; +} + + +static int +lav_muxer_open_file(muxer_t *m, const char *filename) +{ + AVFormatContext *oc; + lav_muxer_t *lm = (lav_muxer_t*)m; + + oc = lm->lm_oc; + snprintf(oc->filename, sizeof(oc->filename), "%s", filename); + + if(avio_open(&oc->pb, filename, AVIO_FLAG_WRITE) < 0) { + tvhlog(LOG_ERR, "libav", "Could not open %s", filename); + lm->m_errors++; + return -1; + } + + return 0; +} + + +/** + * Write a packet to the muxer + */ +static int +lav_muxer_write_pkt(muxer_t *m, streaming_message_type_t smt, void *data) +{ + int i; + AVFormatContext *oc; + AVStream *st; + AVPacket packet; + th_pkt_t *pkt = (th_pkt_t*)data; + lav_muxer_t *lm = (lav_muxer_t*)m; + + assert(smt == SMT_PACKET); + + oc = lm->lm_oc; + + if(!oc->nb_streams) { + tvhlog(LOG_ERR, "libav", "No streams to mux"); + lm->m_errors++; + return -1; + } + + for(i=0; inb_streams; i++) { + st = oc->streams[i]; + + if(st->id != pkt->pkt_componentindex) + continue; + + av_init_packet(&packet); + + if(st->codec->codec_id == CODEC_ID_MPEG2VIDEO) + pkt = pkt_merge_header(pkt); + + if(lm->lm_h264_filter && st->codec->codec_id == CODEC_ID_H264) { + av_bitstream_filter_filter(lm->lm_h264_filter, + st->codec, + NULL, + &packet.data, + &packet.size, + pktbuf_ptr(pkt->pkt_payload), + pktbuf_len(pkt->pkt_payload), + pkt->pkt_frametype < PKT_P_FRAME); + } else { + packet.data = pktbuf_ptr(pkt->pkt_payload); + packet.size = pktbuf_len(pkt->pkt_payload); + } + + packet.stream_index = st->index; + + packet.pts = av_rescale_q(pkt->pkt_pts , mpeg_tc, st->time_base); + packet.dts = av_rescale_q(pkt->pkt_dts , mpeg_tc, st->time_base); + packet.duration = av_rescale_q(pkt->pkt_duration, mpeg_tc, st->time_base); + + if(pkt->pkt_frametype < PKT_P_FRAME) + packet.flags |= AV_PKT_FLAG_KEY; + + if (av_interleaved_write_frame(oc, &packet) != 0) { + tvhlog(LOG_WARNING, "libav", "Failed to write frame"); + lm->m_errors++; + return -1; + } + + break; + } + + pkt_ref_dec(pkt); + + return 0; +} + + +/** + * NOP + */ +static int +lav_muxer_write_meta(muxer_t *m, struct epg_broadcast *eb) +{ + return 0; +} + + +/** + * Close the muxer and append trailer to output + */ +static int +lav_muxer_close(muxer_t *m) +{ + int i; + int ret = 0; + lav_muxer_t *lm = (lav_muxer_t*)m; + + if(lm->lm_oc->nb_streams && av_write_trailer(lm->lm_oc) < 0) { + tvhlog(LOG_WARNING, "libav", "Failed to write %s trailer", + muxer_container_type2txt(lm->m_container)); + lm->m_errors++; + ret = -1; + } + + if(lm->lm_h264_filter) + av_bitstream_filter_close(lm->lm_h264_filter); + + for(i=0; ilm_oc->nb_streams; i++) + av_freep(&lm->lm_oc->streams[i]->codec->extradata); + + lm->lm_oc->nb_streams = 0; + + return ret; +} + + +/** + * Free all memory associated with the muxer + */ +static void +lav_muxer_destroy(muxer_t *m) +{ + lav_muxer_t *lm = (lav_muxer_t*)m; + + if(lm->lm_oc && lm->lm_oc->pb) + av_free(lm->lm_oc->pb); + + if(lm->lm_oc) + av_free(lm->lm_oc); + + free(lm); +} + + +/** + * Create a new libavformat based muxer + */ +muxer_t* +lav_muxer_create(muxer_container_type_t mc) +{ + const char *mux_name; + lav_muxer_t *lm; + AVOutputFormat *fmt; + + switch(mc) { + case MC_MPEGPS: + mux_name = "dvd"; + break; + default: + mux_name = muxer_container_type2txt(mc); + break; + } + + fmt = av_guess_format(mux_name, NULL, NULL); + if(!fmt) { + tvhlog(LOG_ERR, "libav", "Can't find the '%s' muxer", mux_name); + return NULL; + } + + lm = calloc(1, sizeof(lav_muxer_t)); + lm->m_open_stream = lav_muxer_open_stream; + lm->m_open_file = lav_muxer_open_file; + lm->m_init = lav_muxer_init; + lm->m_reconfigure = lav_muxer_reconfigure; + lm->m_mime = lav_muxer_mime; + lm->m_write_meta = lav_muxer_write_meta; + lm->m_write_pkt = lav_muxer_write_pkt; + lm->m_close = lav_muxer_close; + lm->m_destroy = lav_muxer_destroy; + lm->m_container = mc; + lm->lm_oc = avformat_alloc_context(); + lm->lm_oc->oformat = fmt; + lm->lm_fd = -1; + + return (muxer_t*)lm; +} + diff --git a/src/muxer/muxer_libav.h b/src/muxer/muxer_libav.h new file mode 100644 index 00000000..a8325b06 --- /dev/null +++ b/src/muxer/muxer_libav.h @@ -0,0 +1,26 @@ +/* + * tvheadend, muxing of packets with libavformat + * Copyright (C) 2012 John Törnblom + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LAV_MUXER_H_ +#define LAV_MUXER_H_ + +#include "muxer.h" + +muxer_t* lav_muxer_create(muxer_container_type_t mc); + +#endif diff --git a/src/webui/extjs.c b/src/webui/extjs.c index 88496e6f..b7f98f43 100644 --- a/src/webui/extjs.c +++ b/src/webui/extjs.c @@ -736,6 +736,47 @@ skip: } + +/** + * + */ +static int +extjs_dvr_containers(http_connection_t *hc, const char *remain, void *opaque) +{ + htsbuf_queue_t *hq = &hc->hc_reply; + const char *op = http_arg_get(&hc->hc_req_args, "op"); + htsmsg_t *out, *array; + + pthread_mutex_lock(&global_lock); + + if(op != NULL && !strcmp(op, "list")) { + + out = htsmsg_create_map(); + array = htsmsg_create_list(); + + if (http_access_verify(hc, ACCESS_RECORDER_ALL)) + goto skip; + + muxer_container_list(array); + +skip: + htsmsg_add_msg(out, "entries", array); + + } else { + pthread_mutex_unlock(&global_lock); + return HTTP_STATUS_BAD_REQUEST; + } + + pthread_mutex_unlock(&global_lock); + + htsmsg_json_serialize(out, hq, 0); + htsmsg_destroy(out); + http_output_content(hc, "text/x-json; charset=UTF-8"); + return 0; + +} + + /** * */ @@ -2018,6 +2059,7 @@ extjs_start(void) http_path_add("/dvrlist_upcoming", NULL, extjs_dvrlist_upcoming, ACCESS_WEB_INTERFACE); http_path_add("/dvrlist_finished", NULL, extjs_dvrlist_finished, ACCESS_WEB_INTERFACE); http_path_add("/dvrlist_failed", NULL, extjs_dvrlist_failed, ACCESS_WEB_INTERFACE); + http_path_add("/dvr_containers", NULL, extjs_dvr_containers, ACCESS_WEB_INTERFACE); http_path_add("/subscriptions", NULL, extjs_subscriptions, ACCESS_WEB_INTERFACE); http_path_add("/ecglist", NULL, extjs_ecglist, ACCESS_WEB_INTERFACE); http_path_add("/config", NULL, extjs_config, ACCESS_WEB_INTERFACE); diff --git a/src/webui/static/app/dvr.js b/src/webui/static/app/dvr.js index fa7d4041..215fdf66 100644 --- a/src/webui/static/app/dvr.js +++ b/src/webui/static/app/dvr.js @@ -14,13 +14,20 @@ tvheadend.dvrprio = new Ext.data.SimpleStore({ [ 'unimportant', 'Unimportant' ] ] }); + //For the container configuration -tvheadend.containers = new Ext.data.SimpleStore({ - fields : [ 'identifier', 'name' ], - id : 0, - data : [ [ 'matroska', 'Matroska' ], [ 'pass', 'TS (Pass-through)' ] ] +tvheadend.containers = new Ext.data.JsonStore({ + autoLoad : true, + root : 'entries', + fields : [ 'name', 'description' ], + id : 'name', + url : 'dvr_containers', + baseParams : { + op : 'list' + } }); + /** * Configuration names */ @@ -743,11 +750,11 @@ tvheadend.dvrsettings = function() { }, new Ext.form.ComboBox({ store : tvheadend.containers, fieldLabel : 'Media container', - mode : 'local', triggerAction : 'all', - displayField : 'name', - valueField : 'identifier', + displayField : 'description', + valueField : 'name', editable : false, + width : 200, hiddenName : 'container' }), new Ext.form.NumberField({ allowNegative : false,