diff --git a/Makefile b/Makefile
index ac713add..f3699845 100644
--- a/Makefile
+++ b/Makefile
@@ -21,7 +21,7 @@ SRCS += iptv_input.c iptv_output.c
SRCS += avgen.c file_input.c
-SRCS += htsclient.c rtsp.c rtp.c
+SRCS += rtsp.c rtp.c
SRCS += v4l.c
diff --git a/autorec.c b/autorec.c
index b8a8fbbc..43a8bbf7 100644
--- a/autorec.c
+++ b/autorec.c
@@ -99,7 +99,7 @@ autorec_check_new_ar(autorec_t *ar)
event_t *e;
channel_t *ch;
- RB_FOREACH(ch, &channel_tree, ch_global_link) {
+ RB_FOREACH(ch, &channel_name_tree, ch_name_link) {
e = ch->ch_epg_cur_event;
if(e == NULL)
continue;
@@ -306,7 +306,7 @@ autorec_load(void)
contentgroup ?
epg_content_group_find_by_name(contentgroup) : NULL,
- channel ? channel_find(channel, 1) : NULL,
+ channel ? channel_find_by_name(channel, 1) : NULL,
id, creator);
if(id > ar_id_ceil)
diff --git a/avgen.c b/avgen.c
index b0020ed8..edc0bd74 100644
--- a/avgen.c
+++ b/avgen.c
@@ -98,7 +98,7 @@ avgen_init(void)
if(avcodec_find_encoder(CODEC_ID_MP2) == NULL)
return;
- ch = channel_find("Test PAL", 1);
+ ch = channel_find_by_name("Test PAL", 1);
t = calloc(1, sizeof(th_transport_t));
diff --git a/channels.c b/channels.c
index 9670435e..4823ce62 100644
--- a/channels.c
+++ b/channels.c
@@ -42,7 +42,8 @@
#include "pvr.h"
#include "autorec.h"
-struct channel_tree channel_tree;
+struct channel_tree channel_name_tree;
+static struct channel_tree channel_identifier_tree;
static int
dictcmp(const char *a, const char *b)
@@ -83,6 +84,16 @@ channelcmp(const channel_t *a, const channel_t *b)
}
+/**
+ *
+ */
+static int
+chidcmp(const channel_t *a, const channel_t *b)
+{
+ return a->ch_id - b->ch_id;
+}
+
+
/**
*
*/
@@ -115,38 +126,70 @@ channel_set_name(channel_t *ch, const char *name)
free((void *)n2);
- x = RB_INSERT_SORTED(&channel_tree, ch, ch_global_link, channelcmp);
+ x = RB_INSERT_SORTED(&channel_name_tree, ch, ch_name_link, channelcmp);
assert(x == NULL);
}
+
+
+/**
+ *
+ */
+static channel_t *
+channel_create(const char *name)
+{
+ channel_t *ch, *x;
+ int id;
+
+ ch = RB_LAST(&channel_identifier_tree);
+ if(ch == NULL) {
+ id = 1;
+ } else {
+ id = ch->ch_id + 1;
+ }
+
+ ch = calloc(1, sizeof(channel_t));
+ TAILQ_INIT(&ch->ch_epg_events);
+
+ channel_set_name(ch, name);
+
+ ch->ch_id = id;
+ x = RB_INSERT_SORTED(&channel_identifier_tree, ch,
+ ch_identifier_link, chidcmp);
+ assert(x == NULL);
+ return ch;
+}
+
/**
*
*/
channel_t *
-channel_find(const char *name, int create)
+channel_find_by_name(const char *name, int create)
{
- channel_t *ch, skel;
-
- skel.ch_name = name;
-
- if((ch = RB_FIND(&channel_tree, &skel, ch_global_link, channelcmp)) != NULL)
+ channel_t skel, *ch;
+ skel.ch_name = (char *)name;
+ ch = RB_FIND(&channel_name_tree, &skel, ch_name_link, channelcmp);
+ if(ch != NULL || create == 0)
return ch;
+ return channel_create(name);
+}
- if(create == 0)
- return NULL;
- ch = calloc(1, sizeof(channel_t));
- ch->ch_index = channel_tree.entries;
-
- TAILQ_INIT(&ch->ch_epg_events);
-
- channel_set_name(ch, name);
-
- ch->ch_tag = tag_get();
+/**
+ *
+ */
+channel_t *
+channel_find_by_identifier(int id)
+{
+ channel_t skel, *ch;
+ skel.ch_id = id;
+ ch = RB_FIND(&channel_identifier_tree, &skel, ch_identifier_link, chidcmp);
return ch;
}
+
+
static struct strtab commercial_detect_tab[] = {
{ "none", COMMERCIAL_DETECT_NONE },
{ "ttp192", COMMERCIAL_DETECT_TTP192 },
@@ -159,123 +202,11 @@ static struct strtab commercial_detect_tab[] = {
void
channels_load(void)
{
-#if 0
- struct config_head cl;
- config_entry_t *ce;
- char buf[PATH_MAX];
- DIR *dir;
- struct dirent *d;
- const char *name, *grp, *x;
- channel_t *ch;
- int v;
- TAILQ_INIT(&all_channel_groups);
- TAILQ_INIT(&cl);
-
- snprintf(buf, sizeof(buf), "%s/channel-group-settings.cfg", settings_dir);
- config_read_file0(buf, &cl);
-
- TAILQ_FOREACH(ce, &cl, ce_link) {
- if(ce->ce_type != CFG_SUB || strcasecmp("channel-group", ce->ce_key))
- continue;
-
- if((name = config_get_str_sub(&ce->ce_sub, "name", NULL)) == NULL)
- continue;
-
- channel_group_find(name, 1);
- }
- config_free0(&cl);
-
- tcg = channel_group_find("-disabled-", 1);
- tcg->tcg_cant_delete_me = 1;
- tcg->tcg_hidden = 1;
-
- defgroup = channel_group_find("Uncategorized", 1);
- defgroup->tcg_cant_delete_me = 1;
-
- snprintf(buf, sizeof(buf), "%s/channels", settings_dir);
-
- if((dir = opendir(buf)) == NULL)
- return;
-
- while((d = readdir(dir)) != NULL) {
-
- if(d->d_name[0] == '.')
- continue;
-
- snprintf(buf, sizeof(buf), "%s/channels/%s", settings_dir, d->d_name);
- TAILQ_INIT(&cl);
- config_read_file0(buf, &cl);
-
- name = config_get_str_sub(&cl, "name", NULL);
- grp = config_get_str_sub(&cl, "channel-group", NULL);
- if(name != NULL && grp != NULL) {
- tcg = channel_group_find(grp, 1);
- ch = channel_find(name, 1, tcg);
-
- x = config_get_str_sub(&cl, "commercial-detect", NULL);
- if(x != NULL) {
- v = str2val(x, commercial_detect_tab);
- if(v > 1)
- ch->ch_commercial_detection = v;
- }
-
- if((x = config_get_str_sub(&cl, "icon", NULL)) != NULL)
- ch->ch_icon = strdup(x);
- }
- config_free0(&cl);
- }
-
- closedir(dir);
-
-
- /* Static services */
-
- TAILQ_FOREACH(ce, &config_list, ce_link) {
- if(ce->ce_type == CFG_SUB && !strcasecmp("service", ce->ce_key)) {
- service_load(&ce->ce_sub);
- }
- }
-#endif
}
-/**
- * The index stuff should go away
- */
-channel_t *
-channel_by_index(uint32_t index)
-{
- channel_t *ch;
-
- RB_FOREACH(ch, &channel_tree, ch_global_link)
- if(ch->ch_index == index)
- return ch;
-
- return NULL;
-}
-
-
-
-/**
- *
- */
-channel_t *
-channel_by_tag(uint32_t tag)
-{
- channel_t *ch;
-
- RB_FOREACH(ch, &channel_tree, ch_global_link)
- if(ch->ch_tag == tag)
- return ch;
-
- return NULL;
-}
-
-
-
-
/**
* Write out a config file for a channel
*/
@@ -283,7 +214,9 @@ static void
channel_save(channel_t *ch)
{
htsmsg_t *m = htsmsg_create();
- htsmsg_add_str(m, "icon", ch->ch_icon);
+ if(ch->ch_icon != NULL)
+ htsmsg_add_str(m, "icon", ch->ch_icon);
+
htsmsg_add_str(m, "commercial_detect",
val2str(ch->ch_commercial_detection,
commercial_detect_tab) ?: "?");
@@ -299,12 +232,15 @@ channel_rename(channel_t *ch, const char *newname)
{
th_transport_t *t;
- if(channel_find(newname, 0))
+ if(channel_find_by_name(newname, 0))
return -1;
+ tvhlog(LOG_NOTICE, "channels", "Channel \"%s\" renamed to \"%s\"",
+ ch->ch_name, newname);
+
hts_settings_remove("channels/%s", ch->ch_name);
- RB_REMOVE(&channel_tree, ch, ch_global_link);
+ RB_REMOVE(&channel_name_tree, ch, ch_name_link);
channel_set_name(ch, newname);
LIST_FOREACH(t, &ch->ch_transports, tht_ch_link) {
@@ -326,6 +262,9 @@ channel_delete(channel_t *ch)
th_transport_t *t;
th_subscription_t *s;
+ tvhlog(LOG_NOTICE, "channels", "Channel \"%s\" deleted",
+ ch->ch_name);
+
pvr_destroy_by_channel(ch);
while((t = LIST_FIRST(&ch->ch_transports)) != NULL) {
@@ -344,11 +283,13 @@ channel_delete(channel_t *ch)
hts_settings_remove("channels/%s", ch->ch_name);
- free((void *)ch->ch_name);
- free((void *)ch->ch_sname);
+ RB_REMOVE(&channel_name_tree, ch, ch_name_link);
+ RB_REMOVE(&channel_identifier_tree, ch, ch_identifier_link);
+
+ free(ch->ch_name);
+ free(ch->ch_sname);
free(ch->ch_icon);
- RB_REMOVE(&channel_tree, ch, ch_global_link);
free(ch);
}
@@ -363,6 +304,9 @@ void
channel_merge(channel_t *dst, channel_t *src)
{
th_transport_t *t;
+
+ tvhlog(LOG_NOTICE, "channels", "Channel \"%s\" merged into \"%s\"",
+ src->ch_name, dst->ch_name);
while((t = LIST_FIRST(&src->ch_transports)) != NULL) {
transport_unmap_channel(t);
diff --git a/channels.h b/channels.h
index f2106c83..08d514af 100644
--- a/channels.h
+++ b/channels.h
@@ -21,18 +21,12 @@
void channels_load(void);
-channel_t *channel_by_index(uint32_t id);
+channel_t *channel_find_by_name(const char *name, int create);
-channel_t *channel_by_tag(uint32_t tag);
-
-int id_by_channel(channel_t *ch);
-
-int channel_get_channels(void);
+channel_t *channel_find_by_identifier(int id);
void channel_unsubscribe(th_subscription_t *s);
-channel_t *channel_find(const char *name, int create);
-
void channel_set_teletext_rundown(channel_t *ch, int v);
void channel_settings_write(channel_t *ch);
diff --git a/dvb/dvb_transport.c b/dvb/dvb_transport.c
index d9da18a0..04741fdf 100644
--- a/dvb/dvb_transport.c
+++ b/dvb/dvb_transport.c
@@ -156,18 +156,23 @@ dvb_transport_quality(th_transport_t *t)
* Generate a descriptive name for the source
*/
static const char *
-dvb_transport_name(th_transport_t *t)
+dvb_transport_sourcename(th_transport_t *t)
{
- th_dvb_mux_instance_t *tdmi;
- static char buf[200];
+ th_dvb_mux_instance_t *tdmi = t->tht_dvb_mux_instance;
- tdmi = t->tht_dvb_mux_instance;
+ return tdmi->tdmi_adapter->tda_displayname;
+}
- snprintf(buf, sizeof(buf), "\"%s\" on \"%s\"",
- tdmi->tdmi_network ?: "Unknown network",
- tdmi->tdmi_adapter->tda_rootpath);
- return buf;
+/**
+ * Generate a descriptive name for the source
+ */
+static const char *
+dvb_transport_networkname(th_transport_t *t)
+{
+ th_dvb_mux_instance_t *tdmi = t->tht_dvb_mux_instance;
+
+ return tdmi->tdmi_network;
}
@@ -209,7 +214,8 @@ dvb_transport_find(th_dvb_mux_instance_t *tdmi, uint16_t sid, int pmt_pid,
t->tht_start_feed = dvb_start_feed;
t->tht_stop_feed = dvb_stop_feed;
t->tht_config_change = dvb_transport_save;
- t->tht_sourcename = dvb_transport_name;
+ t->tht_sourcename = dvb_transport_sourcename;
+ t->tht_networkname = dvb_transport_networkname;
t->tht_dvb_mux_instance = tdmi;
t->tht_quality_index = dvb_transport_quality;
diff --git a/epg.c b/epg.c
index 9f2f8494..139a2836 100644
--- a/epg.c
+++ b/epg.c
@@ -26,7 +26,6 @@
#include "channels.h"
#include "epg.h"
#include "dispatch.h"
-#include "htsclient.h"
#include "htsp.h"
#include "autorec.h"
@@ -413,7 +412,7 @@ epg_channel_maintain(void *aux, int64_t clk)
now = dispatch_clock;
- RB_FOREACH(ch, &channel_tree, ch_global_link) {
+ RB_FOREACH(ch, &channel_name_tree, ch_name_link) {
/* Age out any old events */
@@ -559,7 +558,7 @@ epg_search(struct event_list *h, const char *title,
regcomp(&preg, title, REG_ICASE | REG_EXTENDED | REG_NOSUB))
return -1;
- RB_FOREACH(ch, &channel_tree, ch_global_link) {
+ RB_FOREACH(ch, &channel_name_tree, ch_name_link) {
if(LIST_FIRST(&ch->ch_transports) == NULL)
continue;
diff --git a/epg_xmltv.c b/epg_xmltv.c
index 2dcb07b4..27aef910 100644
--- a/epg_xmltv.c
+++ b/epg_xmltv.c
@@ -250,7 +250,7 @@ xmltv_resolve_by_events(xmltv_channel_t *xc)
if(ex == NULL)
break;
- RB_FOREACH(ch, &channel_tree, ch_global_link) {
+ RB_FOREACH(ch, &channel_name_tree, ch_name_link) {
ec = epg_event_find_by_time0(&ch->ch_epg_events, now);
cnt = 0;
@@ -289,14 +289,14 @@ xmltv_transfer_events(xmltv_grabber_t *xg)
continue;
if(xc->xc_channel != NULL) {
- ch = channel_find(xc->xc_channel, 0);
+ ch = channel_find_by_name(xc->xc_channel, 0);
if(ch == NULL)
continue;
} else {
how = 0;
- ch = channel_find(xc->xc_displayname, 0);
+ ch = channel_find_by_name(xc->xc_displayname, 0);
if(ch == NULL) {
ch = xmltv_resolve_by_events(xc);
diff --git a/ffmuxer.c b/ffmuxer.c
index ab65ec5c..1bc5a855 100644
--- a/ffmuxer.c
+++ b/ffmuxer.c
@@ -39,7 +39,6 @@
#include "tvhead.h"
#include "channels.h"
#include "subscriptions.h"
-#include "htsclient.h"
#include "ffmuxer.h"
#include "buffer.h"
diff --git a/file_input.c b/file_input.c
index 39f1121f..83a7a67e 100644
--- a/file_input.c
+++ b/file_input.c
@@ -159,7 +159,7 @@ file_input_init(void)
if((s = config_get_str_sub(&ce->ce_sub, "channel", NULL)) == NULL)
continue;
- ch = channel_find(s, 1);
+ ch = channel_find_by_name(s, 1);
t->tht_name = strdup(ch->ch_name);
diff --git a/htsclient.c b/htsclient.c
deleted file mode 100644
index 3f0ebd9a..00000000
--- a/htsclient.c
+++ /dev/null
@@ -1,575 +0,0 @@
-/*
- * tvheadend, (simple) client interface
- * Copyright (C) 2007 Andreas Öman
- *
- * 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
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "tvhead.h"
-#include "channels.h"
-#include "subscriptions.h"
-#include "pvr.h"
-#include "epg.h"
-#include "teletext.h"
-#include "dispatch.h"
-#include "buffer.h"
-#include "tsmux.h"
-#include "tcp.h"
-#include "htsclient.h"
-
-LIST_HEAD(client_list, client);
-
-struct client_list all_clients;
-
-
-/*
- * Client
- */
-
-typedef struct client {
- tcp_session_t c_tcp_session;
-
- LIST_ENTRY(client) c_global_link;
- int c_streamfd;
- pthread_t c_ptid;
-
- LIST_HEAD(, th_subscription) c_subscriptions;
-
- struct in_addr c_ipaddr;
- int c_port;
-
- struct ref_update_queue c_refq;
-
- dtimer_t c_status_timer;
-
- void *c_muxer;
-
-} client_t;
-
-
-
-void client_status_update(void *aux, int64_t now);
-
-#define cprintf(c, fmt...) tcp_printf(&(c)->c_tcp_session, fmt)
-
-
-static void
-client_output_ts(void *opaque, th_subscription_t *s,
- uint8_t *pkt, int blocks, int64_t pcr)
-{
- struct msghdr msg;
- struct iovec vec[2];
- int r;
- client_t *c = opaque;
- struct sockaddr_in sin;
- char hdr[2];
-
-
- memset(&sin, 0, sizeof(sin));
- sin.sin_family = AF_INET;
- sin.sin_port = htons(c->c_port);
- sin.sin_addr = c->c_ipaddr;
-
- hdr[0] = HTSTV_TRANSPORT_STREAM;
- hdr[1] = s->ths_u32;
-
- vec[0].iov_base = hdr;
- vec[0].iov_len = 2;
- vec[1].iov_base = pkt;
- vec[1].iov_len = blocks * 188;
-
- memset(&msg, 0, sizeof(msg));
- msg.msg_name = &sin;
- msg.msg_namelen = sizeof(struct sockaddr_in);
- msg.msg_iov = vec;
- msg.msg_iovlen = 2;
-
- r = sendmsg(c->c_streamfd, &msg, 0);
- if(r < 0)
- perror("sendmsg");
-}
-
-
-/*
- *
- */
-
-static int
-cr_channel_info(client_t *c, char **argv, int argc)
-{
- channel_t *ch;
-
- if(argc < 1)
- return 1;
-
- if((ch = channel_by_index(atoi(argv[0]))) == NULL)
- return 1;
-
- cprintf(c,
- "displayname = %s\n"
- "icon = %s\n"
- "tag = %d\n",
- ch->ch_name,
- ch->ch_icon ? ch->ch_icon : "",
- ch->ch_tag);
-
- return 0;
-}
-
-
-/*
- *
- */
-
-static int
-cr_channel_unsubscribe(client_t *c, char **argv, int argc)
-{
- th_subscription_t *s;
- int chindex;
-
- if(argc < 1)
- return 1;
-
- chindex = atoi(argv[0]);
-
- LIST_FOREACH(s, &c->c_subscriptions, ths_subscriber_link) {
- if(s->ths_u32 == chindex)
- break;
- }
-
- if(s == NULL)
- return 1;
-
- LIST_REMOVE(s, ths_subscriber_link);
- subscription_unsubscribe(s);
- return 0;
-}
-
-
-/*
- * Called when a subscription gets/loses access to a transport
- */
-static void
-client_subscription_callback(struct th_subscription *s,
- subscription_event_t event, void *opaque)
-{
- client_t *c = opaque;
-
- switch(event) {
- case TRANSPORT_AVAILABLE:
- assert(c->c_muxer == NULL);
- c->c_muxer = ts_muxer_init(s, client_output_ts, c,
- TS_HTSCLIENT | TS_SEEK, 0);
- ts_muxer_play(c->c_muxer, 0);
- break;
-
- case TRANSPORT_UNAVAILABLE:
- assert(c->c_muxer != NULL);
- ts_muxer_deinit(c->c_muxer, s);
- c->c_muxer = NULL;
- break;
- }
-}
-
-/*
- *
- */
-static int
-cr_channel_subscribe(client_t *c, char **argv, int argc)
-{
- channel_t *ch;
- th_subscription_t *s;
- unsigned int chindex, weight;
- char tmp[100];
- struct sockaddr_in *si;
-
- if(argc < 1)
- return 1;
-
- chindex = atoi(argv[0]);
-
- weight = argc > 1 ? atoi(argv[1]) : 100;
-
- LIST_FOREACH(s, &c->c_subscriptions, ths_subscriber_link) {
- if(s->ths_u32 == chindex) {
- subscription_set_weight(s, weight);
- return 0;
- }
- }
-
- if((ch = channel_by_index(chindex)) == NULL)
- return 1;
-
- si = (struct sockaddr_in *)&c->c_tcp_session.tcp_peer_addr;
-
- snprintf(tmp, sizeof(tmp), "HTS Client @ %s",
- inet_ntoa(si->sin_addr));
-
- s = subscription_create(ch, weight, tmp, client_subscription_callback, c, 0);
- if(s == NULL)
- return 1;
-
- s->ths_u32 = ch->ch_index;
- LIST_INSERT_HEAD(&c->c_subscriptions, s, ths_subscriber_link);
- return 0;
-}
-
-
-/*
- *
- */
-
-static int
-cr_channels_list(client_t *c, char **argv, int argc)
-{
- channel_t *ch;
-
- RB_FOREACH(ch, &channel_tree, ch_global_link) {
- cprintf(c, "channel = %d\n", ch->ch_index);
- }
- return 0;
-}
-
-
-/*
- *
- */
-
-static int
-cr_streamport(client_t *c, char **argv, int argc)
-{
- if(argc < 2)
- return 1;
-
- if(c->c_streamfd == -1)
- c->c_streamfd = socket(AF_INET, SOCK_DGRAM, 0);
-
- c->c_ipaddr.s_addr = inet_addr(argv[0]);
- c->c_port = atoi(argv[1]);
-
- tvhlog(LOG_INFO, "htsclient",
- "%s registers UDP stream target %s:%d",
- tcp_logname(&c->c_tcp_session), inet_ntoa(c->c_ipaddr), c->c_port);
-
- return 0;
-}
-
-/*
- *
- */
-
-static int
-cr_event_info(client_t *c, char **argv, int argc)
-{
- event_t *e = NULL, *x;
- uint32_t tag, prev, next;
- channel_t *ch;
- pvr_rec_t *pvrr;
-
- if(argc < 2)
- return 1;
-
- if(!strcasecmp(argv[0], "tag"))
- e = epg_event_find_by_tag(atoi(argv[1]));
- if(!strcasecmp(argv[0], "now"))
- if((ch = channel_by_index(atoi(argv[1]))) != NULL)
- e = epg_event_get_current(ch);
- if(!strcasecmp(argv[0], "at") && argc == 3)
- if((ch = channel_by_index(atoi(argv[1]))) != NULL)
- e = epg_event_find_by_time(ch, atoi(argv[2]));
-
- if(e == NULL) {
- return 1;
- }
-
- tag = e->e_tag;
- x = TAILQ_PREV(e, event_queue, e_channel_link);
- prev = x != NULL ? x->e_tag : 0;
-
- x = TAILQ_NEXT(e, e_channel_link);
- next = x != NULL ? x->e_tag : 0;
-
- pvrr = pvr_get_by_entry(e);
-
- cprintf(c,
- "start = %ld\n"
- "stop = %ld\n"
- "title = %s\n"
- "desc = %s\n"
- "tag = %u\n"
- "prev = %u\n"
- "next = %u\n"
- "pvrstatus = %d\n",
-
- e->e_start,
- e->e_start + e->e_duration,
- e->e_title ?: "",
- e->e_desc ?: "",
- tag,
- prev,
- next,
- pvrr != NULL ? pvrr->pvrr_status : HTSTV_PVR_STATUS_NONE);
-
- return 0;
-}
-
-/*
- *
- */
-
-static int
-cr_event_record(client_t *c, char **argv, int argc)
-{
- event_t *e;
-
- if(argc < 1)
- return 1;
-
- e = epg_event_find_by_tag(atoi(argv[0]));
- if(e == NULL) {
- return 1;
- }
-
- pvr_schedule_by_event(e, "htsclient");
-
- return 0;
-}
-
-
-
-
-/*
- *
- */
-static int
-cr_channel_record(client_t *c, char **argv, int argc)
-{
- channel_t *ch;
- int duration;
-
- if(argc < 2)
- return 1;
-
- if((ch = channel_by_index(atoi(argv[0]))) == NULL)
- return 1;
-
- duration = atoi(argv[1]);
-
- pvr_schedule_by_channel_and_time(ch, duration, "htsclient");
- return 0;
-}
-
-/*
- *
- */
-static int
-cr_pvr_entry(client_t *c, pvr_rec_t *pvrr)
-{
- event_t *e;
-
- if(pvrr == NULL)
- return 1;
-
- cprintf(c,
- "title = %s\n"
- "start = %ld\n"
- "stop = %ld\n"
- "desc = %s\n"
- "pvr_tag = %d\n"
- "pvrstatus = %d\n"
- "filename = %s\n"
- "channel = %d\n",
- pvrr->pvrr_title ?: "",
- pvrr->pvrr_start,
- pvrr->pvrr_stop,
- pvrr->pvrr_desc ?: "",
- pvrr->pvrr_ref,
- pvrr->pvrr_status,
- pvrr->pvrr_filename,
- pvrr->pvrr_channel->ch_index);
-
-
- e = epg_event_find_by_time(pvrr->pvrr_channel, pvrr->pvrr_start);
- if(e != NULL)
- cprintf(c, "event_tag = %d\n", e->e_tag);
-
- return 0;
-}
-
-/*
- *
- */
-
-static int
-cr_pvr_getlog(client_t *c, char **argv, int argc)
-{
- pvr_rec_t *pvrr;
-
- if(argc < 1)
- return 1;
-
- pvrr = pvr_get_log_entry(atoi(argv[0]));
- return cr_pvr_entry(c, pvrr);
-}
-
-
-/*
- *
- */
-
-static int
-cr_pvr_gettag(client_t *c, char **argv, int argc)
-{
- pvr_rec_t *pvrr;
-
- if(argc < 1)
- return 1;
-
- pvrr = pvr_get_tag_entry(atoi(argv[0]));
- return cr_pvr_entry(c, pvrr);
-}
-
-
-/*
- *
- */
-
-const struct {
- const char *name;
- int (*func)(client_t *c, char *argv[], int argc);
-} cr_cmds[] = {
- { "streamport", cr_streamport },
- { "channels.list", cr_channels_list },
- { "channel.info", cr_channel_info },
- { "channel.subscribe", cr_channel_subscribe },
- { "channel.unsubscribe", cr_channel_unsubscribe },
- { "channel.record", cr_channel_record },
- { "event.info", cr_event_info },
- { "event.record", cr_event_record },
- { "pvr.getlog", cr_pvr_getlog },
- { "pvr.gettag", cr_pvr_gettag },
-};
-
-
-static int
-client_req(void *aux, char *buf)
-{
- client_t *c = aux;
- int i, l, x;
- const char *n;
- char *argv[40];
- int argc = 0;
-
- for(i = 0; i < sizeof(cr_cmds) / sizeof(cr_cmds[0]); i++) {
- n = cr_cmds[i].name;
- l = strlen(n);
- if(!strncasecmp(buf, n, l) && (buf[l] == ' ' || buf[l] == 0)) {
- buf += l;
-
- while(*buf) {
- if(*buf < 33) {
- buf++;
- continue;
- }
- argv[argc++] = buf;
- while(*buf > 32)
- buf++;
- if(*buf == 0)
- break;
- *buf++ = 0;
- }
- x = cr_cmds[i].func(c, argv, argc);
-
- if(x >= 0)
- cprintf(c, "eom %s\n", x ? "error" : "ok");
-
- return 0;
- }
- }
- cprintf(c, "eom nocommand\n");
- return 0;
-}
-
-/*
- * client disconnect
- */
-static void
-client_disconnect(client_t *c)
-{
- th_subscription_t *s;
-
- dtimer_disarm(&c->c_status_timer);
-
- if(c->c_streamfd != -1)
- close(c->c_streamfd);
-
- LIST_REMOVE(c, c_global_link);
-
- while((s = LIST_FIRST(&c->c_subscriptions)) != NULL) {
- LIST_REMOVE(s, ths_subscriber_link);
- subscription_unsubscribe(s);
- }
-}
-
-
-/*
- *
- */
-static void
-htsclient_tcp_callback(tcpevent_t event, void *tcpsession)
-{
- client_t *c = tcpsession;
-
- switch(event) {
- case TCP_CONNECT:
- TAILQ_INIT(&c->c_refq);
- LIST_INSERT_HEAD(&all_clients, c, c_global_link);
- c->c_streamfd = -1;
- break;
-
- case TCP_DISCONNECT:
- client_disconnect(c);
- break;
-
- case TCP_INPUT:
- tcp_line_read(&c->c_tcp_session, client_req);
- break;
- }
-}
-
-
-/*
- * Fire up client handling
- */
-
-void
-client_start(void)
-{
- tcp_create_server(9909, sizeof(client_t), "htsclient",
- htsclient_tcp_callback);
-}
-
diff --git a/htsclient.h b/htsclient.h
deleted file mode 100644
index 6d5ed224..00000000
--- a/htsclient.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * tvheadend, (simple) client interface
- * Copyright (C) 2007 Andreas Öman
- *
- * 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 HTSCLIENT_H_
-#define HTSCLIENT_H_
-
-void client_start(void);
-
-void clients_send_ref(int ref);
-
-#endif /* HTSCLIENT_H_ */
diff --git a/htsp.c b/htsp.c
index 76c3a2e2..495cd00f 100644
--- a/htsp.c
+++ b/htsp.c
@@ -80,7 +80,7 @@ htsp_build_channel_msg(channel_t *ch, const char *method)
htsmsg_add_str(msg, "method", method);
htsmsg_add_str(msg, "channelName", ch->ch_name);
- htsmsg_add_u32(msg, "channelTag", ch->ch_tag);
+ htsmsg_add_u32(msg, "channelId", ch->ch_id);
if(ch->ch_icon != NULL)
htsmsg_add_str(msg, "channelIcon", ch->ch_icon);
@@ -102,7 +102,7 @@ htsp_send_all_channels(htsp_t *htsp)
htsmsg_t *msg;
channel_t *ch;
- RB_FOREACH(ch, &channel_tree, ch_global_link) {
+ RB_FOREACH(ch, &channel_name_tree, ch_name_link) {
if(LIST_FIRST(&ch->ch_transports) == NULL)
continue;
@@ -140,13 +140,12 @@ htsp_subscribe(rpc_session_t *ses, htsmsg_t *in, void *opaque)
channel_t *ch;
th_subscription_t *s;
htsmsg_t *r;
+ uint32_t u32;
- uint32_t tag;
+ if(htsmsg_get_u32(in, "channelId", &u32))
+ return rpc_error(ses, "missing argument: channelId");
- if(htsmsg_get_u32(in, "channelTag", &tag))
- return rpc_error(ses, "missing argument: channelTag");
-
- if((ch = channel_by_tag(tag)) == NULL)
+ if((ch = channel_find_by_identifier(u32)) == NULL)
return rpc_error(ses, "Channel not found");
LIST_FOREACH(s, &htsp->htsp_subscriptions, ths_subscriber_link) {
diff --git a/htsp_muxer.c b/htsp_muxer.c
index 8a2c801e..a8de6371 100644
--- a/htsp_muxer.c
+++ b/htsp_muxer.c
@@ -51,7 +51,7 @@ htsp_packet_input(void *opaque, th_muxstream_t *tms, th_pkt_t *pkt)
*/
htsmsg_add_str(m, "method", "muxpkt");
- htsmsg_add_u32(m, "channelTag", s->ths_channel->ch_tag);
+ htsmsg_add_u32(m, "channelId", s->ths_channel->ch_id);
htsmsg_add_u64(m, "stream", tms->tms_index);
htsmsg_add_u64(m, "dts", pkt->pkt_dts);
@@ -89,7 +89,7 @@ htsp_subscription_callback(struct th_subscription *s,
m = htsmsg_create();
htsmsg_add_str(m, "method", "subscription_start");
- htsmsg_add_u32(m, "channelTag", s->ths_channel->ch_tag);
+ htsmsg_add_u32(m, "channelId", s->ths_channel->ch_id);
LIST_FOREACH(tms, &tm->tm_streams, tms_muxer_link0) {
tms->tms_index = index++;
@@ -113,7 +113,7 @@ htsp_subscription_callback(struct th_subscription *s,
if(htsp->htsp_zombie == 0) {
m = htsmsg_create();
htsmsg_add_str(m, "method", "subscription_stop");
- htsmsg_add_u32(m, "channelTag", s->ths_channel->ch_tag);
+ htsmsg_add_u32(m, "channelId", s->ths_channel->ch_id);
htsmsg_add_str(m, "reason", "unknown");
htsp_send_msg(htsp, m, 0);
@@ -159,7 +159,7 @@ htsp_muxer_unsubscribe(htsp_t *htsp, uint32_t id)
th_subscription_t *s;
LIST_FOREACH(s, &htsp->htsp_subscriptions, ths_subscriber_link) {
- if(s->ths_channel->ch_tag == id)
+ if(s->ths_channel->ch_id == id)
break;
}
diff --git a/iptv_output.c b/iptv_output.c
index 39b8d80e..c55c662c 100644
--- a/iptv_output.c
+++ b/iptv_output.c
@@ -133,7 +133,7 @@ output_multicast_load(struct config_head *head)
if((name = config_get_str_sub(head, "channel", NULL)) == NULL)
return;
- ch = channel_find(name, 1);
+ ch = channel_find_by_name(name, 1);
om = calloc(1, sizeof(output_multicast_t));
diff --git a/main.c b/main.c
index 3cf2433b..2378b150 100644
--- a/main.c
+++ b/main.c
@@ -40,7 +40,6 @@
#include "dvb/dvb.h"
#include "v4l.h"
#include "channels.h"
-#include "htsclient.h"
#include "epg.h"
#include "epg_xmltv.h"
#include "pvr.h"
@@ -309,7 +308,6 @@ main(int argc, char **argv)
pvr_init();
output_multicast_setup();
- client_start();
p = atoi(config_get_str("http-server-port", "9981"));
if(p)
diff --git a/psi.c b/psi.c
index 790ae90c..c991428a 100644
--- a/psi.c
+++ b/psi.c
@@ -618,7 +618,7 @@ psi_load_transport_settings(htsmsg_t *m, th_transport_t *t)
st = transport_add_stream(t, pid, type);
st->st_tb = (AVRational){1, 90000};
- if((v = htsmsg_get_str(c, "lang")) != NULL)
+ if((v = htsmsg_get_str(c, "language")) != NULL)
av_strlcpy(st->st_lang, v, 4);
if(!htsmsg_get_u32(c, "frameduration", &u32))
diff --git a/pvr.c b/pvr.c
index 4f76864a..6dbcdedc 100644
--- a/pvr.c
+++ b/pvr.c
@@ -37,7 +37,6 @@
#include "tvhead.h"
#include "channels.h"
#include "subscriptions.h"
-#include "htsclient.h"
#include "pvr.h"
#include "epg.h"
#include "dispatch.h"
@@ -438,7 +437,7 @@ pvr_database_load(void)
if(channel != NULL && start && stop && title && status) {
pvrr = calloc(1, sizeof(pvr_rec_t));
- pvrr->pvrr_channel = channel_find(channel, 1);
+ pvrr->pvrr_channel = channel_find_by_name(channel, 1);
pvrr->pvrr_start = start;
pvrr->pvrr_stop = stop;
pvrr->pvrr_status = *status;
diff --git a/rpc.c b/rpc.c
index 3b592d0d..e60d1b3e 100644
--- a/rpc.c
+++ b/rpc.c
@@ -51,7 +51,7 @@ rpc_build_channel_msg(channel_t *ch)
m = htsmsg_create();
htsmsg_add_str(m, "name", ch->ch_name);
- htsmsg_add_u32(m, "tag", ch->ch_tag);
+ htsmsg_add_u32(m, "id", ch->ch_id);
if(ch->ch_icon)
htsmsg_add_str(m, "icon", ch->ch_icon);
return m;
@@ -148,7 +148,7 @@ rpc_channels_list(rpc_session_t *ses, htsmsg_t *in, void *opaque)
out = htsmsg_create();
htsmsg_add_u32(out, "seq", ses->rs_seq);
- RB_FOREACH(ch, &channel_tree, ch_global_link)
+ RB_FOREACH(ch, &channel_name_tree, ch_name_link)
htsmsg_add_msg(out, "channel", rpc_build_channel_msg(ch));
return out;
@@ -174,7 +174,7 @@ rpc_event_info(rpc_session_t *ses, htsmsg_t *in, void *opaque)
if(htsmsg_get_u32(in, "tag", &u32) >= 0) {
e = epg_event_find_by_tag(u32);
} else if((s = htsmsg_get_str(in, "channel")) != NULL) {
- if((ch = channel_find(s, 0)) == NULL) {
+ if((ch = channel_find_by_name(s, 0)) == NULL) {
errtxt = "Channel not found";
} else {
if(htsmsg_get_u32(in, "time", &u32) < 0) {
diff --git a/rtsp.c b/rtsp.c
index cfce733b..961f8055 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -104,7 +104,7 @@ rtsp_channel_by_url(char *url)
return NULL;
c++;
- RB_FOREACH(ch, &channel_tree, ch_global_link)
+ RB_FOREACH(ch, &channel_name_tree, ch_name_link)
if(!strcasecmp(ch->ch_sname, c))
return ch;
diff --git a/serviceprobe.c b/serviceprobe.c
index abec5f55..ff86e0b7 100644
--- a/serviceprobe.c
+++ b/serviceprobe.c
@@ -93,7 +93,7 @@ sp_packet_input(void *opaque, th_muxstream_t *tms, th_pkt_t *pkt)
tvhlog(LOG_INFO, "serviceprobe", "Probed \"%s\" -- Ok", t->tht_svcname);
if(t->tht_ch == NULL && t->tht_svcname != NULL) {
- ch = channel_find(t->tht_svcname, 1);
+ ch = channel_find_by_name(t->tht_svcname, 1);
transport_map_channel(t, ch);
t->tht_config_change(t);
diff --git a/transports.c b/transports.c
index 8cf75bf8..af2f60d5 100644
--- a/transports.c
+++ b/transports.c
@@ -558,7 +558,7 @@ transport_map_channel(th_transport_t *t, channel_t *ch)
if(ch == NULL) {
if(t->tht_chname == NULL)
return;
- ch = channel_find(t->tht_chname, 1);
+ ch = channel_find_by_name(t->tht_chname, 1);
} else {
free(t->tht_chname);
t->tht_chname = strdup(ch->ch_name);
diff --git a/tvhead.h b/tvhead.h
index b3eb0a37..f1fe7e4f 100644
--- a/tvhead.h
+++ b/tvhead.h
@@ -95,7 +95,7 @@ LIST_HEAD(autorec_list, autorec);
extern time_t dispatch_clock;
extern int startupcounter;
extern struct th_transport_list all_transports;
-extern struct channel_tree channel_tree;
+extern struct channel_tree channel_name_tree;
extern struct pvr_rec_list pvrr_global_list;
extern struct th_subscription_list subscriptions;
@@ -439,6 +439,8 @@ typedef struct th_transport {
void (*tht_config_change)(struct th_transport *t);
+ const char *(*tht_networkname)(struct th_transport *t);
+
const char *(*tht_sourcename)(struct th_transport *t);
int (*tht_quality_index)(struct th_transport *t);
@@ -778,22 +780,19 @@ typedef struct tt_decoder {
*/
typedef struct channel {
- RB_ENTRY(channel) ch_global_link;
+ RB_ENTRY(channel) ch_name_link;
+ char *ch_name;
+ char *ch_sname;
+ RB_ENTRY(channel) ch_identifier_link;
+ int ch_id;
LIST_HEAD(, th_transport) ch_transports;
LIST_HEAD(, th_subscription) ch_subscriptions;
- int ch_index;
-
- const char *ch_name;
- const char *ch_sname;
-
struct tt_decoder ch_tt;
- int ch_tag;
-
enum {
COMMERCIAL_DETECT_NONE,
COMMERCIAL_DETECT_TTP192,
diff --git a/webui/extjs.c b/webui/extjs.c
index 0b6f1a04..a86ccb30 100644
--- a/webui/extjs.c
+++ b/webui/extjs.c
@@ -32,6 +32,8 @@
#include "webui.h"
#include "access.h"
#include "dtable.h"
+#include "channels.h"
+#include "psi.h"
#include "dvb/dvb.h"
#include "dvb/dvb_support.h"
@@ -105,6 +107,7 @@ extjs_root(http_connection_t *hc, http_reply_t *hr,
extjs_load(hq, "static/app/acleditor.js");
extjs_load(hq, "static/app/cwceditor.js");
extjs_load(hq, "static/app/dvb.js");
+ extjs_load(hq, "static/app/chconf.js");
/**
* Finally, the app itself
@@ -199,6 +202,38 @@ extjs_tablemgr(http_connection_t *hc, http_reply_t *hr,
return 0;
}
+
+/**
+ *
+ */
+static int
+extjs_chlist(http_connection_t *hc, http_reply_t *hr,
+ const char *remain, void *opaque)
+{
+ htsbuf_queue_t *hq = &hr->hr_q;
+ htsmsg_t *out, *array, *c;
+ channel_t *ch;
+
+ out = htsmsg_create();
+
+ array = htsmsg_create_array();
+
+ RB_FOREACH(ch, &channel_name_tree, ch_name_link) {
+ c = htsmsg_create();
+ htsmsg_add_str(c, "name", ch->ch_name);
+ htsmsg_add_u32(c, "chid", ch->ch_id);
+ htsmsg_add_msg(array, NULL, c);
+ }
+
+ htsmsg_add_msg(out, "entries", array);
+
+ htsmsg_json_serialize(out, hq, 0);
+ htsmsg_destroy(out);
+ http_output(hc, hr, "text/x-json; charset=UTF-8", NULL, 0);
+ return 0;
+}
+
+
/**
*
*/
@@ -328,7 +363,7 @@ json_single_record(htsmsg_t *rec, const char *root)
array = htsmsg_create_array();
htsmsg_add_msg(array, NULL, rec);
- htsmsg_add_msg(out, "dvbadapters", array);
+ htsmsg_add_msg(out, root, array);
return out;
}
@@ -399,6 +434,188 @@ extjs_dvbadapter(http_connection_t *hc, http_reply_t *hr,
return 0;
}
+/**
+ *
+ */
+static htsmsg_t *
+build_transport_msg(th_transport_t *t)
+{
+ htsmsg_t *r = htsmsg_create();
+ th_stream_t *st;
+
+ char video[200];
+ char audio[200];
+ char subtitles[200];
+ char scrambling[200];
+
+ htsmsg_add_u32(r, "enabled", !t->tht_disabled);
+ htsmsg_add_str(r, "name", t->tht_svcname);
+
+ htsmsg_add_str(r, "provider", t->tht_provider ?: "");
+ htsmsg_add_str(r, "network", t->tht_networkname(t));
+ htsmsg_add_str(r, "source", t->tht_sourcename(t));
+
+ htsmsg_add_str(r, "status", transport_status_to_text(t->tht_last_status));
+
+ video[0] = 0;
+ audio[0] = 0;
+ subtitles[0] = 0;
+ scrambling[0] = 0;
+
+ LIST_FOREACH(st, &t->tht_streams, st_link) {
+ switch(st->st_type) {
+ case HTSTV_TELETEXT:
+ case HTSTV_SUBTITLES:
+ case HTSTV_PAT:
+ case HTSTV_PMT:
+ break;
+
+ case HTSTV_MPEG2VIDEO:
+ snprintf(video + strlen(video), sizeof(video) - strlen(video),
+ "%sMPEG-2 (PID:%d", strlen(video) > 0 ? ", " : "",
+ st->st_pid);
+
+ video:
+ if(st->st_frame_duration) {
+ snprintf(video + strlen(video), sizeof(video) - strlen(video),
+ ", %d Hz)", 90000 / st->st_frame_duration);
+ } else {
+ snprintf(video + strlen(video), sizeof(video) - strlen(video),
+ ")");
+ }
+
+ break;
+
+ case HTSTV_H264:
+ snprintf(video + strlen(video), sizeof(video) - strlen(video),
+ "%sH.264 (PID:%d", strlen(video) > 0 ? ", " : "",
+ st->st_pid);
+ goto video;
+
+ case HTSTV_MPEG2AUDIO:
+ snprintf(audio + strlen(audio), sizeof(audio) - strlen(audio),
+ "%sMPEG-2 (PID:%d", strlen(audio) > 0 ? ", " : "",
+ st->st_pid);
+ audio:
+ if(st->st_lang[0]) {
+ snprintf(audio + strlen(audio), sizeof(audio) - strlen(audio),
+ ", languange: \"%s\")", st->st_lang);
+ } else {
+ snprintf(audio + strlen(audio), sizeof(audio) - strlen(audio),
+ ")");
+ }
+ break;
+
+ case HTSTV_AC3:
+ snprintf(audio + strlen(audio), sizeof(audio) - strlen(audio),
+ "%sAC3 (PID:%d", strlen(audio) > 0 ? ", " : "",
+ st->st_pid);
+ goto audio;
+
+ case HTSTV_CA:
+ snprintf(scrambling + strlen(scrambling),
+ sizeof(scrambling) - strlen(scrambling),
+ "%s%s", strlen(scrambling) > 0 ? ", " : "",
+ psi_caid2name(st->st_caid));
+ break;
+ }
+ }
+
+ htsmsg_add_str(r, "video", video);
+ htsmsg_add_str(r, "audio", audio);
+ htsmsg_add_str(r, "scrambling", scrambling[0] ? scrambling : "none");
+ return r;
+}
+
+
+/**
+ *
+ */
+static int
+extjs_channel(http_connection_t *hc, http_reply_t *hr,
+ const char *remain, void *opaque)
+{
+ htsbuf_queue_t *hq = &hr->hr_q;
+ const char *s = http_arg_get(&hc->hc_req_args, "chid");
+ const char *op = http_arg_get(&hc->hc_req_args, "op");
+ channel_t *ch = s ? channel_find_by_identifier(atoi(s)) : NULL;
+ channel_t *ch2;
+ th_transport_t *t;
+ int reloadchlist = 0;
+ htsmsg_t *out, *array, *r;
+
+ if(ch == NULL)
+ return HTTP_STATUS_BAD_REQUEST;
+
+ if(!strcmp(op, "load")) {
+ r = htsmsg_create();
+ htsmsg_add_u32(r, "id", ch->ch_id);
+ htsmsg_add_str(r, "name", ch->ch_name);
+ htsmsg_add_str(r, "comdetect", "tt192");
+ out = json_single_record(r, "channels");
+
+ } else if(!strcmp(op, "gettransports")) {
+ out = htsmsg_create();
+ array = htsmsg_create_array();
+ LIST_FOREACH(t, &ch->ch_transports, tht_ch_link)
+ htsmsg_add_msg(array, NULL, build_transport_msg(t));
+
+ htsmsg_add_msg(out, "entries", array);
+
+ } else if(!strcmp(op, "delete")) {
+
+ channel_delete(ch);
+
+ out = htsmsg_create();
+ htsmsg_add_u32(out, "reloadchlist", 1);
+ htsmsg_add_u32(out, "success", 1);
+
+ } else if(!strcmp(op, "mergefrom")) {
+
+ if((s = http_arg_get(&hc->hc_req_args, "srcch")) == NULL)
+ return HTTP_STATUS_BAD_REQUEST;
+
+ ch2 = channel_find_by_identifier(atoi(s));
+ if(ch2 == NULL || ch2 == ch)
+ return HTTP_STATUS_BAD_REQUEST;
+
+ channel_merge(ch, ch2); /* ch2 goes away here */
+
+ out = htsmsg_create();
+ htsmsg_add_u32(out, "reloadchlist", 1);
+ htsmsg_add_u32(out, "success", 1);
+
+
+ } else if(!strcmp(op, "save")) {
+
+ if((s = http_arg_get(&hc->hc_req_args, "name")) != NULL &&
+ strcmp(s, ch->ch_name)) {
+
+ if(channel_rename(ch, s)) {
+ out = htsmsg_create();
+ htsmsg_add_u32(out, "success", 0);
+ htsmsg_add_str(out, "errormsg", "Channel name already exist");
+ goto response;
+ } else {
+ reloadchlist = 1;
+ }
+ }
+
+ out = htsmsg_create();
+ htsmsg_add_u32(out, "reloadchlist", 1);
+ htsmsg_add_u32(out, "success", 1);
+
+ } else {
+ return HTTP_STATUS_BAD_REQUEST;
+ }
+ response:
+ htsmsg_json_serialize(out, hq, 0);
+ htsmsg_destroy(out);
+
+ http_output(hc, hr, "text/x-json; charset=UTF-8", NULL, 0);
+ return 0;
+}
+
/**
* WEB user interface
*/
@@ -410,4 +627,6 @@ extjs_start(void)
http_path_add("/dvbtree", NULL, extjs_dvbtree, ACCESS_WEB_INTERFACE);
http_path_add("/dvbadapter", NULL, extjs_dvbadapter, ACCESS_WEB_INTERFACE);
http_path_add("/dvbnetworks", NULL, extjs_dvbnetworks, ACCESS_WEB_INTERFACE);
+ http_path_add("/chlist", NULL, extjs_chlist, ACCESS_WEB_INTERFACE);
+ http_path_add("/channel", NULL, extjs_channel, ACCESS_WEB_INTERFACE);
}
diff --git a/webui/static/app/chconf.js b/webui/static/app/chconf.js
new file mode 100644
index 00000000..03d4d279
--- /dev/null
+++ b/webui/static/app/chconf.js
@@ -0,0 +1,361 @@
+/**
+ * Channel details
+ */
+tvheadend.channeldetails = function(chid, chname) {
+ var fm = Ext.form;
+ var xg = Ext.grid;
+
+ var expander = new xg.RowExpander({
+ tpl : new Ext.Template(
+ 'Video:{video}
',
+ 'Audio:{audio}
',
+ 'Subtitling:{subtitles}
',
+ 'Scrambling:{scrambling}
'
+ )
+ });
+
+ var enabledColumn = new Ext.grid.CheckColumn({
+ header: "Enabled",
+ dataIndex: 'enabled',
+ width: 60
+ });
+
+ var cm = new Ext.grid.ColumnModel([expander,
+ enabledColumn,
+ {
+ width: 125,
+ id:'name',
+ header: "Original name",
+ dataIndex: 'name',
+ },{
+ width: 125,
+ id:'status',
+ header: "Last status",
+ dataIndex: 'status',
+ },{
+ width: 125,
+ id:'provider',
+ header: "Provider",
+ dataIndex: 'provider',
+ },{
+ width: 125,
+ id:'network',
+ header: "Network",
+ dataIndex: 'network',
+ },{
+ width: 250,
+ id:'source',
+ header: "Source",
+ dataIndex: 'source',
+ }
+ ]);
+
+
+ var transportRecord = Ext.data.Record.create([
+ {name: 'enabled'},
+ {name: 'status'},
+ {name: 'name'},
+ {name: 'provider'},
+ {name: 'network'},
+ {name: 'source'},
+ {name: 'video'},
+ {name: 'audio'},
+ {name: 'scrambling'},
+ {name: 'subtitles'}
+ ]);
+
+ var transportsstore =
+ new Ext.data.JsonStore({root: 'entries',
+ fields: transportRecord,
+ url: "channel",
+ autoLoad: true,
+ id: 'id',
+ storeid: 'id',
+ baseParams: {chid: chid, op: "gettransports"}
+ });
+
+
+ var transportsgrid = new Ext.grid.EditorGridPanel({
+ title:'Transports',
+ anchor: '100% 50%',
+ plugins:[enabledColumn, expander],
+ store: transportsstore,
+ clicksToEdit: 2,
+ cm: cm,
+ selModel: new Ext.grid.RowSelectionModel({singleSelect:false})
+ });
+
+
+ var confreader = new Ext.data.JsonReader({
+ root: 'channels',
+ }, ['name', 'comdetect']);
+
+ var confpanel = new Ext.FormPanel({
+ frame:true,
+ disabled:true,
+ anchor: '100% 50%',
+ labelAlign: 'right',
+ labelWidth: 150,
+ waitMsgTarget: true,
+ reader: confreader,
+ // defaultType: 'textfield',
+
+ items: [{
+ layout:'column',
+ items:[{
+ columnWidth:.5,
+ layout: 'form',
+ defaultType: 'textfield',
+ items: [{
+
+ fieldLabel: 'Channel name',
+ name: 'name',
+ }
+ /*
+ ,
+ new Ext.form.ComboBox({
+ allowBlank: false,
+ fieldLabel: 'Commercial detection',
+ name: 'comdetect',
+ displayField:'mode',
+ valueField:'imode',
+ mode: 'local',
+ triggerAction: 'all',
+ selectOnFocus:true,
+ editable:false,
+ store: new Ext.data.SimpleStore({
+ fields: ['imode', 'mode'],
+ data: [
+ ['none', 'None'],
+ ['tt192', 'Teletext page 192']]
+ })
+ })
+ */
+ ]
+ }
+ /*
+ ,{
+ columnWidth:.5,
+ layout: 'form',
+ items: [{
+ xtype: 'checkboxgroup',
+ fieldLabel: 'Tags',
+ itemCls: 'x-check-group-alt',
+ columns: 1,
+ vertical: true,
+ items: [{
+ boxLabel: 'Favourites', name: 'favourite'},{
+ boxLabel: 'Sports', name: 'sports'},{
+ boxLabel: 'News', name: 'news'},{
+ boxLabel: 'Movies', name: 'movies'},{
+ boxLabel: 'Children', name: 'children'}
+ ]
+ }
+ ]
+ } */
+ ]
+ }]
+ });
+
+ confpanel.getForm().load({url:'/channel',
+ params:{'chid': chid, 'op':'load'},
+ success:function(form, action) {
+ confpanel.enable();
+ }});
+
+
+ function saveChanges() {
+ confpanel.getForm().submit({url:'/channel',
+ params:{'chid': chid, 'op':'save'},
+ waitMsg:'Saving Data...',
+
+ success: function(form, action) {
+
+ if(action.result.reloadchlist) {
+ tvheadend.chconfliststore.reload();
+ }
+ },
+
+ failure: function(form, action) {
+ Ext.Msg.alert('Save failed', action.result.errormsg);
+ }
+ });
+ }
+
+ function deleteChannel() {
+ Ext.MessageBox.confirm('Message',
+ 'Do you really want to delete "' + chname + '"',
+ function(button) {
+ if(button == 'no')
+ return;
+ Ext.Ajax.request({url: '/channel',
+ params:{'chid': chid, 'op':'delete'},
+ success: function() {
+ tvheadend.chconfliststore.reload();
+ panel.destroy();
+ }
+ });
+ }
+ );
+ }
+
+ var panel = new Ext.Panel({
+ title: chname,
+ border:false,
+ tbar: [{
+ text: "Save changes",
+ handler: saveChanges
+ },{
+ text: "Delete channel",
+ handler: deleteChannel
+ }],
+ defaults: {
+ border:false,
+ },
+ layout:'anchor',
+ items: [confpanel,transportsgrid]
+ });
+
+
+ panel.on('afterlayout', function(parent, n) {
+ var DropTargetEl = parent.body.dom;
+
+ var DropTarget = new Ext.dd.DropTarget(DropTargetEl, {
+ ddGroup : 'chconfddgroup',
+ notifyEnter : function(ddSource, e, data) {
+
+ //Add some flare to invite drop.
+ parent.body.stopFx();
+ parent.body.highlight();
+ },
+ notifyDrop : function(ddSource, e, data){
+
+ // Reference the record (single selection) for readability
+ var selectedRecord = ddSource.dragData.selections[0];
+
+ Ext.MessageBox.confirm('Merge channels',
+ 'Copy transport configuration from "' + selectedRecord.data.name +
+ '" to "' + chname + '". This will also remove the channel "' +
+ selectedRecord.data.name + '"',
+ function(button) {
+ if(button == 'no')
+ return;
+ Ext.Ajax.request({url: '/channel',
+ params:{chid: chid,
+ op:'mergefrom',
+ srcch: selectedRecord.data.chid},
+ success: function() {
+ transportsstore.reload();
+ tvheadend.chconfliststore.reload();
+ }});
+ }
+ );
+ }
+ });
+ });
+ return panel;
+ }
+
+
+/**
+ *
+ */
+tvheadend.chconf = function() {
+
+ var ChannelRecord = Ext.data.Record.create([
+ {name: 'name'},
+ {name: 'chid'}]);
+
+ var store = new Ext.data.JsonStore({root: 'entries',
+ fields: ChannelRecord,
+ url: "chlist",
+ autoLoad: true,
+ id: 'id',
+ storeid: 'id'
+ });
+
+
+ var chlist = new Ext.grid.GridPanel({
+ ddGroup: 'chconfddgroup',
+ enableDragDrop: true,
+ stripeRows:true,
+ region:'west',
+ width: 300,
+ columns: [{id:'name',
+ header: "Channel name",
+ width: 260,
+ dataIndex: 'name'}
+ ],
+ selModel: new Ext.grid.RowSelectionModel({singleSelect:true}),
+ store: store,
+ });
+
+ var details = new Ext.Panel({
+ region:'center', layout:'fit',
+ items:[{border: false}]
+ });
+
+
+ var panel = new Ext.Panel({
+ listeners: {activate: handleActivate},
+ border: false,
+ title:'Channels',
+ layout:'border',
+ items: [chlist, details]
+ });
+
+ function handleActivate(tab){
+ store.reload();
+ }
+
+ chlist.on('rowclick', function(grid, n) {
+ var rec = store.getAt(n);
+
+ details.remove(details.getComponent(0));
+ details.doLayout();
+
+ var newpanel = new tvheadend.channeldetails(rec.data.chid,
+ rec.data.name);
+
+ details.add(newpanel);
+ details.doLayout();
+ });
+
+
+ /**
+ * Setup Drop Targets
+ */
+
+ // This will make sure we only drop to the view container
+
+ /*
+ var DropTargetEl = details.getView();
+
+ var DropTarget = new Ext.dd.DropTarget(DropTargetEl, {
+ ddGroup : 'chconfddgroup',
+ notifyEnter : function(ddSource, e, data) {
+
+ //Add some flare to invite drop.
+ panel.body.stopFx();
+ panel.body.highlight();
+ },
+ notifyDrop : function(ddSource, e, data){
+
+ // Reference the record (single selection) for readability
+ var selectedRecord = ddSource.dragData.selections[0];
+
+ console.log(selectedRecord);
+ }
+ });
+
+ */
+ /*
+ details.on('afterlayout', function(parent, n) {
+ console.log(parent);
+ });
+ */
+
+ tvheadend.chconfliststore = store;
+
+ return panel;
+}
\ No newline at end of file
diff --git a/webui/static/app/dvb.js b/webui/static/app/dvb.js
index 6ffcd529..7ec80527 100644
--- a/webui/static/app/dvb.js
+++ b/webui/static/app/dvb.js
@@ -167,6 +167,7 @@ tvheadend.dvb_adapterdetails = function(adapterId, adapterName, treenode) {
border: false,
items:[status, confpanel],
tbar:[{
+ iconCls:'add',
text: 'Add mux(es)',
handler: addmux
},{
diff --git a/webui/static/app/extensions.js b/webui/static/app/extensions.js
index 52beba69..4baf3ae3 100644
--- a/webui/static/app/extensions.js
+++ b/webui/static/app/extensions.js
@@ -143,3 +143,135 @@ Ext.tree.ColumnNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
}
}
});
+
+
+
+
+Ext.grid.RowExpander = function(config){
+ Ext.apply(this, config);
+
+ this.addEvents({
+ beforeexpand : true,
+ expand: true,
+ beforecollapse: true,
+ collapse: true
+ });
+
+ Ext.grid.RowExpander.superclass.constructor.call(this);
+
+ if(this.tpl){
+ if(typeof this.tpl == 'string'){
+ this.tpl = new Ext.Template(this.tpl);
+ }
+ this.tpl.compile();
+ }
+
+ this.state = {};
+ this.bodyContent = {};
+};
+
+Ext.extend(Ext.grid.RowExpander, Ext.util.Observable, {
+ header: "",
+ width: 20,
+ sortable: false,
+ fixed:true,
+ menuDisabled:true,
+ dataIndex: '',
+ id: 'expander',
+ lazyRender : true,
+ enableCaching: true,
+
+ getRowClass : function(record, rowIndex, p, ds){
+ p.cols = p.cols-1;
+ var content = this.bodyContent[record.id];
+ if(!content && !this.lazyRender){
+ content = this.getBodyContent(record, rowIndex);
+ }
+ if(content){
+ p.body = content;
+ }
+ return this.state[record.id] ? 'x-grid3-row-expanded' : 'x-grid3-row-collapsed';
+ },
+
+ init : function(grid){
+ this.grid = grid;
+
+ var view = grid.getView();
+ view.getRowClass = this.getRowClass.createDelegate(this);
+
+ view.enableRowBody = true;
+
+ grid.on('render', function(){
+ view.mainBody.on('mousedown', this.onMouseDown, this);
+ }, this);
+ },
+
+ getBodyContent : function(record, index){
+ if(!this.enableCaching){
+ return this.tpl.apply(record.data);
+ }
+ var content = this.bodyContent[record.id];
+ if(!content){
+ content = this.tpl.apply(record.data);
+ this.bodyContent[record.id] = content;
+ }
+ return content;
+ },
+
+ onMouseDown : function(e, t){
+ if(t.className == 'x-grid3-row-expander'){
+ e.stopEvent();
+ var row = e.getTarget('.x-grid3-row');
+ this.toggleRow(row);
+ }
+ },
+
+ renderer : function(v, p, record){
+ p.cellAttr = 'rowspan="2"';
+ return '
';
+ },
+
+ beforeExpand : function(record, body, rowIndex){
+ if(this.fireEvent('beforeexpand', this, record, body, rowIndex) !== false){
+ if(this.tpl && this.lazyRender){
+ body.innerHTML = this.getBodyContent(record, rowIndex);
+ }
+ return true;
+ }else{
+ return false;
+ }
+ },
+
+ toggleRow : function(row){
+ if(typeof row == 'number'){
+ row = this.grid.view.getRow(row);
+ }
+ this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'expandRow' : 'collapseRow'](row);
+ },
+
+ expandRow : function(row){
+ if(typeof row == 'number'){
+ row = this.grid.view.getRow(row);
+ }
+ var record = this.grid.store.getAt(row.rowIndex);
+ var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);
+ if(this.beforeExpand(record, body, row.rowIndex)){
+ this.state[record.id] = true;
+ Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded');
+ this.fireEvent('expand', this, record, body, row.rowIndex);
+ }
+ },
+
+ collapseRow : function(row){
+ if(typeof row == 'number'){
+ row = this.grid.view.getRow(row);
+ }
+ var record = this.grid.store.getAt(row.rowIndex);
+ var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true);
+ if(this.fireEvent('beforecollapse', this, record, body, row.rowIndex) !== false){
+ this.state[record.id] = false;
+ Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed');
+ this.fireEvent('collapse', this, record, body, row.rowIndex);
+ }
+ }
+});
diff --git a/webui/static/app/tvheadend.js b/webui/static/app/tvheadend.js
index b086d175..cd99ad29 100644
--- a/webui/static/app/tvheadend.js
+++ b/webui/static/app/tvheadend.js
@@ -87,7 +87,8 @@ tvheadend.app = function() {
activeTab:0,
autoScroll:true,
title: 'Configuration',
- items: [new tvheadend.dvb,
+ items: [new tvheadend.chconf,
+ new tvheadend.dvb,
new tvheadend.acleditor,
new tvheadend.cwceditor]
});