Add UI to configure V4L adapters.

Not 100% done yet. Just moving code around.

#102
This commit is contained in:
Andreas Öman 2009-09-09 20:02:41 +00:00
parent 2c0ef0f264
commit 4dc2891a2c
11 changed files with 1047 additions and 200 deletions

View file

@ -499,6 +499,8 @@ dvb_adapter_build_msg(th_dvb_adapter_t *tda)
}
}
htsmsg_add_str(m, "type", "dvb");
htsmsg_add_u32(m, "services", numsvc);
htsmsg_add_u32(m, "muxes", nummux);
htsmsg_add_u32(m, "initialMuxes", tda->tda_initial_num_mux);
@ -538,7 +540,7 @@ dvb_adapter_build_msg(th_dvb_adapter_t *tda)
void
dvb_adapter_notify(th_dvb_adapter_t *tda)
{
notify_by_msg("dvbAdapter", dvb_adapter_build_msg(tda));
notify_by_msg("tvAdapter", dvb_adapter_build_msg(tda));
}

View file

@ -54,7 +54,7 @@
#include "avahi.h"
#include "iptv_input.h"
#include "transports.h"
#include "v4l.h"
#include "parachute.h"
#include "settings.h"
@ -341,6 +341,8 @@ main(int argc, char **argv)
iptv_input_init();
v4l_init();
http_server_init();
webui_init(contentpath);

View file

