]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
bouquets: add auto-map, auto-remove features
authorJaroslav Kysela <perex@perex.cz>
Fri, 31 Oct 2014 22:25:56 +0000 (23:25 +0100)
committerJaroslav Kysela <perex@perex.cz>
Sat, 8 Nov 2014 20:05:37 +0000 (21:05 +0100)
12 files changed:
src/api/api_bouquet.c
src/bouquet.c
src/bouquet.h
src/channels.c
src/channels.h
src/input/mpegts.h
src/input/mpegts/dvb_psi.c
src/service.h
src/service_mapper.c
src/service_mapper.h
src/webui/static/app/cteditor.js
src/webui/static/app/idnode.js

index bd3e5e9eefd7fa8429d7283505c480d017994fb3..0653435c39833b5dd7133b0ac1802ee6273a9e41 100644 (file)
 #include "access.h"
 #include "api.h"
 
+static int
+api_bouquet_list
+  ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
+{
+  bouquet_t *bq;
+  htsmsg_t *l, *e;
+
+  l = htsmsg_create_list();
+  pthread_mutex_lock(&global_lock);
+  RB_FOREACH(bq, &bouquets, bq_link) {
+    e = htsmsg_create_map();
+    htsmsg_add_str(e, "key", idnode_uuid_as_str(&bq->bq_id));
+    htsmsg_add_str(e, "val", bq->bq_name ?: "");
+    htsmsg_add_msg(l, NULL, e);
+  }
+  pthread_mutex_unlock(&global_lock);
+  *resp = htsmsg_create_map();
+  htsmsg_add_msg(*resp, "entries", l);
+
+  return 0;
+}
+
 static void
 api_bouquet_grid
   ( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf )
