Add an electronic program guide (EPG) to the web interface.
Only for reading yet.
This commit is contained in:
parent
7122c27598
commit
2b854cb48d
7 changed files with 543 additions and 30 deletions
14
channels.c
14
channels.c
|
@ -599,6 +599,20 @@ channel_tag_find(const char *id, int create)
|
|||
return ct;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
channel_tag_t *
|
||||
channel_tag_find_by_name(const char *name)
|
||||
{
|
||||
channel_tag_t *ct;
|
||||
|
||||
TAILQ_FOREACH(ct, &channel_tags, ct_link)
|
||||
if(!strcmp(ct->ct_name, name))
|
||||
break;
|
||||
return ct;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
|
@ -120,6 +120,8 @@ void channel_set_xmltv_source(channel_t *ch, struct xmltv_channel *xc);
|
|||
|
||||
void channel_set_tags_from_list(channel_t *ch, const char *maplist);
|
||||
|
||||
channel_tag_t *channel_tag_find_by_name(const char *name);
|
||||
|
||||
extern struct channel_list channels_not_xmltv_mapped;
|
||||
|
||||
#endif /* CHANNELS_H */
|
||||
|
|
133
epg.c
133
epg.c
|
@ -117,6 +117,7 @@ event_t *
|
|||
epg_event_find_by_start(channel_t *ch, time_t start, int create)
|
||||
{
|
||||
static event_t *skel, *e;
|
||||
static int tally;
|
||||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
|
@ -134,6 +135,7 @@ epg_event_find_by_start(channel_t *ch, time_t start, int create)
|
|||
e = skel;
|
||||
skel = NULL;
|
||||
|
||||
e->e_id = ++tally;
|
||||
e->e_refcount = 1;
|
||||
e->e_channel = ch;
|
||||
epg_event_changed(e);
|
||||
|
@ -269,6 +271,15 @@ static const char *groupnames[16] = {
|
|||
[11] = "Special characteristics",
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const char *
|
||||
epg_content_group_get_name(unsigned int id)
|
||||
{
|
||||
return id < 16 ? groupnames[id] : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a content type
|
||||
*/
|
||||
|
@ -309,7 +320,7 @@ epg_content_group_find_by_name(const char *name)
|
|||
|
||||
for(i = 0; i < 16; i++) {
|
||||
ecg = epg_content_groups[i];
|
||||
if(ecg->ecg_name && !strcmp(name, ecg->ecg_name))
|
||||
if(ecg != NULL && ecg->ecg_name && !strcmp(name, ecg->ecg_name))
|
||||
return ecg;
|
||||
}
|
||||
return NULL;
|
||||
|
@ -384,3 +395,123 @@ epg_init(void)
|
|||
epg_content_type_find_by_dvbcode(i);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
eqr_add(epg_query_result_t *eqr, event_t *e, regex_t *preg)
|
||||
{
|
||||
if(preg != NULL && regexec(preg, e->e_title, 0, NULL, 0))
|
||||
return;
|
||||
|
||||
if(eqr->eqr_entries == eqr->eqr_alloced) {
|
||||
/* Need to alloc more space */
|
||||
|
||||
eqr->eqr_alloced = MAX(100, eqr->eqr_alloced * 2);
|
||||
eqr->eqr_array = realloc(eqr->eqr_array,
|
||||
eqr->eqr_alloced * sizeof(event_t *));
|
||||
}
|
||||
eqr->eqr_array[eqr->eqr_entries++] = e;
|
||||
e->e_refcount++;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
epg_query_add_channel(epg_query_result_t *eqr, channel_t *ch,
|
||||
epg_content_group_t *ecg, regex_t *preg)
|
||||
{
|
||||
event_t *e;
|
||||
|
||||
if(ecg == NULL) {
|
||||
RB_FOREACH(e, &ch->ch_epg_events, e_channel_link)
|
||||
eqr_add(eqr, e, preg);
|
||||
return;
|
||||
}
|
||||
|
||||
RB_FOREACH(e, &ch->ch_epg_events, e_channel_link)
|
||||
if(e->e_content_type != NULL && ecg == e->e_content_type->ect_group)
|
||||
eqr_add(eqr, e, preg);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
epg_query(epg_query_result_t *eqr, const char *channel, const char *tag,
|
||||
const char *contentgroup, const char *title)
|
||||
{
|
||||
channel_t *ch = channel ? channel_find_by_name(channel, 0) : NULL;
|
||||
channel_tag_t *ct = tag ? channel_tag_find_by_name(tag) : NULL;
|
||||
epg_content_group_t *ecg = contentgroup ?
|
||||
epg_content_group_find_by_name(contentgroup) : NULL;
|
||||
channel_tag_mapping_t *ctm;
|
||||
|
||||
regex_t preg0, *preg;
|
||||
|
||||
if(title != NULL) {
|
||||
if(regcomp(&preg0, title, REG_ICASE | REG_EXTENDED | REG_NOSUB))
|
||||
return;
|
||||
preg = &preg0;
|
||||
} else {
|
||||
preg = NULL;
|
||||
}
|
||||
|
||||
lock_assert(&global_lock);
|
||||
memset(eqr, 0, sizeof(epg_query_result_t));
|
||||
|
||||
if(ch != NULL && ct == NULL) {
|
||||
epg_query_add_channel(eqr, ch, ecg, preg);
|
||||
return;
|
||||
}
|
||||
|
||||
if(ct != NULL) {
|
||||
LIST_FOREACH(ctm, &ct->ct_ctms, ctm_tag_link)
|
||||
if(ch == NULL || ctm->ctm_channel == ch)
|
||||
epg_query_add_channel(eqr, ctm->ctm_channel, ecg, preg);
|
||||
return;
|
||||
}
|
||||
|
||||
RB_FOREACH(ch, &channel_name_tree, ch_name_link)
|
||||
epg_query_add_channel(eqr, ch, ecg, preg);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
epg_query_free(epg_query_result_t *eqr)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i = 0; i < eqr->eqr_entries; i++)
|
||||
epg_event_unref(eqr->eqr_array[i]);
|
||||
free(eqr->eqr_array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorting functions
|
||||
*/
|
||||
static int
|
||||
epg_sort_start_ascending(const void *A, const void *B)
|
||||
{
|
||||
event_t *a = *(event_t **)A;
|
||||
event_t *b = *(event_t **)B;
|
||||
return a->e_start - b->e_start;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
epg_query_sort(epg_query_result_t *eqr)
|
||||
{
|
||||
int (*sf)(const void *a, const void *b);
|
||||
|
||||
sf = epg_sort_start_ascending;
|
||||
|
||||
qsort(eqr->eqr_array, eqr->eqr_entries, sizeof(event_t *), sf);
|
||||
}
|
||||
|
|
17
epg.h
17
epg.h
|
@ -48,6 +48,7 @@ typedef struct event {
|
|||
RB_ENTRY(event) e_channel_link;
|
||||
|
||||
int e_refcount;
|
||||
uint32_t e_id;
|
||||
|
||||
LIST_ENTRY(event) e_content_type_link;
|
||||
epg_content_type_t *e_content_type;
|
||||
|
@ -93,4 +94,20 @@ epg_content_type_t *epg_content_type_find_by_dvbcode(uint8_t dvbcode);
|
|||
|
||||
epg_content_group_t *epg_content_group_find_by_name(const char *name);
|
||||
|
||||
const char *epg_content_group_get_name(unsigned int id);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
typedef struct epg_query_result {
|
||||
event_t **eqr_array;
|
||||
int eqr_entries;
|
||||
int eqr_alloced;
|
||||
} epg_query_result_t;
|
||||
|
||||
void epg_query(epg_query_result_t *eqr, const char *channel, const char *tag,
|
||||
const char *contentgroup, const char *title);
|
||||
void epg_query_free(epg_query_result_t *eqr);
|
||||
void epg_query_sort(epg_query_result_t *eqr);
|
||||
|
||||
#endif /* EPG_H */
|
||||
|
|
108
webui/extjs.c
108
webui/extjs.c
|
@ -41,6 +41,7 @@
|
|||
#include "transports.h"
|
||||
#include "serviceprobe.h"
|
||||
#include "xmltv.h"
|
||||
#include "epg.h"
|
||||
|
||||
extern const char *htsversion;
|
||||
|
||||
|
@ -109,6 +110,7 @@ extjs_root(http_connection_t *hc, const char *remain, void *opaque)
|
|||
extjs_load(hq, "static/app/cwceditor.js");
|
||||
extjs_load(hq, "static/app/dvb.js");
|
||||
extjs_load(hq, "static/app/chconf.js");
|
||||
extjs_load(hq, "static/app/epg.js");
|
||||
|
||||
/**
|
||||
* Finally, the app itself
|
||||
|
@ -243,6 +245,38 @@ extjs_chlist(http_connection_t *hc, const char *remain, void *opaque)
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* EPG Content Groups
|
||||
*/
|
||||
static int
|
||||
extjs_ecglist(http_connection_t *hc, const char *remain, void *opaque)
|
||||
{
|
||||
htsbuf_queue_t *hq = &hc->hc_reply;
|
||||
htsmsg_t *out, *array, *c;
|
||||
const char *s;
|
||||
int i;
|
||||
|
||||
out = htsmsg_create();
|
||||
array = htsmsg_create_array();
|
||||
|
||||
for(i = 0; i < 16; i++) {
|
||||
if((s = epg_content_group_get_name(i)) == NULL)
|
||||
continue;
|
||||
|
||||
c = htsmsg_create();
|
||||
htsmsg_add_str(c, "name", s);
|
||||
htsmsg_add_msg(array, NULL, c);
|
||||
}
|
||||
|
||||
htsmsg_add_msg(out, "entries", array);
|
||||
|
||||
htsmsg_json_serialize(out, hq, 0);
|
||||
htsmsg_destroy(out);
|
||||
http_output_content(hc, "text/x-json; charset=UTF-8");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -757,7 +791,79 @@ extjs_channeltags(http_connection_t *hc, const char *remain, void *opaque)
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int
|
||||
extjs_epg(http_connection_t *hc, const char *remain, void *opaque)
|
||||
{
|
||||
htsbuf_queue_t *hq = &hc->hc_reply;
|
||||
htsmsg_t *out, *array, *m;
|
||||
epg_query_result_t eqr;
|
||||
event_t *e;
|
||||
int start = 0, end, limit, i;
|
||||
const char *s;
|
||||
const char *channel = http_arg_get(&hc->hc_req_args, "channel");
|
||||
const char *tag = http_arg_get(&hc->hc_req_args, "tag");
|
||||
const char *cgrp = http_arg_get(&hc->hc_req_args, "contentgrp");
|
||||
const char *title = http_arg_get(&hc->hc_req_args, "title");
|
||||
|
||||
if(channel && !channel[0]) channel = NULL;
|
||||
if(tag && !tag[0]) tag = NULL;
|
||||
|
||||
if((s = http_arg_get(&hc->hc_req_args, "start")) != NULL)
|
||||
start = atoi(s);
|
||||
|
||||
if((s = http_arg_get(&hc->hc_req_args, "limit")) != NULL)
|
||||
limit = atoi(s);
|
||||
else
|
||||
limit = 20; /* XXX */
|
||||
|
||||
out = htsmsg_create();
|
||||
array = htsmsg_create_array();
|
||||
|
||||
pthread_mutex_lock(&global_lock);
|
||||
|
||||
epg_query(&eqr, channel, tag, cgrp, title);
|
||||
|
||||
epg_query_sort(&eqr);
|
||||
|
||||
htsmsg_add_u32(out, "totalCount", eqr.eqr_entries);
|
||||
|
||||
|
||||
start = MIN(start, eqr.eqr_entries);
|
||||
end = MIN(start + limit, eqr.eqr_entries);
|
||||
|
||||
for(i = start; i < end; i++) {
|
||||
e = eqr.eqr_array[i];
|
||||
|
||||
m = htsmsg_create();
|
||||
|
||||
if(e->e_channel != NULL)
|
||||
htsmsg_add_str(m, "channel", e->e_channel->ch_name);
|
||||
htsmsg_add_str(m, "title", e->e_title);
|
||||
htsmsg_add_str(m, "description", e->e_desc);
|
||||
htsmsg_add_u32(m, "id", e->e_id);
|
||||
htsmsg_add_u32(m, "start", e->e_start);
|
||||
htsmsg_add_u32(m, "end", e->e_start + e->e_duration);
|
||||
htsmsg_add_u32(m, "duration", e->e_duration);
|
||||
|
||||
if(e->e_content_type != NULL)
|
||||
htsmsg_add_str(m, "contentgrp", e->e_content_type->ect_group->ecg_name);
|
||||
htsmsg_add_msg(array, NULL, m);
|
||||
}
|
||||
|
||||
epg_query_free(&eqr);
|
||||
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
|
||||
htsmsg_add_msg(out, "entries", array);
|
||||
|
||||
htsmsg_json_serialize(out, hq, 0);
|
||||
htsmsg_destroy(out);
|
||||
http_output_content(hc, "text/x-json; charset=UTF-8");
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* WEB user interface
|
||||
*/
|
||||
|
@ -773,4 +879,6 @@ extjs_start(void)
|
|||
http_path_add("/channel", NULL, extjs_channel, ACCESS_WEB_INTERFACE);
|
||||
http_path_add("/xmltv", NULL, extjs_xmltv, ACCESS_WEB_INTERFACE);
|
||||
http_path_add("/channeltags", NULL, extjs_channeltags, ACCESS_WEB_INTERFACE);
|
||||
http_path_add("/epg", NULL, extjs_epg, ACCESS_WEB_INTERFACE);
|
||||
http_path_add("/ecglist", NULL, extjs_ecglist, ACCESS_WEB_INTERFACE);
|
||||
}
|
||||
|
|
250
webui/static/app/epg.js
Normal file
250
webui/static/app/epg.js
Normal file
|
@ -0,0 +1,250 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
tvheadend.epg = function() {
|
||||
var xg = Ext.grid;
|
||||
|
||||
var epgRecord = Ext.data.Record.create([
|
||||
{name: 'id'},
|
||||
{name: 'channel'},
|
||||
{name: 'title'},
|
||||
{name: 'description'},
|
||||
{name: 'start', type: 'date', dateFormat: 'U' /* unix time */},
|
||||
{name: 'end', type: 'date', dateFormat: 'U' /* unix time */},
|
||||
{name: 'duration'},
|
||||
{name: 'contentgrp'}
|
||||
]);
|
||||
|
||||
var epgStore = new Ext.data.JsonStore({
|
||||
root: 'entries',
|
||||
totalProperty: 'totalCount',
|
||||
fields: epgRecord,
|
||||
url: 'epg',
|
||||
autoLoad: true,
|
||||
id: 'id',
|
||||
remoteSort: true,
|
||||
});
|
||||
|
||||
var expander = new xg.RowExpander({
|
||||
tpl : new Ext.Template('<div>{description}</div>')
|
||||
});
|
||||
|
||||
function renderDate(value){
|
||||
var dt = new Date(value);
|
||||
return dt.format('l H:i');
|
||||
}
|
||||
|
||||
function renderDuration(value){
|
||||
value = value / 60; /* Nevermind the seconds */
|
||||
|
||||
if(value >= 60) {
|
||||
var min = value % 60;
|
||||
var hours = parseInt(value / 60);
|
||||
|
||||
if(min == 0) {
|
||||
return hours + ' hrs';
|
||||
}
|
||||
return hours + ' hrs, ' + min + ' min';
|
||||
} else {
|
||||
return value + ' min';
|
||||
}
|
||||
}
|
||||
|
||||
var epgCm = new Ext.grid.ColumnModel([
|
||||
expander,
|
||||
{
|
||||
width: 250,
|
||||
id:'title',
|
||||
header: "Title",
|
||||
dataIndex: 'title',
|
||||
},{
|
||||
width: 100,
|
||||
id:'start',
|
||||
header: "Start",
|
||||
dataIndex: 'start',
|
||||
renderer: renderDate,
|
||||
},{
|
||||
width: 100,
|
||||
hidden:true,
|
||||
id:'end',
|
||||
header: "End",
|
||||
dataIndex: 'end',
|
||||
renderer: renderDate,
|
||||
},{
|
||||
width: 100,
|
||||
id:'duration',
|
||||
header: "Duration",
|
||||
dataIndex: 'duration',
|
||||
renderer: renderDuration
|
||||
},{
|
||||
width: 250,
|
||||
id:'channel',
|
||||
header: "Channel",
|
||||
dataIndex: 'channel',
|
||||
},{
|
||||
width: 250,
|
||||
id:'contentgrp',
|
||||
header: "Content Type",
|
||||
dataIndex: 'contentgrp',
|
||||
}
|
||||
]);
|
||||
|
||||
|
||||
// Title search box
|
||||
|
||||
var epgFilterTitle = new Ext.form.TextField({
|
||||
emptyText: 'Search title...',
|
||||
width: 200
|
||||
});
|
||||
|
||||
|
||||
// Channels, XXX: Perhaps we should make channes a global store as well
|
||||
|
||||
var epgFilterChannelsStore = new Ext.data.JsonStore({
|
||||
root:'entries',
|
||||
fields: [{name: 'name'}],
|
||||
url:'chlist'
|
||||
});
|
||||
|
||||
var epgFilterChannels = new Ext.form.ComboBox({
|
||||
loadingText: 'Loading...',
|
||||
width: 200,
|
||||
displayField:'name',
|
||||
store: epgFilterChannelsStore,
|
||||
mode: 'remote',
|
||||
editable: false,
|
||||
triggerAction: 'all',
|
||||
emptyText: 'Only include channel...'
|
||||
});
|
||||
|
||||
// Tags, uses global store
|
||||
|
||||
var epgFilterChannelTags = new Ext.form.ComboBox({
|
||||
width: 200,
|
||||
displayField:'name',
|
||||
store: tvheadend.channelTags,
|
||||
mode: 'local',
|
||||
editable: false,
|
||||
triggerAction: 'all',
|
||||
emptyText: 'Only include tag...'
|
||||
});
|
||||
|
||||
// Content groups
|
||||
|
||||
var epgFilterContentGroupStore = new Ext.data.JsonStore({
|
||||
root:'entries',
|
||||
fields: [{name: 'name'}],
|
||||
url:'ecglist',
|
||||
});
|
||||
|
||||
|
||||
var epgFilterContentGroup = new Ext.form.ComboBox({
|
||||
loadingText: 'Loading...',
|
||||
width: 200,
|
||||
displayField:'name',
|
||||
store: epgFilterContentGroupStore,
|
||||
mode: 'remote',
|
||||
editable: false,
|
||||
triggerAction: 'all',
|
||||
emptyText: 'Only include content...'
|
||||
});
|
||||
|
||||
/*
|
||||
function epgReload() {
|
||||
epgStore.baseParams.channel = epgFilterChannels.getValue();
|
||||
epgStore.baseParams.tag = epgFilterChannelTags.getValue();
|
||||
epgStore.baseParams.contentgrp = epgFilterContentGroup.getValue();
|
||||
epgStore.baseParams.title = epgFilterTitle.getValue();
|
||||
console.log(epgStore.baseParams.title);
|
||||
epgStore.reload();
|
||||
}
|
||||
*/
|
||||
|
||||
function epgQueryClear() {
|
||||
epgStore.baseParams.channel = null;
|
||||
epgStore.baseParams.tag = null;
|
||||
epgStore.baseParams.contentgrp = null;
|
||||
epgStore.baseParams.title = null;
|
||||
|
||||
epgFilterChannels.setValue("");
|
||||
epgFilterChannelTags.setValue("");
|
||||
epgFilterContentGroup.setValue("");
|
||||
epgFilterTitle.setValue("");
|
||||
|
||||
epgStore.reload();
|
||||
}
|
||||
|
||||
epgFilterChannels.on('select', function(c, r) {
|
||||
if(epgStore.baseParams.channel != r.data.name) {
|
||||
epgStore.baseParams.channel = r.data.name;
|
||||
epgStore.reload();
|
||||
}
|
||||
});
|
||||
|
||||
epgFilterChannelTags.on('select', function(c, r) {
|
||||
if(epgStore.baseParams.tag != r.data.name) {
|
||||
epgStore.baseParams.tag = r.data.name;
|
||||
epgStore.reload();
|
||||
}
|
||||
});
|
||||
|
||||
epgFilterContentGroup.on('select', function(c, r) {
|
||||
if(epgStore.baseParams.contentgrp != r.data.name) {
|
||||
epgStore.baseParams.contentgrp = r.data.name;
|
||||
epgStore.reload();
|
||||
}
|
||||
});
|
||||
|
||||
epgFilterTitle.on('valid', function(c) {
|
||||
var value = c.getValue();
|
||||
|
||||
if(value.length < 1)
|
||||
value = null;
|
||||
|
||||
if(epgStore.baseParams.title != value) {
|
||||
epgStore.baseParams.title = value;
|
||||
epgStore.reload();
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
epgFilterChannelTags.on('select', epgReload);
|
||||
epgFilterContentGroup.on('select', epgReload);
|
||||
epgFilterTitle.on('valid', epgReload);
|
||||
*/
|
||||
var panel = new Ext.grid.GridPanel({
|
||||
loadMask: true,
|
||||
title: 'Electronic Program Guide',
|
||||
store: epgStore,
|
||||
cm: epgCm,
|
||||
plugins:[expander],
|
||||
|
||||
tbar: [
|
||||
epgFilterTitle,
|
||||
'-',
|
||||
epgFilterChannels,
|
||||
'-',
|
||||
epgFilterChannelTags,
|
||||
'-',
|
||||
epgFilterContentGroup,
|
||||
'-',
|
||||
{
|
||||
text: 'Reset',
|
||||
handler: epgQueryClear
|
||||
}
|
||||
],
|
||||
|
||||
bbar: new Ext.PagingToolbar({
|
||||
store: epgStore,
|
||||
pageSize: 20,
|
||||
displayInfo: true,
|
||||
displayMsg: 'Programs {0} - {1} of {2}',
|
||||
emptyMsg: "No programs to display"
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
|
@ -98,38 +98,29 @@ tvheadend.app = function() {
|
|||
new tvheadend.acleditor,
|
||||
new tvheadend.cwceditor]
|
||||
});
|
||||
|
||||
var pvrpanel = new Ext.TabPanel({
|
||||
autoScroll:true,
|
||||
title: 'Video Recorder'
|
||||
});
|
||||
|
||||
var chpanel = new Ext.TabPanel({
|
||||
autoScroll:true,
|
||||
title: 'Channels'
|
||||
});
|
||||
|
||||
|
||||
console.log(tvheadend);
|
||||
|
||||
var viewport = new Ext.Viewport({
|
||||
layout:'border',
|
||||
items:[{
|
||||
region:'south',
|
||||
contentEl: 'systemlog',
|
||||
split:true,
|
||||
autoScroll:true,
|
||||
height: 150,
|
||||
minSize: 100,
|
||||
maxSize: 400,
|
||||
collapsible: true,
|
||||
title:'System log',
|
||||
margins:'0 0 0 0'
|
||||
},
|
||||
new Ext.TabPanel({region:'center',
|
||||
activeTab:0,
|
||||
items:[confpanel,
|
||||
pvrpanel,
|
||||
chpanel]})
|
||||
]
|
||||
items:[
|
||||
{
|
||||
region:'south',
|
||||
contentEl: 'systemlog',
|
||||
split:true,
|
||||
autoScroll:true,
|
||||
height: 150,
|
||||
minSize: 100,
|
||||
maxSize: 400,
|
||||
collapsible: true,
|
||||
title:'System log',
|
||||
margins:'0 0 0 0'
|
||||
},new Ext.TabPanel({
|
||||
region:'center',
|
||||
activeTab:0,
|
||||
items:[new tvheadend.epg,confpanel]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
new tvheadend.comet_poller;
|
||||
|
|
Loading…
Add table
Reference in a new issue