Add channel configuration and setup.

htsclient.c is now no longer supported.
This commit is contained in:
Andreas Öman 2008-08-26 14:15:33 +00:00
parent e7e4b9f710
commit e544180acf
28 changed files with 850 additions and 800 deletions

View file

@ -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

View file

@ -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)

View file

@ -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));

View file

@ -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);

View file

@ -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);

View file

@ -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;

5
epg.c
View file

@ -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;

View file

@ -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);

View file

@ -39,7 +39,6 @@
#include "tvhead.h"
#include "channels.h"
#include "subscriptions.h"
#include "htsclient.h"
#include "ffmuxer.h"
#include "buffer.h"

View file

@ -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);

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <fcntl.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#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);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef HTSCLIENT_H_
#define HTSCLIENT_H_
void client_start(void);
void clients_send_ref(int ref);
#endif /* HTSCLIENT_H_ */

13
htsp.c
View file

@ -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) {

View file

@ -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;
}

View file

@ -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));

2
main.c
View file

@ -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)

2
psi.c
View file

@ -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))

3
pvr.c
View file

@ -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;

6
rpc.c
View file

@ -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) {

2
rtsp.c
View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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,

View file

@ -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);
}

361
webui/static/app/chconf.js Normal file
View file

@ -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(
'<div><b width=100px>Video:</b>{video}</div>',
'<div><b>Audio:</b>{audio}</div>',
'<div><b>Subtitling:</b>{subtitles}</div>',
'<div><b>Scrambling:</b>{scrambling}</div>'
)
});
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;
}

View file

@ -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
},{

View file

@ -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 '<div class="x-grid3-row-expander">&#160;</div>';
},
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);
}
}
});

View file

@ -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]
});