@ -553,7 +553,7 @@ typedef struct th_transport {
*/
struct v4l_adapter *tht_v4l_adapter;
int tht_v4l_frequency; // In Hz
/*********************************************************

329
src/v4l.c
View file

@ -31,42 +31,17 @@
#include <poll.h>
#include <inttypes.h>
#define __user
#include <linux/videodev2.h>
#include "settings.h"
#include "tvhead.h"
#include "transports.h"
#include "v4l.h"
#include "parsers.h"
LIST_HEAD(v4l_adapter_list, v4l_adapter);
struct v4l_adapter_list v4l_adapters;
typedef struct v4l_adapter {
LIST_ENTRY(v4l_adapter) va_global_link;
char *va_path;
char *va_identifier;
struct v4l2_capability va_caps;
struct th_transport *va_current_transport;
#include "notify.h"
#include "psi.h"
int va_fd;
pthread_t va_thread;
int va_pipe[2];
/** Mpeg stream parsing */
uint32_t va_startcode;
int va_lenlock;
} v4l_adapter_t;
struct v4l_adapter_queue v4l_adapters;
/**
@ -290,7 +265,25 @@ v4l_transport_stop(th_transport_t *t)
static void
v4l_transport_save(th_transport_t *t)
{
v4l_adapter_t *va = t->tht_v4l_adapter;
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_u32(m, "frequency", t->tht_v4l_frequency);
if(t->tht_ch != NULL) {
htsmsg_add_str(m, "channelname", t->tht_ch->ch_name);
htsmsg_add_u32(m, "mapped", 1);
}
pthread_mutex_lock(&t->tht_stream_mutex);
psi_save_transport_settings(m, t);
pthread_mutex_unlock(&t->tht_stream_mutex);
hts_settings_save(m, "v4lservices/%s/%s",
va->va_identifier, t->tht_identifier);
htsmsg_destroy(m);
}
@ -323,18 +316,36 @@ v4l_transport_sourceinfo(th_transport_t *t)
/**
*
*/
static th_transport_t *
v4l_add_transport(v4l_adapter_t *va)
th_transport_t *
v4l_transport_find(v4l_adapter_t *va, const char *id, int create)
{
th_transport_t *t;
char buf[200];
char id[256];
int vaidlen = strlen(va->va_identifier);
snprintf(id, sizeof(id), "%s_%s", va->va_identifier, "foo");
printf("Adding transport %s\n", id);
if(id != NULL) {
if(strncmp(id, va->va_identifier, vaidlen))
return NULL;
LIST_FOREACH(t, &va->va_transports, tht_group_link)
if(!strcmp(t->tht_identifier, id))
return t;
}
if(create == 0)
return NULL;
if(id == NULL) {
va->va_tally++;
snprintf(buf, sizeof(buf), "%s_%d", va->va_identifier, va->va_tally);
id = buf;
} else {
va->va_tally = MAX(atoi(id + vaidlen), va->va_tally);
}
t = transport_create(id, TRANSPORT_V4L, 0);
t->tht_flags |= THT_DEBUG;
t->tht_start_feed = v4l_transport_start;
t->tht_refresh_feed = v4l_transport_refresh;
@ -342,25 +353,50 @@ v4l_add_transport(v4l_adapter_t *va)
t->tht_config_save = v4l_transport_save;
t->tht_sourceinfo = v4l_transport_sourceinfo;
t->tht_quality_index = v4l_transport_quality;
t->tht_iptv_fd = -1;
t->tht_v4l_adapter = va;
pthread_mutex_lock(&t->tht_stream_mutex);
LIST_INSERT_HEAD(&va->va_transports, t, tht_group_link);
t->tht_video = transport_stream_create(t, -1, SCT_MPEG2VIDEO);
t->tht_audio = transport_stream_create(t, -1, SCT_MPEG2AUDIO);
pthread_mutex_unlock(&t->tht_stream_mutex);
transport_map_channel(t, channel_find_by_name("alpha", 1), 0);
//XXX LIST_INSERT_HEAD(&v4l_all_transports, t, tht_group_link);
return t;
}
/**
*
*/
static void
v4l_adapter_add(const char *path, const char *displayname,
const char *devicename )
{
v4l_adapter_t *va;
int i, r;
va = calloc(1, sizeof(v4l_adapter_t));
va->va_identifier = strdup(path);
r = strlen(va->va_identifier);
for(i = 0; i < r; i++)
if(!isalnum((int)va->va_identifier[i]))
va->va_identifier[i] = '_';
va->va_displayname = strdup(displayname);
va->va_path = path ? strdup(path) : NULL;
va->va_devicename = devicename ? strdup(devicename) : NULL;
// va->va_caps = caps;
TAILQ_INSERT_TAIL(&v4l_adapters, va, va_global_link);
#if 0
tvhlog(LOG_INFO, "v4l", "Adding adapter %s: %s (%s) @ %s",
path, caps.card, caps.driver, caps.bus_info, caps.bus_info);
#endif
// v4l_add_transport(va);
}
/**
@ -371,7 +407,6 @@ v4l_adapter_check(const char *path, int fd)
{
int r, i;
v4l_adapter_t *va;
struct v4l2_capability caps;
r = ioctl(fd, VIDIOC_QUERYCAP, &caps);
@ -432,26 +467,7 @@ v4l_adapter_check(const char *path, int fd)
break;
}
va = calloc(1, sizeof(v4l_adapter_t));
va->va_identifier = strdup(path);
r = strlen(va->va_identifier);
for(i = 0; i < r; i++)
if(!isalnum((int)va->va_identifier[i]))
va->va_identifier[i] = '_';
va->va_path = strdup(path);
va->va_caps = caps;
LIST_INSERT_HEAD(&v4l_adapters, va, va_global_link);
tvhlog(LOG_INFO, "v4l", "Adding adapter %s: %s (%s) @ %s",
path, caps.card, caps.driver, caps.bus_info, caps.bus_info);
v4l_add_transport(va);
v4l_adapter_add(path, "fiktiv adapter", "andomas hw");
}
@ -481,15 +497,196 @@ v4l_adapter_probe(const char *path)
/**
* Save config for the given adapter
*/
static void
v4l_adapter_save(v4l_adapter_t *va)
{
htsmsg_t *m = htsmsg_create_map();
lock_assert(&global_lock);
htsmsg_add_str(m, "displayname", va->va_displayname);
htsmsg_add_u32(m, "logging", va->va_logging);
hts_settings_save(m, "v4ladapters/%s", va->va_identifier);
htsmsg_destroy(m);
}
/**
*
*/
htsmsg_t *
v4l_adapter_build_msg(v4l_adapter_t *va)
{
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "identifier", va->va_identifier);
htsmsg_add_str(m, "name", va->va_displayname);
htsmsg_add_str(m, "type", "v4l");
if(va->va_path)
htsmsg_add_str(m, "path", va->va_path);
if(va->va_devicename)
htsmsg_add_str(m, "devicename", va->va_devicename);
return m;
}
/**
*
*/
static void
v4l_adapter_notify(v4l_adapter_t *va)
{
notify_by_msg("tvAdapter", v4l_adapter_build_msg(va));
}
/**
*
*/
v4l_adapter_t *
v4l_adapter_find_by_identifier(const char *identifier)
{
v4l_adapter_t *va;
TAILQ_FOREACH(va, &v4l_adapters, va_global_link)
if(!strcmp(identifier, va->va_identifier))
return va;
return NULL;
}
/**
*
*/
void
v4l_adapter_set_displayname(v4l_adapter_t *va, const char *name)
{
lock_assert(&global_lock);
if(!strcmp(name, va->va_displayname))
return;
tvhlog(LOG_NOTICE, "v4l", "Adapter \"%s\" renamed to \"%s\"",
va->va_displayname, name);
tvh_str_set(&va->va_displayname, name);
v4l_adapter_save(va);
v4l_adapter_notify(va);
}
/**
*
*/
void
v4l_adapter_set_logging(v4l_adapter_t *va, int on)
{
if(va->va_logging == on)
return;
lock_assert(&global_lock);
tvhlog(LOG_NOTICE, "v4l", "Adapter \"%s\" detailed logging set to: %s",
va->va_displayname, on ? "On" : "Off");
va->va_logging = on;
v4l_adapter_save(va);
v4l_adapter_notify(va);
}
/**
*
*/
static void
v4l_service_create_by_msg(v4l_adapter_t *va, htsmsg_t *c, const char *name)
{
const char *s;
unsigned int u32;
th_transport_t *t = v4l_transport_find(va, name, 1);
if(t == NULL)
return;
s = htsmsg_get_str(c, "channelname");
if(htsmsg_get_u32(c, "mapped", &u32))
u32 = 0;
if(!htsmsg_get_u32(c, "frequency", &u32))
t->tht_v4l_frequency = u32;
if(s && u32)
transport_map_channel(t, channel_find_by_name(s, 1), 0);
}
/**
*
*/
static void
v4l_service_load(v4l_adapter_t *va)
{
htsmsg_t *l, *c;
htsmsg_field_t *f;
if((l = hts_settings_load("v4lservices/%s", va->va_identifier)) == NULL)
return;
HTSMSG_FOREACH(f, l) {
if((c = htsmsg_get_map_by_field(f)) == NULL)
continue;
v4l_service_create_by_msg(va, c, f->hmf_name);
}
htsmsg_destroy(l);
}
/**
*
*/
void
v4l_init(void)
{
htsmsg_t *l, *c;
htsmsg_field_t *f;
char buf[256];
int i;
v4l_adapter_t *va;
TAILQ_INIT(&v4l_adapters);
for(i = 0; i < 1; i++) {
snprintf(buf, sizeof(buf), "/dev/video%d", i);
v4l_adapter_probe(buf);
}
l = hts_settings_load("v4ladapters");
if(l != NULL) {
HTSMSG_FOREACH(f, l) {
if((c = htsmsg_get_map_by_field(f)) == NULL)
continue;
if((va = v4l_adapter_find_by_identifier(f->hmf_name)) == NULL) {
/* Not discovered by hardware, create it */
va = calloc(1, sizeof(v4l_adapter_t));
va->va_identifier = strdup(f->hmf_name);
va->va_path = NULL;
va->va_devicename = NULL;
}
tvh_str_update(&va->va_displayname, htsmsg_get_str(c, "displayname"));
htsmsg_get_u32(c, "logging", &va->va_logging);
}
htsmsg_destroy(l);
}
TAILQ_FOREACH(va, &v4l_adapters, va_global_link)
v4l_service_load(va);
}