@@ -57,6 +79,7 @@ api_bouquet_create
 void api_bouquet_init ( void )
 {
   static api_hook_t ah[] = {
+    { "bouquet/list",    ACCESS_ADMIN, api_bouquet_list, NULL },
     { "bouquet/class",   ACCESS_ADMIN, api_idnode_class, (void*)&bouquet_class },
     { "bouquet/grid",    ACCESS_ADMIN, api_idnode_grid,  api_bouquet_grid },
     { "bouquet/create",  ACCESS_ADMIN, api_bouquet_create, NULL },
index 0881f91bf1dab72c054dc62a5c43d2e336d90248..adb779ac40a760d524a436aebf4a7732a6376a2b 100644 (file)
@@ -21,6 +21,8 @@
 #include "access.h"
 #include "bouquet.h"
 #include "service.h"
+#include "channels.h"
+#include "service_mapper.h"
 
 bouquet_tree_t bouquets;
 
@@ -47,6 +49,7 @@ bouquet_create(const char *uuid, htsmsg_t *conf,
 
   bq = calloc(1, sizeof(bouquet_t));
   bq->bq_services = idnode_set_create();
+  bq->bq_active_services = idnode_set_create();
 
   if (idnode_insert(&bq->bq_id, uuid, &bouquet_class, 0)) {
     if (uuid)
@@ -93,6 +96,7 @@ bouquet_destroy(bouquet_t *bq)
   RB_REMOVE(&bouquets, bq, bq_link);
   idnode_unlink(&bq->bq_id);
 
+  idnode_set_free(bq->bq_active_services);
   idnode_set_free(bq->bq_services);
   free(bq->bq_name);
   free(bq->bq_src);
@@ -136,6 +140,21 @@ bouquet_find_by_source(const char *name, const char *src, int create)
   return NULL;
 }
 
+/*
+ *
+ */
+static void
+bouquet_map_channel(bouquet_t *bq, service_t *t)
+{
+  channel_service_mapping_t *csm;
+
+  LIST_FOREACH(csm, &t->s_channels, csm_svc_link)
+    if (csm->csm_chn->ch_bouquet == bq)
+      break;
+  if (!csm)
+    service_mapper_process(t, bq);
+}
+
 /*
  *
  */
@@ -145,8 +164,88 @@ bouquet_add_service(bouquet_t *bq, service_t *s)
   lock_assert(&global_lock);
 
   if (!idnode_set_exists(bq->bq_services, &s->s_id)) {
+    tvhtrace("bouquet", "add service %s to %s", s->s_nicename, bq->bq_name ?: "<unknown>");
     idnode_set_add(bq->bq_services, &s->s_id, NULL);
     bq->bq_saveflag = 1;
+    if (bq->bq_enabled && bq->bq_maptoch)
+      bouquet_map_channel(bq, s);
+  }
+  if (!bq->bq_in_load &&
+      !idnode_set_exists(bq->bq_active_services, &s->s_id))
+    idnode_set_add(bq->bq_active_services, &s->s_id, NULL);
+}
+
+/*
+ *
+ */
+static void
+bouquet_unmap_channel(bouquet_t *bq, service_t *t)
+{
+  channel_service_mapping_t *csm, *csm_next;
+
+  csm = LIST_FIRST(&t->s_channels);
+  while (csm) {
+    csm_next = LIST_NEXT(csm, csm_svc_link);
+    if (csm->csm_chn->ch_bouquet == bq) {
+      tvhinfo("bouquet", "%s / %s: unmapped from %s",
+              channel_get_name(csm->csm_chn), t->s_nicename,
+              bq->bq_name ?: "<unknown>");
+      channel_delete(csm->csm_chn, 1);
+    }
+    csm = csm_next;
+  }
+}
+
+/*
+ *
+ */
+static void
+bouquet_remove_service(bouquet_t *bq, service_t *s)
+{
+  tvhtrace("bouquet", "remove service %s from %s", s->s_nicename, bq->bq_name ?: "<unknown>");
+  idnode_set_remove(bq->bq_services, &s->s_id);
+}
+
+/*
+ *
+ */
+void
+bouquet_completed(bouquet_t *bq)
+{
+  idnode_set_t *remove;
+  size_t z;
+
+  tvhtrace("bouquet", "completed: active=%zi old=%zi",
+            bq->bq_active_services->is_count, bq->bq_services->is_count);
+
+  remove = idnode_set_create();
+  for (z = 0; z < bq->bq_services->is_count; z++)
+    if (!idnode_set_exists(bq->bq_active_services, bq->bq_services->is_array[z]))
+      idnode_set_add(remove, bq->bq_services->is_array[z], NULL);
+  for (z = 0; z < remove->is_count; z++)
+    bouquet_remove_service(bq, (service_t *)remove->is_array[z]);
+  idnode_set_free(remove);
+
+  idnode_set_free(bq->bq_active_services);
+  bq->bq_active_services = idnode_set_create();
+}
+
+/*
+ *
+ */
+void
+bouquet_map_to_channels(bouquet_t *bq)
+{
+  service_t *t;
+  size_t z;
+
+  for (z = 0; z < bq->bq_services->is_count; z++) {
+    t = (service_t *)bq->bq_services->is_array[z];
+    if (bq->bq_enabled && bq->bq_maptoch) {
+      bouquet_map_channel(bq, t);
+    } else {
+      bouquet_unmap_channel(bq, t);
+    }
   }
 }
 
@@ -202,20 +301,27 @@ bouquet_class_get_title (idnode_t *self)
   return bq->bq_name ?: "";
 }
 
+/* exported for others */
+htsmsg_t *
+bouquet_class_get_list(void *o)
+{
+  htsmsg_t *m = htsmsg_create_map();
+  htsmsg_add_str(m, "type",  "api");
+  htsmsg_add_str(m, "uri",   "bouquet/list");
+  htsmsg_add_str(m, "event", "bouquet");
+  return m;
+}
+
 static void
 bouquet_class_enabled_notify ( void *obj )
 {
-  bouquet_t *bq = obj;
-  service_t *s;
-  size_t z;
+  bouquet_map_to_channels((bouquet_t *)obj);
+}
 
-  if (!bq->bq_enabled) {
-    for (z = 0; z < bq->bq_services->is_count; z++) {
-      s = (service_t *)bq->bq_services->is_array[z];
-      if (s->s_master_bouquet == bq)
-        s->s_master_bouquet = NULL;
-    }
-  }
+static void
+bouquet_class_maptoch_notify ( void *obj )
+{
+  bouquet_map_to_channels((bouquet_t *)obj);
 }
 
 static const void *
@@ -280,6 +386,13 @@ const idclass_t bouquet_class = {
       .off      = offsetof(bouquet_t, bq_enabled),
       .notify   = bouquet_class_enabled_notify,
     },
+    {
+      .type     = PT_BOOL,
+      .id       = "maptoch",
+      .name     = "Auto-Map to Channels",
+      .off      = offsetof(bouquet_t, bq_maptoch),
+      .notify   = bouquet_class_maptoch_notify,
+    },
     {
       .type     = PT_STR,
       .id       = "name",
@@ -334,6 +447,7 @@ bouquet_init(void)
 {
   htsmsg_t *c, *m;
   htsmsg_field_t *f;
+  bouquet_t *bq;
 
   RB_INIT(&bouquets);
 
@@ -341,7 +455,8 @@ bouquet_init(void)
   if ((c = hts_settings_load("bouquet")) != NULL) {
     HTSMSG_FOREACH(f, c) {
       if (!(m = htsmsg_field_get_map(f))) continue;
-      (void)bouquet_create(f->hmf_name, m, NULL, NULL);
+      bq = bouquet_create(f->hmf_name, m, NULL, NULL);
+      bq->bq_saveflag = 0;
     }
     htsmsg_destroy(c);
   }
index 2e2537a502e402bb832b1700fe73c2b0d2b23204..fa577e60ececccbcacaedbb254f7c9029d083bae 100644 (file)
@@ -29,13 +29,16 @@ typedef struct bouquet {
 
   int           bq_saveflag;
   int           bq_in_load;
+  time_t        bq_updated;
 
   int           bq_shield;
   int           bq_enabled;
+  int           bq_maptoch;
   char         *bq_name;
   char         *bq_src;
   char         *bq_comment;
   idnode_set_t *bq_services;
+  idnode_set_t *bq_active_services;
   htsmsg_t     *bq_services_waiting;
   uint32_t      bq_lcn_offset;
 
@@ -50,37 +53,30 @@ extern const idclass_t bouquet_class;
 /**
  *
  */
-bouquet_t *
-bouquet_create(const char *uuid, htsmsg_t *conf,
-               const char *name, const char *src);
 
-/**
- *
- */
-void
-bouquet_destroy_by_service(service_t *t);
+htsmsg_t * bouquet_class_get_list(void *o);
 
-/**
- *
- */
-bouquet_t *
-bouquet_find_by_source(const char *name, const char *src, int create);
+bouquet_t * bouquet_create(const char *uuid, htsmsg_t *conf,
+                           const char *name, const char *src);
 
-/**
- *
- */
-void
-bouquet_add_service(bouquet_t *bq, service_t *s);
+void bouquet_destroy_by_service(service_t *t);
 
-/**
- *
- */
-void
-bouquet_save(bouquet_t *bq, int notify);
+static inline bouquet_t *
+bouquet_find_by_uuid(const char *uuid)
+  { return (bouquet_t *)idnode_find(uuid, &bouquet_class, NULL); }
+
+bouquet_t * bouquet_find_by_source(const char *name, const char *src, int create);
+
+void bouquet_map_to_channels(bouquet_t *bq);
+void bouquet_add_service(bouquet_t *bq, service_t *s);
+void bouquet_completed(bouquet_t *bq);
+
+void bouquet_save(bouquet_t *bq, int notify);
 
 /**
  *
  */
+
 void bouquet_init(void);
 void bouquet_service_resolve(void);
 void bouquet_done(void);
index aa555c1fca75dbf292566e380a8468f29be167bd..8d9a7be2c50bf1a458772527cbbab3e10c95eab9 100644 (file)
@@ -42,6 +42,7 @@
 #include "imagecache.h"
 #include "service_mapper.h"
 #include "htsbuf.h"
+#include "bouquet.h"
 #include "intlconv.h"
 
 struct channel_tree channels;
@@ -273,6 +274,33 @@ channel_class_epggrab_list ( void *o )
   return m;
 }
 
+static const void *
+channel_class_bouquet_get ( void *o )
+{
+  static const char *sbuf;
+  channel_t *ch = o;
+  if (ch->ch_bouquet)
+    sbuf = idnode_uuid_as_str(&ch->ch_bouquet->bq_id);
+  else
+    sbuf = "";
+  return &sbuf;
+}
+
+static int
+channel_class_bouquet_set ( void *o, const void *v )
+{
+  channel_t *ch = o;
+  bouquet_t *bq = bouquet_find_by_uuid(v);
+  if (bq == NULL && ch->ch_bouquet) {
+    ch->ch_bouquet = NULL;
+    return 1;
+  } else if (bq != ch->ch_bouquet) {
+    ch->ch_bouquet = bq;
+    return 1;
+  }
+  return 0;
+}
+
 const idclass_t channel_class = {
   .ic_class      = "channel",
   .ic_caption    = "Channel",
@@ -363,6 +391,15 @@ const idclass_t channel_class = {
       .list     = channel_tag_class_get_list,
       .rend     = channel_class_tags_rend
     },
+    {
+      .type     = PT_STR,
+      .id       = "bouquet",
+      .name     = "Bouquet (auto)",
+      .get      = channel_class_bouquet_get,
+      .set      = channel_class_bouquet_set,
+      .list     = bouquet_class_get_list,
+      .opts     = PO_RDONLY
+    },
     {}
   }
 };
@@ -527,12 +564,20 @@ channel_get_name ( channel_t *ch )
 int64_t
 channel_get_number ( channel_t *ch )
 {
-  int n;
+  int64_t n = 0;
   channel_service_mapping_t *csm;
-  if (ch->ch_number) return ch->ch_number;
-  LIST_FOREACH(csm, &ch->ch_services, csm_chn_link)
-    if ((n = service_get_channel_number(csm->csm_svc)))
-      return n;
+  if (ch->ch_number) {
+    n = ch->ch_number;
+  } else {
+    LIST_FOREACH(csm, &ch->ch_services, csm_chn_link)
+      if ((n = service_get_channel_number(csm->csm_svc)))
+        break;
+  }
+  if (n) {
+    if (ch->ch_bouquet)
+      n += (int64_t)ch->ch_bouquet->bq_lcn_offset * CHANNEL_SPLIT;
+    return n;
+  }
   return 0;
 }
 
index fbc1e773d2f327907d7ca6929a12170cb6fb2cbf..a1fd54e32cbc55901e67a916f357e90b91cd72c9 100644 (file)
@@ -23,6 +23,7 @@
 #include "idnode.h"
 
 struct access;
+struct bouquet;
 
 RB_HEAD(channel_tree, channel);
 
@@ -52,6 +53,7 @@ typedef struct channel
   int64_t ch_number;
   char   *ch_icon;
   struct  channel_tag_mapping_list ch_ctms;
+  struct bouquet *ch_bouquet;
 
   /* Service/subscriptions */
   LIST_HEAD(, channel_service_mapping) ch_services;
index 57a5c0470d85f9992d0bba4adba616c7e055fa85..bdc9114aa095e9212ef19db3a9ff30bc171e89ef 100644 (file)
@@ -111,6 +111,7 @@ typedef struct mpegts_table_state
   int      version;
   int      complete;
   uint32_t sections[8];
+  void    *bouquet;
   RB_ENTRY(mpegts_table_state)   link;
 } mpegts_table_state_t;
 
index 8f8a7abda8a09a8a0e0db5428afa4b68a94fbfd2..caa3b1614e1abd27e38242c11b880f91eb529062 100644 (file)
@@ -346,7 +346,7 @@ dvb_desc_service_list
 
 static int
 dvb_desc_local_channel
-  ( const char *dstr, const uint8_t *ptr, int len, mpegts_mux_t *mm, bouquet_t *bq )
+  ( const char *dstr, const uint8_t *ptr, int len, mpegts_mux_t *mm )
 {
   int save = 0;
   uint16_t sid, lcn;
@@ -358,11 +358,6 @@ dvb_desc_local_channel
     if (lcn && mm) {
       mpegts_service_t *s = mpegts_service_find(mm, sid, 0, 0, &save);
       if (s) {
-        if (bq && bq->bq_lcn_offset &&
-            (!s->s_master_bouquet || s->s_master_bouquet == bq)) {
-          lcn += bq->bq_lcn_offset;
-          s->s_master_bouquet = bq;
-        }
         if (s->s_dvb_channel_num != lcn) {
           s->s_dvb_channel_num = lcn;
           s->s_config_save((service_t*)s);
@@ -740,6 +735,13 @@ dvb_nit_callback
   if (tableid != 0x40 && tableid != 0x41 && tableid != 0x4A && tableid != 0xBC)
     return -1;
   r = dvb_table_begin(mt, ptr, len, tableid, nbid, 7, &st, &sect, &last, &ver);
+  if (r == 0) {
+    if (tableid != 0xBC /* fastscan */) {
+      RB_FOREACH(st, &mt->mt_state, link)
+        if (st->bouquet)
+          bouquet_completed((bouquet_t *)st->bouquet);
+    }
+  }
   if (r != 1) return r;
 
   /* NIT */
@@ -805,9 +807,9 @@ dvb_nit_callback
 
 #if ENABLE_MPEGTS_DVB
     dauth[0] = 0;
-    if (mux && *name && tableid == 0x4A /* BAT */) {
-      if (idnode_is_instance(&mux->mm_id, &dvb_mux_dvbs_class)) {
-        dvb_mux_conf_t *mc = &((dvb_mux_t *)mux)->lm_tuning;
+    if (!bq && *name && tableid == 0x4A /* BAT */) {
+      if (idnode_is_instance(&mm->mm_id, &dvb_mux_dvbs_class)) {
+        dvb_mux_conf_t *mc = &((dvb_mux_t *)mm)->lm_tuning;
         if (mc->u.dmc_fe_qpsk.orbital_dir) {
           int pos = mc->u.dmc_fe_qpsk.orbital_pos;
           if (mc->u.dmc_fe_qpsk.orbital_dir == 'W')
@@ -825,6 +827,7 @@ dvb_nit_callback
       if (bq2 != bq && bq && bq->bq_saveflag)
         bouquet_save(bq, 1);
       bq = bq2;
+      st->bouquet = bq;
     }
 #endif
 
@@ -881,7 +884,7 @@ dvb_nit_callback
             mpegts_mux_set_crid_authority(mux, dauth);
           break;
         case DVB_DESC_LOCAL_CHAN:
-          if (dvb_desc_local_channel(mt->mt_name, dptr, dlen, mux, bq))
+          if (dvb_desc_local_channel(mt->mt_name, dptr, dlen, mux))
             return -1;
           break;
         case DVB_DESC_SERVICE_LIST:
@@ -1170,6 +1173,7 @@ dvb_fs_sdt_callback
   if (tableid != 0xBD)
     return -1;
   r = dvb_table_begin(mt, ptr, len, tableid, nbid, 7, &st, &sect, &last, &ver);
+  if (r == 0) bouquet_completed(bq);
   if (r != 1) return r;
   if (len < 5) return -1;
   ptr += 5;
@@ -1216,7 +1220,7 @@ dvb_fs_sdt_callback
           LIST_FOREACH(mux, &mn->mn_muxes, mm_network_link)
             if (mux->mm_onid == onid && mux->mm_tsid == tsid)
               break;
-          if (dvb_desc_local_channel(mt->mt_name, dptr, dlen, mux, bq))
+          if (dvb_desc_local_channel(mt->mt_name, dptr, dlen, mux))
             return -1;
           break;
         case DVB_DESC_SAT_DEL:
index 226a6251ed3f2153ef352b1ce6881307f4138154..3097cb305cc2305b43c2d5167a250de18f33e52c 100644 (file)
@@ -443,11 +443,6 @@ typedef struct service {
 
   int64_t s_current_pts;
 
-  /*
-   *
-   */
-  void *s_master_bouquet;
-
 } service_t;
 
 
index c408fdaf0c1d4653c45992cb548ae4a1e1c47dfd..4f8f46a9a9cea2cf7a47564218f401ef9ce02b43 100644 (file)
@@ -31,6 +31,7 @@
 #include "streaming.h"
 #include "service.h"
 #include "profile.h"
+#include "bouquet.h"
 #include "api.h"
 
 static service_mapper_status_t service_mapper_stat; 
@@ -38,7 +39,6 @@ static pthread_cond_t          service_mapper_cond;
 static struct service_queue    service_mapper_queue;
 static service_mapper_conf_t   service_mapper_conf;
 
-static void service_mapper_process ( service_t *s );
 static void *service_mapper_thread ( void *p );
 
 /**
@@ -80,7 +80,8 @@ service_mapper_start ( const service_mapper_conf_t *conf, htsmsg_t *uuids )
   service_t *s;
 
   /* Reset stat counters */
-  service_mapper_reset_stats();
+  if (TAILQ_EMPTY(&service_mapper_queue))
+    service_mapper_reset_stats();
 
   /* Store config */
   service_mapper_conf = *conf;
@@ -135,7 +136,7 @@ service_mapper_start ( const service_mapper_conf_t *conf, htsmsg_t *uuids )
     /* Process */
     } else {
       tvhtrace("service_mapper", "  process");
-      service_mapper_process(s);
+      service_mapper_process(s, NULL);
     }
   }
   
@@ -264,29 +265,32 @@ service_mapper_clean ( service_t *s, channel_t *c, void *origin )
  * Process a service 
  */
 void
-service_mapper_process ( service_t *s )
+service_mapper_process ( service_t *s, bouquet_t *bq )
 {
   channel_t *chn = NULL;
   const char *name;
 
   /* Ignore */
   if (s->s_status == SERVICE_ZOMBIE) {
-    service_mapper_stat.ignore++;
+    if (!bq)
+      service_mapper_stat.ignore++;
     goto exit;
   }
 
   /* Safety check (in-case something has been mapped manually in the interim) */
-  if (LIST_FIRST(&s->s_channels)) {
+  if (!bq && LIST_FIRST(&s->s_channels)) {
     service_mapper_stat.ignore++;
     goto exit;
   }
 
   /* Find existing channel */
   name = service_get_channel_name(s);
-  if (service_mapper_conf.merge_same_name && name && *name)
+  if (!bq && service_mapper_conf.merge_same_name && name && *name)
     chn = channel_find_by_name(name);
-  if (!chn)
+  if (!chn) {
     chn = channel_create(NULL, NULL, NULL);
+    chn->ch_bouquet = bq;
+  }
     
   /* Map */
   if (chn) {
@@ -313,9 +317,12 @@ service_mapper_process ( service_t *s )
     idnode_notify_simple(&chn->ch_id);
     channel_save(chn);
   }
-  service_mapper_stat.ok++;
-
-  tvhinfo("service_mapper", "%s: success", s->s_nicename);
+  if (!bq) {
+    service_mapper_stat.ok++;
+    tvhinfo("service_mapper", "%s: success", s->s_nicename);
+  } else {
+    tvhinfo("bouquet", "%s: mapped service from %s", s->s_nicename, bq->bq_name ?: "<unknown>");
+  }
 
   /* Remove */
 exit:
@@ -430,7 +437,7 @@ service_mapper_thread ( void *aux )
       tvhinfo("service_mapper", "%s: failed [err %s]", s->s_nicename, err);
       service_mapper_stat.fail++;
     } else
-      service_mapper_process(s);
+      service_mapper_process(s, NULL);
 
     service_unref(s);
     service_mapper_stat.active = NULL;
index 0d974f702a1c4275c09fcfb8d1d03832c9e26a96..c7e94ffa1eccae9ae11a8d509be2f6b26e443b90 100644 (file)
@@ -19,6 +19,8 @@
 #ifndef __TVH_SERVICE_MAPPER_H__
 #define __TVH_SERVICE_MAPPER_H__
 
+struct bouquet;
+
 typedef struct service_mapper_conf
 {
   int check_availability; ///< Check service is receivable
@@ -72,6 +74,9 @@ void service_mapper_unlink ( struct service *s, struct channel *c, void *origin
  */
 int service_mapper_clean ( struct service *s, struct channel *ch, void *origin );
 
+// Process one service
+void service_mapper_process ( struct service *s, struct bouquet *bq );
+
 // Resets the stat counters
 void service_mapper_reset_stats ( void );
 
index 89560c34d2196c5734f9c45847608ed797bad0a8..bbba03923b519a710b40c50b5e5bb7b24e55cb1f 100644 (file)
@@ -32,7 +32,7 @@ tvheadend.cteditor = function(panel, index)
  */
 tvheadend.bouquet = function(panel, index)
 {
-    var list = 'enabled,name,source,services_count,comment,lcn_off';
+    var list = 'enabled,name,maptoch,source,services_count,comment,lcn_off';
 
     tvheadend.idnode_grid(panel, {
         url: 'api/bouquet',
index fb6e018c001ba86d0dbf10dff3bd1b8ec30c924d..2ffd8160b8e79b49d6c8ef898adabacf5301c859 100644 (file)
@@ -383,6 +383,7 @@ tvheadend.IdNodeField = function(conf)
                 case 'u32':
                 case 'u16':
                 case 's32':
+                case 's64':
                 case 'dbl':
                 case 'time':
                     if (this.hexa) {