From 4dc2891a2c09f39a99980a826e90773fe66654d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96man?= Date: Wed, 9 Sep 2009 20:02:41 +0000 Subject: [PATCH] Add UI to configure V4L adapters. Not 100% done yet. Just moving code around. #102 --- src/dvb/dvb_adapter.c | 4 +- src/main.c | 4 +- src/tvhead.h | 2 +- src/v4l.c | 329 ++++++++++++++++++++++------ src/v4l.h | 56 +++++ src/webui/extjs.c | 252 ++++++++++++++++++++++ src/webui/static/app/comet.js | 2 +- src/webui/static/app/dvb.js | 130 +---------- src/webui/static/app/tvadapters.js | 133 ++++++++++++ src/webui/static/app/tvheadend.js | 2 +- src/webui/static/app/v4l.js | 333 +++++++++++++++++++++++++++++ 11 files changed, 1047 insertions(+), 200 deletions(-) create mode 100644 src/webui/static/app/tvadapters.js create mode 100644 src/webui/static/app/v4l.js diff --git a/src/dvb/dvb_adapter.c b/src/dvb/dvb_adapter.c index 0511c3d5..85695790 100644 --- a/src/dvb/dvb_adapter.c +++ b/src/dvb/dvb_adapter.c @@ -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)); } diff --git a/src/main.c b/src/main.c index e8ba6f6d..e7a99b24 100644 --- a/src/main.c +++ b/src/main.c @@ -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); diff --git a/src/tvhead.h b/src/tvhead.h index d651d3fc..e6a4bc10 100644 --- a/src/tvhead.h +++ b/src/tvhead.h @@ -553,7 +553,7 @@ typedef struct th_transport { */ struct v4l_adapter *tht_v4l_adapter; - + int tht_v4l_frequency; // In Hz /********************************************************* diff --git a/src/v4l.c b/src/v4l.c index 854b1ed7..bd57a066 100644 --- a/src/v4l.c +++ b/src/v4l.c @@ -31,42 +31,17 @@ #include #include -#define __user -#include +#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); } diff --git a/src/v4l.h b/src/v4l.h index a518bb51..87bea72d 100644 --- a/src/v4l.h +++ b/src/v4l.h @@ -19,6 +19,62 @@ #ifndef V4L_H_ #define V4L_H_ +#define __user +#include + +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 */ diff --git a/src/webui/extjs.c b/src/webui/extjs.c index 71058c42..4b95068d 100644 --- a/src/webui/extjs.c +++ b/src/webui/extjs.c @@ -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); + } diff --git a/src/webui/static/app/comet.js b/src/webui/static/app/comet.js index 989b3826..c7305289 100644 --- a/src/webui/static/app/comet.js +++ b/src/webui/static/app/comet.js @@ -4,7 +4,7 @@ Ext.extend(tvheadend.Comet = function() { this.addEvents({ accessUpdate: true, - dvbAdapter: true, + tvAdapter: true, dvbMux: true, dvbStore: true, dvbSatConf: true, diff --git a/src/webui/static/app/dvb.js b/src/webui/static/app/dvb.js index e952e286..ee3d5361 100644 --- a/src/webui/static/app/dvb.js +++ b/src/webui/static/app/dvb.js @@ -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 += '
'; - html += 'PID '; - html += 'Type'; - html += 'Details'; - html += '
'; - - for(i = 0; i < data.streams.length; i++) { - s = data.streams[i]; - - html += '
'; - html += '' + s.pid + ''; - html += '' + s.type + ''; - html += '' + (s.details.length > 0 ? s.details : ' ') + ''; - html += '
'; - } - - 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(); -} diff --git a/src/webui/static/app/tvadapters.js b/src/webui/static/app/tvadapters.js new file mode 100644 index 00000000..be024de9 --- /dev/null +++ b/src/webui/static/app/tvadapters.js @@ -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 += '
'; + html += 'PID '; + html += 'Type'; + html += 'Details'; + html += '
'; + + for(i = 0; i < data.streams.length; i++) { + s = data.streams[i]; + + html += '
'; + html += '' + s.pid + ''; + html += '' + s.type + ''; + html += '' + (s.details.length > 0 ? s.details : ' ') + ''; + html += '
'; + } + + 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(); +} diff --git a/src/webui/static/app/tvheadend.js b/src/webui/static/app/tvheadend.js index 497f740a..2fcd3327 100644 --- a/src/webui/static/app/tvheadend.js +++ b/src/webui/static/app/tvheadend.js @@ -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] diff --git a/src/webui/static/app/v4l.js b/src/webui/static/app/v4l.js new file mode 100644 index 00000000..388e2cbf --- /dev/null +++ b/src/webui/static/app/v4l.js @@ -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( + '

Hardware

' + + '

Device path:

{path}' + + '

Device name:

{devicename}' + + '

Status

' + + '

Currently tuned to:

{currentFrequency} ' + ); + + + 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 : + 'Unmapped'; + }, + 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; +}