View file

@ -19,6 +19,62 @@
#ifndef V4L_H_
#define V4L_H_
#define __user
#include <linux/videodev2.h>
LIST_HEAD(v4l_adapter_list, v4l_adapter);
TAILQ_HEAD(v4l_adapter_queue, v4l_adapter);
extern struct v4l_adapter_queue v4l_adapters;
typedef struct v4l_adapter {
TAILQ_ENTRY(v4l_adapter) va_global_link;
char *va_path;
char *va_identifier;
char *va_displayname;
char *va_devicename;
uint32_t va_logging;
// struct v4l2_capability va_caps;
struct th_transport *va_current_transport;
struct th_transport_list va_transports;
int va_tally;
/** Receiver thread stuff */
int va_fd;
pthread_t va_thread;
int va_pipe[2];
/** Mpeg stream parsing */
uint32_t va_startcode;
int va_lenlock;
} v4l_adapter_t;
v4l_adapter_t *v4l_adapter_find_by_identifier(const char *identifier);
void v4l_adapter_set_displayname(v4l_adapter_t *va, const char *name);
void v4l_adapter_set_logging(v4l_adapter_t *va, int on);
htsmsg_t *v4l_adapter_build_msg(v4l_adapter_t *va);
th_transport_t *v4l_transport_find(v4l_adapter_t *va, const char *id,
int create);
void v4l_init(void);
#endif /* V4L_H */

View file

@ -42,6 +42,7 @@
#include "dvb/dvb_support.h"
#include "dvb/dvb_preconf.h"
#include "dvr/dvr.h"
#include "v4l.h"
#include "transports.h"
#include "serviceprobe.h"
#include "xmltv.h"
@ -126,8 +127,10 @@ extjs_root(http_connection_t *hc, const char *remain, void *opaque)
extjs_load(hq, "static/app/cteditor.js");
extjs_load(hq, "static/app/acleditor.js");
extjs_load(hq, "static/app/cwceditor.js");
extjs_load(hq, "static/app/tvadapters.js");
extjs_load(hq, "static/app/dvb.js");
extjs_load(hq, "static/app/iptv.js");
extjs_load(hq, "static/app/v4l.js");
extjs_load(hq, "static/app/chconf.js");
extjs_load(hq, "static/app/epg.js");
extjs_load(hq, "static/app/dvr.js");
@ -1765,6 +1768,245 @@ extjs_iptvservices(http_connection_t *hc, const char *remain, void *opaque)
return 0;
}
/**
*
*/
static int
extjs_v4ladapter(http_connection_t *hc, const char *remain, void *opaque)
{
htsbuf_queue_t *hq = &hc->hc_reply;
v4l_adapter_t *va;
htsmsg_t *out, *array, *r;
const char *op = http_arg_get(&hc->hc_req_args, "op");
const char *s;
pthread_mutex_lock(&global_lock);
if(remain == NULL) {
/* Just list all adapters */
array = htsmsg_create_list();
TAILQ_FOREACH(va, &v4l_adapters, va_global_link)
htsmsg_add_msg(array, NULL, v4l_adapter_build_msg(va));
pthread_mutex_unlock(&global_lock);
out = htsmsg_create_map();
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;
}
if((va = v4l_adapter_find_by_identifier(remain)) == NULL) {
pthread_mutex_unlock(&global_lock);
return 404;
}
if(!strcmp(op, "load")) {
r = htsmsg_create_map();
htsmsg_add_str(r, "id", va->va_identifier);
htsmsg_add_str(r, "device", va->va_path ?: "No hardware attached");
htsmsg_add_str(r, "name", va->va_displayname);
htsmsg_add_u32(r, "logging", va->va_logging);
out = json_single_record(r, "v4ladapters");
} else if(!strcmp(op, "save")) {
if((s = http_arg_get(&hc->hc_req_args, "name")) != NULL)
v4l_adapter_set_displayname(va, s);
s = http_arg_get(&hc->hc_req_args, "logging");
v4l_adapter_set_logging(va, !!s);
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);
} else {
pthread_mutex_unlock(&global_lock);
return HTTP_STATUS_BAD_REQUEST;
}
pthread_mutex_unlock(&global_lock);
htsmsg_json_serialize(out, hq, 0);
htsmsg_destroy(out);
http_output_content(hc, "text/x-json; charset=UTF-8");
return 0;
}
/**
*
*/
static void
transport_update_v4l(htsmsg_t *in)
{
htsmsg_field_t *f;
htsmsg_t *c;
th_transport_t *t;
uint32_t u32;
const char *id;
int save;
TAILQ_FOREACH(f, &in->hm_fields, hmf_link) {
if((c = htsmsg_get_map_by_field(f)) == NULL ||
(id = htsmsg_get_str(c, "id")) == NULL)
continue;
if((t = transport_find_by_identifier(id)) == NULL)
continue;
save = 0;
if(!htsmsg_get_u32(c, "frequency", &u32)) {
t->tht_v4l_frequency = u32;
save = 1;
}
if(save)
t->tht_config_save(t); // Save config
}
}
/**
*
*/
static htsmsg_t *
build_record_v4l(th_transport_t *t)
{
htsmsg_t *r = htsmsg_create_map();
htsmsg_add_str(r, "id", t->tht_identifier);
htsmsg_add_str(r, "channelname", t->tht_ch ? t->tht_ch->ch_name : "");
htsmsg_add_u32(r, "frequency", t->tht_v4l_frequency);
htsmsg_add_u32(r, "enabled", t->tht_enabled);
return r;
}
/**
*
*/
static int
v4l_transportcmp(const void *A, const void *B)
{
th_transport_t *a = *(th_transport_t **)A;
th_transport_t *b = *(th_transport_t **)B;
return (int)a->tht_v4l_frequency - (int)b->tht_v4l_frequency;
}
/**
*
*/
static int
extjs_v4lservices(http_connection_t *hc, const char *remain, void *opaque)
{
v4l_adapter_t *va;
htsbuf_queue_t *hq = &hc->hc_reply;
htsmsg_t *out, *in, *array;
const char *op = http_arg_get(&hc->hc_req_args, "op");
const char *entries = http_arg_get(&hc->hc_req_args, "entries");
th_transport_t *t, **tvec;
int count = 0, i = 0;
pthread_mutex_lock(&global_lock);
if((va = v4l_adapter_find_by_identifier(remain)) == NULL) {
pthread_mutex_unlock(&global_lock);
return 404;
}
in = entries != NULL ? htsmsg_json_deserialize(entries) : NULL;
if(!strcmp(op, "get")) {
LIST_FOREACH(t, &va->va_transports, tht_group_link)
count++;
tvec = alloca(sizeof(th_transport_t *) * count);
LIST_FOREACH(t, &va->va_transports, tht_group_link)
tvec[i++] = t;
out = htsmsg_create_map();
array = htsmsg_create_list();
qsort(tvec, count, sizeof(th_transport_t *), v4l_transportcmp);
for(i = 0; i < count; i++)
htsmsg_add_msg(array, NULL, build_record_v4l(tvec[i]));
htsmsg_add_msg(out, "entries", array);
} else if(!strcmp(op, "update")) {
if(in != NULL) {
transport_update(in); // Generic transport parameters
transport_update_v4l(in); // V4L speicifc
}
out = htsmsg_create_map();
} else if(!strcmp(op, "create")) {
out = build_record_v4l(v4l_transport_find(va, NULL, 1));
} else if(!strcmp(op, "delete")) {
if(in != NULL)
transport_delete(in);
out = htsmsg_create_map();
} else {
pthread_mutex_unlock(&global_lock);
htsmsg_destroy(in);
return HTTP_STATUS_BAD_REQUEST;
}
htsmsg_destroy(in);
pthread_mutex_unlock(&global_lock);
htsmsg_json_serialize(out, hq, 0);
htsmsg_destroy(out);
http_output_content(hc, "text/x-json; charset=UTF-8");
return 0;
}
/**
*
*/
static int
extjs_tvadapter(http_connection_t *hc, const char *remain, void *opaque)
{
htsbuf_queue_t *hq = &hc->hc_reply;
htsmsg_t *out, *array;
th_dvb_adapter_t *tda;
v4l_adapter_t *va;
pthread_mutex_lock(&global_lock);
/* Just list all adapters */
array = htsmsg_create_list();
TAILQ_FOREACH(tda, &dvb_adapters, tda_global_link)
htsmsg_add_msg(array, NULL, dvb_adapter_build_msg(tda));
TAILQ_FOREACH(va, &v4l_adapters, va_global_link)
htsmsg_add_msg(array, NULL, v4l_adapter_build_msg(va));
pthread_mutex_unlock(&global_lock);
out = htsmsg_create_map();
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
@ -1817,4 +2059,14 @@ extjs_start(void)
http_path_add("/servicedetails",
NULL, extjs_servicedetails, ACCESS_ADMIN);
http_path_add("/v4l/adapter",
NULL, extjs_v4ladapter, ACCESS_ADMIN);
http_path_add("/v4l/services",
NULL, extjs_v4lservices, ACCESS_ADMIN);
http_path_add("/tv/adapter",
NULL, extjs_tvadapter, ACCESS_ADMIN);
}

View file

@ -4,7 +4,7 @@
Ext.extend(tvheadend.Comet = function() {
this.addEvents({
accessUpdate: true,
dvbAdapter: true,
tvAdapter: true,
dvbMux: true,
dvbStore: true,
dvbSatConf: true,

View file

@ -1,40 +1,3 @@
/**
* Datastore for adapters
*/
tvheadend.dvbAdapterStore = new Ext.data.JsonStore({
root:'entries',
id: 'identifier',
fields: ['identifier',
'name',
'path',
'devicename',
'currentMux',
'services',
'muxes',
'initialMuxes',
'satConf',
'deliverySystem',
'freqMin',
'freqMax',
'freqStep',
'symrateMin',
'symrateMax'
],
url:'dvb/adapter'
});
tvheadend.comet.on('dvbAdapter', function(m) {
idx = tvheadend.dvbAdapterStore.find('identifier', m.identifier);
if(idx == -1)
return;
r = tvheadend.dvbAdapterStore.getAt(idx);
r.beginEdit();
for (key in m)
r.set(key, m[key]);
r.endEdit();
tvheadend.dvbAdapterStore.commitChanges();
});
/**
@ -1116,7 +1079,7 @@ tvheadend.dvb_adapter_general = function(adapterData, satConfStore) {
/**
* Subscribe and react on updates for this adapter
*/
tvheadend.dvbAdapterStore.on('update', function(s, r, o) {
tvheadend.tvAdapterStore.on('update', function(s, r, o) {
if(r.data.identifier != adapterId)
return;
infoTemplate.overwrite(infoPanel.body, r.data);
@ -1240,94 +1203,3 @@ tvheadend.dvb_adapter = function(data)
return panel;
}
/**
*
*/
tvheadend.dvb = function()
{
var adapterSelection = new Ext.form.ComboBox({
loadingText: 'Loading...',
width: 300,
displayField:'name',
store: tvheadend.dvbAdapterStore,
mode: 'remote',
editable: false,
triggerAction: 'all',
emptyText: 'Select DVB adapter...'
});
var dummyadapter = new Ext.Panel({
region:'center', layout:'fit',
items:[{border: false}]
});
var panel = new Ext.Panel({
title: 'DVB Adapters',
iconCls: 'hardware',
layout:'fit',
tbar: [
adapterSelection, '->', {
text: 'Help',
handler: function() {
new tvheadend.help('DVB', 'config_dvb.html');
}
}
],
items: [
dummyadapter
]
});
adapterSelection.on('select', function(c, r) {
panel.remove(panel.getComponent(0));
panel.doLayout();
var newPanel = new tvheadend.dvb_adapter(r.data)
panel.add(newPanel);
panel.doLayout();
});
return panel;
}
/**
*
*/
tvheadend.showTransportDetails = function(data)
{
html = '';
html += '<div style="display:block;font-weight:bold;margin-bottom:4px">';
html += '<span style="float:left;width:100px">PID </span>';
html += '<span style="float:left;width:100px">Type</span>';
html += '<span>Details</span>';
html += '</div>';
for(i = 0; i < data.streams.length; i++) {
s = data.streams[i];
html += '<div style="display:block">';
html += '<span style="float:left;width:100px">' + s.pid + '</span>';
html += '<span style="float:left;width:100px">' + s.type + '</span>';
html += '<span>' + (s.details.length > 0 ? s.details : '&nbsp') + '</span>';
html += '</div>';
}
win = new Ext.Window({
title: 'Service details for ' + data.title,
layout: 'fit',
width: 400,
height: 400,
plain: true,
bodyStyle: 'padding: 5px',
html: html
});
win.show();
}

View file

@ -0,0 +1,133 @@
/**
* Datastore for adapters
*/
tvheadend.tvAdapterStore = new Ext.data.JsonStore({
root:'entries',
id: 'identifier',
fields: ['identifier',
'type',
'name',
'path',
'devicename',
'currentMux',
'services',
'muxes',
'initialMuxes',
'satConf',
'deliverySystem',
'freqMin',
'freqMax',
'freqStep',
'symrateMin',
'symrateMax'
],
url:'tv/adapter'
});
tvheadend.comet.on('tvAdapter', function(m) {
idx = tvheadend.tvAdapterStore.find('identifier', m.identifier);
if(idx == -1)
return;
r = tvheadend.tvAdapterStore.getAt(idx);
r.beginEdit();
for (key in m)
r.set(key, m[key]);
r.endEdit();
tvheadend.tvAdapterStore.commitChanges();
});
/**
*
*/
tvheadend.tvadapters = function()
{
var adapterSelection = new Ext.form.ComboBox({
loadingText: 'Loading...',
width: 300,
displayField:'name',
store: tvheadend.tvAdapterStore,
mode: 'remote',
editable: false,
triggerAction: 'all',
emptyText: 'Select TV adapter...'
});
var dummyadapter = new Ext.Panel({
region:'center', layout:'fit',
items:[{border: false}]
});
var panel = new Ext.Panel({
title: 'TV Adapters',
iconCls: 'hardware',
layout:'fit',
tbar: [
adapterSelection, '->', {
text: 'Help',
handler: function() {
new tvheadend.help('DVB', 'config_dvb.html');
}
}
],
items: [
dummyadapter
]
});
adapterSelection.on('select', function(c, r) {
panel.remove(panel.getComponent(0));
panel.doLayout();
if(r.data.type == 'dvb')
var newPanel = new tvheadend.dvb_adapter(r.data)
else
var newPanel = new tvheadend.v4l_adapter(r.data)
panel.add(newPanel);
panel.doLayout();
});
return panel;
}
/**
*
*/
tvheadend.showTransportDetails = function(data)
{
html = '';
html += '<div style="display:block;font-weight:bold;margin-bottom:4px">';
html += '<span style="float:left;width:100px">PID </span>';
html += '<span style="float:left;width:100px">Type</span>';
html += '<span>Details</span>';
html += '</div>';
for(i = 0; i < data.streams.length; i++) {
s = data.streams[i];
html += '<div style="display:block">';
html += '<span style="float:left;width:100px">' + s.pid + '</span>';
html += '<span style="float:left;width:100px">' + s.type + '</span>';
html += '<span>' + (s.details.length > 0 ? s.details : '&nbsp') + '</span>';
html += '</div>';
}
win = new Ext.Window({
title: 'Service details for ' + data.title,
layout: 'fit',
width: 400,
height: 400,
plain: true,
bodyStyle: 'padding: 5px',
html: html
});
win.show();
}

View file

@ -51,7 +51,7 @@ function accessUpdate(o) {
new tvheadend.xmltv,
new tvheadend.cteditor,
new tvheadend.dvrsettings,
new tvheadend.dvb,
new tvheadend.tvadapters,
new tvheadend.iptv,
new tvheadend.acleditor,
new tvheadend.cwceditor]

333
src/webui/static/app/v4l.js Normal file
View file

@ -0,0 +1,333 @@
/**
* V4L adapter details
*/
tvheadend.v4l_adapter_general = function(adapterData) {
adapterId = adapterData.identifier;
/* Conf panel */
var confreader = new Ext.data.JsonReader({
root: 'v4ladapters'
}, ['name', 'logging']);
function saveConfForm () {
confform.getForm().submit({
url:'v4l/adapter/' + adapterId,
params:{'op':'save'},
waitMsg:'Saving Data...'
});
}
var items = [
{
fieldLabel: 'Adapter name',
name: 'name',
width: 250
},
new Ext.form.Checkbox({
fieldLabel: 'Detailed logging',
name: 'logging'
})
];
var confform = new Ext.FormPanel({
title:'Adapter configuration',
columnWidth: .40,
frame:true,
border:true,
disabled:true,
style:'margin:10px',
bodyStyle:'padding:5px',
labelAlign: 'right',
labelWidth: 110,
waitMsgTarget: true,
reader: confreader,
defaultType: 'textfield',
items: items,
buttons: [{
text: 'Save',
handler: saveConfForm
}]
});
confform.getForm().load({
url:'v4l/adapter/' + adapterId,
params:{'op':'load'},
success:function(form, action) {
confform.enable();
}
});
/**
* Information / capabilities panel
*/
var infoTemplate = new Ext.XTemplate(
'<h2 style="font-size: 150%">Hardware</h2>' +
'<h3>Device path:</h3>{path}' +
'<h3>Device name:</h3>{devicename}' +
'<h2 style="font-size: 150%">Status</h2>' +
'<h3>Currently tuned to:</h3>{currentFrequency}&nbsp'
);
var infoPanel = new Ext.Panel({
title:'Information and capabilities',
columnWidth: .35,
frame:true,
border:true,
style:'margin:10px',
bodyStyle:'padding:5px',
html: infoTemplate.applyTemplate(adapterData)
});
/**
* Main adapter panel
*/
var panel = new Ext.Panel({
title: 'General',
layout:'column',
items: [confform, infoPanel]
});
/**
* Subscribe and react on updates for this adapter
*/
tvheadend.tvAdapterStore.on('update', function(s, r, o) {
if(r.data.identifier != adapterId)
return;
infoTemplate.overwrite(infoPanel.body, r.data);
});
return panel;
}
/**
* V4L service grid
*/
tvheadend.v4l_services = function(adapterId) {
var fm = Ext.form;
var enabledColumn = new Ext.grid.CheckColumn({
header: "Enabled",
dataIndex: 'enabled',
width: 45
});
var cm = new Ext.grid.ColumnModel([
enabledColumn,
{
header: "Channel name",
dataIndex: 'channelname',
width: 150,
renderer: function(value, metadata, record, row, col, store) {
return value ? value :
'<span class="tvh-grid-unset">Unmapped</span>';
},
editor: new fm.ComboBox({
store: tvheadend.channels,
allowBlank: true,
typeAhead: true,
minChars: 2,
lazyRender: true,
triggerAction: 'all',
mode: 'local',
displayField:'name'
})
},
{
header: "Frequency",
dataIndex: 'frequency',
width: 60,
editor: new fm.NumberField({
minValue: 10000,
maxValue: 1000000000
})
}
]);
cm.defaultSortable = true;
var rec = Ext.data.Record.create([
'id', 'enabled', 'channelname', 'frequency'
]);
var store = new Ext.data.JsonStore({
root: 'entries',
fields: rec,
url: "v4l/services/" + adapterId,
autoLoad: true,
id: 'id',
baseParams: {op: "get"},
listeners: {
'update': function(s, r, o) {
d = s.getModifiedRecords().length == 0
saveBtn.setDisabled(d);
rejectBtn.setDisabled(d);
}
}
});
function addRecord() {
Ext.Ajax.request({
url: "v4l/services/" + adapterId,
params: {
op:"create"
},
failure:function(response,options){
Ext.MessageBox.alert('Server Error',
'Unable to generate new record');
},
success:function(response,options){
var responseData = Ext.util.JSON.decode(response.responseText);
var p = new rec(responseData, responseData.id);
grid.stopEditing();
store.insert(0, p);
grid.startEditing(0, 0);
}
})
};
function delSelected() {
var selectedKeys = grid.selModel.selections.keys;
if(selectedKeys.length > 0) {
Ext.MessageBox.confirm('Message',
'Do you really want to delete selection?',
deleteRecord);
} else {
Ext.MessageBox.alert('Message',
'Please select at least one item to delete');
}
};
function deleteRecord(btn) {
if(btn=='yes') {
var selectedKeys = grid.selModel.selections.keys;
Ext.Ajax.request({
url: "v4l/services/" + adapterId,
params: {
op:"delete",
entries:Ext.encode(selectedKeys)
},
failure:function(response,options) {
Ext.MessageBox.alert('Server Error','Unable to delete');
},
success:function(response,options) {
store.reload();
}
})
}
}
function saveChanges() {
var mr = store.getModifiedRecords();
var out = new Array();
for (var x = 0; x < mr.length; x++) {
v = mr[x].getChanges();
out[x] = v;
out[x].id = mr[x].id;
}
Ext.Ajax.request({
url: "v4l/services/" + adapterId,
params: {
op:"update",
entries:Ext.encode(out)
},
success:function(response,options) {
store.commitChanges();
},
failure:function(response,options) {
Ext.MessageBox.alert('Message',response.statusText);
}
});
}
var delButton = new Ext.Toolbar.Button({
tooltip: 'Delete one or more selected rows',
iconCls:'remove',
text: 'Delete selected services',
handler: delSelected,
disabled: true
});
var saveBtn = new Ext.Toolbar.Button({
tooltip: 'Save any changes made (Changed cells have red borders).',
iconCls:'save',
text: "Save changes",
handler: saveChanges,
disabled: true
});
var rejectBtn = new Ext.Toolbar.Button({
tooltip: 'Revert any changes made (Changed cells have red borders).',
iconCls:'undo',
text: "Revert changes",
handler: function() {
store.rejectChanges();
},
disabled: true
});
var selModel = new Ext.grid.RowSelectionModel({
singleSelect:false
});
var grid = new Ext.grid.EditorGridPanel({
stripeRows: true,
title: 'Services',
plugins: [enabledColumn],
store: store,
clicksToEdit: 2,
cm: cm,
viewConfig: {forceFit:true},
selModel: selModel,
tbar: [{
tooltip: 'Create a new entry on the server. '+
'The new entry is initially disabled so it must be enabled '+
'before it start taking effect.',
iconCls:'add',
text: 'Add service',
handler: addRecord
}, '-', delButton, '-', saveBtn, rejectBtn]
});
store.on('update', function(s, r, o) {
d = s.getModifiedRecords().length == 0
saveBtn.setDisabled(d);
rejectBtn.setDisabled(d);
});
selModel.on('selectionchange', function(self) {
delButton.setDisabled(self.getCount() == 0);
});
return grid;
}
/**
*
*/
tvheadend.v4l_adapter = function(data)
{
var panel = new Ext.TabPanel({
border: false,
activeTab:0,
autoScroll:true,
items: [
new tvheadend.v4l_adapter_general(data),
new tvheadend.v4l_services(data.identifier)
]
});
return panel;
}