]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
Add support for IPTV
authorAndreas Öman <andreas@lonelycoder.com>
Sat, 15 Aug 2009 21:45:34 +0000 (21:45 +0000)
committerAndreas Öman <andreas@lonelycoder.com>
Sat, 15 Aug 2009 21:45:34 +0000 (21:45 +0000)
20 files changed:
Makefile
debian/changelog
src/channels.c
src/dvb/dvb_adapter.c
src/dvb/dvb_tables.c
src/dvb/dvb_transport.c
src/iptv_input.c
src/iptv_input.h
src/main.c
src/psi.c
src/rawtsinput.c
src/subscriptions.c
src/transports.c
src/transports.h
src/tvhead.h
src/webui/extjs.c
src/webui/static/app/ext.css
src/webui/static/app/iptv.js [new file with mode: 0644]
src/webui/static/app/tvheadend.js
src/webui/static/icons/world.png [new file with mode: 0644]

index b099119dc7b0e6f9c281806a33939d6a052d3a4b..6b6a553f2109333834b880e216715661d5ad3c49 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -65,6 +65,7 @@ SRCS =  src/main.c \
        src/avg.c \
        src/htsstr.c \
        src/rawtsinput.c \
+       src/iptv_input.c \
 
 SRCS += src/dvr/dvr_db.c \
        src/dvr/dvr_rec.c \
index 92fd9260fb32817cce9695f31a3af448e2768676..7737bec818273d3455ab3c1c1fa7e54e3df784d3 100644 (file)
@@ -8,6 +8,8 @@ hts-tvheadend (2.5) hts; urgency=low
 
   * The HTSP service is now announced via AVAHI (mDNS service discovery)
 
+  * Support for IPTV has been added.
+
 hts-tvheadend (2.4) hts; urgency=low
 
   * Due to a bug, the polarisation of DVB-S muxes was not correctly
index c2153b3a2c3e49dae9a58d0a903cb3495065e46e..f8cfaaa0daab956056670131e98e57750883be19 100644 (file)
@@ -353,11 +353,8 @@ channel_rename(channel_t *ch, const char *newname)
   RB_REMOVE(&channel_name_tree, ch, ch_name_link);
   channel_set_name(ch, newname);
 
-  LIST_FOREACH(t, &ch->ch_transports, tht_ch_link) {
-    pthread_mutex_lock(&t->tht_stream_mutex);
-    t->tht_config_change(t);
-    pthread_mutex_unlock(&t->tht_stream_mutex);
-  }
+  LIST_FOREACH(t, &ch->ch_transports, tht_ch_link)
+    t->tht_config_save(t);
 
   channel_save(ch);
   htsp_channel_update(ch);
index d1ad49fac70ff038749bba5b6e92c57c30ce1d34..61aabf74d7ba0347bc8eab8fcb3bf5395afa2454 100644 (file)
@@ -381,7 +381,7 @@ dvb_adapter_clone(th_dvb_adapter_t *dst, th_dvb_adapter_t *src)
 
     assert(tdmi_dst != NULL);
 
-    LIST_FOREACH(t_src, &tdmi_src->tdmi_transports, tht_mux_link) {
+    LIST_FOREACH(t_src, &tdmi_src->tdmi_transports, tht_group_link) {
       t_dst = dvb_transport_find(tdmi_dst, 
                                 t_src->tht_dvb_service_id,
                                 t_src->tht_pmt_pid, NULL);
@@ -415,8 +415,8 @@ dvb_adapter_clone(th_dvb_adapter_t *dst, th_dvb_adapter_t *src)
        st_dst->st_caid           = st_src->st_caid;
       }
 
-      t_dst->tht_config_change(t_dst); // Save config
       pthread_mutex_unlock(&t_src->tht_stream_mutex);
+      t_dst->tht_config_save(t_dst); // Save config
 
     }
     dvb_mux_save(tdmi_dst);
@@ -527,7 +527,7 @@ dvb_adapter_build_msg(th_dvb_adapter_t *tda)
   // XXX: bad bad bad slow slow slow
   LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) {
     nummux++;
-    LIST_FOREACH(t, &tdmi->tdmi_transports, tht_mux_link) {
+    LIST_FOREACH(t, &tdmi->tdmi_transports, tht_group_link) {
       numsvc++;
     }
   }
index fdd348e0b2db2a0a7a8577bf18a062d732760002..63f9fbe8b26230f857458ef007a8b86e393cc608 100644 (file)
@@ -677,9 +677,7 @@ dvb_sdt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
            free((void *)t->tht_svcname);
            t->tht_svcname = strdup(chname);
            
-           pthread_mutex_lock(&t->tht_stream_mutex);
-           t->tht_config_change(t);
-           pthread_mutex_unlock(&t->tht_stream_mutex);
+           t->tht_config_save(t);
          }
        }
        break;
index ca7720e54f3db4393acac6c12a927955ff64f2e7..be1856a3c4313b7699af1e20201d0edc7abbe9db 100644 (file)
@@ -66,7 +66,8 @@ dvb_transport_open_demuxers(th_dvb_adapter_t *tda, th_transport_t *t)
       st->st_demuxer_fd = -1;
       tvhlog(LOG_ERR, "dvb",
             "\"%s\" unable to open demuxer \"%s\" for pid %d -- %s",
-            t->tht_name, tda->tda_demux_path, st->st_pid, strerror(errno));
+            t->tht_identifier, tda->tda_demux_path, 
+            st->st_pid, strerror(errno));
       continue;
     }
 
@@ -80,7 +81,8 @@ dvb_transport_open_demuxers(th_dvb_adapter_t *tda, th_transport_t *t)
     if(ioctl(fd, DMX_SET_PES_FILTER, &dmx_param)) {
       tvhlog(LOG_ERR, "dvb",
             "\"%s\" unable to configure demuxer \"%s\" for pid %d -- %s",
-            t->tht_name, tda->tda_demux_path, st->st_pid, strerror(errno));
+            t->tht_identifier, tda->tda_demux_path, 
+            st->st_pid, strerror(errno));
       close(fd);
       fd = -1;
     }
@@ -258,7 +260,9 @@ dvb_transport_save(th_transport_t *t)
     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, "dvbtransports/%s/%s",
                    t->tht_dvb_mux_instance->tdmi_identifier,
@@ -334,7 +338,7 @@ dvb_transport_find(th_dvb_mux_instance_t *tdmi, uint16_t sid, int pmt_pid,
 
   lock_assert(&global_lock);
 
-  LIST_FOREACH(t, &tdmi->tdmi_transports, tht_mux_link) {
+  LIST_FOREACH(t, &tdmi->tdmi_transports, tht_group_link) {
     if(t->tht_dvb_service_id == sid)
       return t;
   }
@@ -359,12 +363,12 @@ dvb_transport_find(th_dvb_mux_instance_t *tdmi, uint16_t sid, int pmt_pid,
   t->tht_start_feed = dvb_transport_start;
   t->tht_refresh_feed = dvb_transport_refresh;
   t->tht_stop_feed  = dvb_transport_stop;
-  t->tht_config_change = dvb_transport_save;
+  t->tht_config_save = dvb_transport_save;
   t->tht_sourceinfo = dvb_transport_sourceinfo;
   t->tht_dvb_mux_instance = tdmi;
   t->tht_quality_index = dvb_transport_quality;
 
-  LIST_INSERT_HEAD(&tdmi->tdmi_transports, t, tht_mux_link);
+  LIST_INSERT_HEAD(&tdmi->tdmi_transports, t, tht_group_link);
 
   dvb_adapter_notify(tdmi->tdmi_adapter);
   return t;
index e757179fae887bb7cb2d5ecf3a3fb745b89a4f5c..221d8b0662d84bba75c07039156633964b46858e 100644 (file)
 #include <pthread.h>
 
 #include <sys/types.h>
-#include <sys/stat.h>
 #include <sys/ioctl.h>
+#include <sys/epoll.h>
 #include <fcntl.h>
+#include <assert.h>
 
 #include <stdio.h>
 #include <unistd.h>
 #include <arpa/inet.h>
 #include <errno.h>
 #include <linux/netdevice.h>
-#include <syslog.h>
 
-#include <libhts/htscfg.h>
+#include <libavutil/avstring.h>
 
 #include "tvhead.h"
-#include "iptv_input.h"
+#include "htsmsg.h"
 #include "channels.h"
 #include "transports.h"
-#include "dispatch.h"
-#include "psi.h"
+#include "iptv_input.h"
 #include "tsdemux.h"
+#include "psi.h"
+#include "settings.h"
+
+static int iptv_thread_running;
+static int iptv_epollfd;
+static pthread_mutex_t iptv_recvmutex;
+
+struct th_transport_list iptv_all_transports; /* All IPTV transports */
+static struct th_transport_list iptv_active_transports; /* Currently enabled */
+
+/**
+ * PAT parser. We only parse a single program. CRC has already been verified
+ */
+static void
+iptv_got_pat(const uint8_t *ptr, int len, void *aux)
+{
+  th_transport_t *t = aux;
+  uint16_t prognum, pmt;
+
+  len -= 8;
+  ptr += 8;
+
+  if(len < 4)
+    return;
+
+  prognum =  ptr[0]         << 8 | ptr[1];
+  pmt     = (ptr[2] & 0x1f) << 8 | ptr[3];
+
+  t->tht_pmt_pid = pmt;
+}
 
-struct th_transport_list iptv_probing_transports;
-struct th_transport_list iptv_stale_transports;
-static dtimer_t iptv_probe_timer;
 
-static void iptv_probe_transport(th_transport_t *t);
-static void iptv_probe_callback(void *aux, int64_t now);
-static void iptv_probe_done(th_transport_t *t, int timeout);
+/**
+ * PMT parser. CRC has already been verified
+ */
+static void
+iptv_got_pmt(const uint8_t *ptr, int len, void *aux)
+{
+  th_transport_t *t = aux;
+
+  if(len < 3 || ptr[0] != 2)
+    return;
+
+  pthread_mutex_lock(&t->tht_stream_mutex);
+  psi_parse_pmt(t, ptr + 3, len - 3, 0, 1);
+  pthread_mutex_unlock(&t->tht_stream_mutex);
+}
+
 
+/**
+ * Handle a single TS packet for the given IPTV transport
+ */
 static void
-iptv_fd_callback(int events, void *opaque, int fd)
+iptv_ts_input(th_transport_t *t, uint8_t *tsb)
 {
-  th_transport_t *t = opaque;
-  uint8_t buf[2000];
-  int r;
-  uint8_t *tsb = buf;
+  uint16_t pid = ((tsb[1] & 0x1f) << 8) | tsb[2];
+
+  if(pid == 0) {
+
+    if(t->tht_pat_section == NULL)
+      t->tht_pat_section = calloc(1, sizeof(psi_section_t));
+    psi_rawts_table_parser(t->tht_pat_section, tsb, iptv_got_pat, t);
+
+  } else if(pid == t->tht_pmt_pid) {
 
-  r = read(fd, buf, sizeof(buf));
+    if(t->tht_pmt_section == NULL)
+      t->tht_pmt_section = calloc(1, sizeof(psi_section_t));
+    psi_rawts_table_parser(t->tht_pmt_section, tsb, iptv_got_pmt, t);
 
-  while(r >= 188) {
+  } else {
     ts_recv_packet1(t, tsb);
-    r -= 188;
-    tsb += 188;
+  } 
+}
+
+
+/**
+ * Main epoll() based input thread for IPTV
+ */
+static void *
+iptv_thread(void *aux)
+{
+  int nfds, fd, r, j;
+  uint8_t tsb[2048];
+  th_transport_t *t;
+  struct epoll_event ev;
+
+  while(1) {
+    nfds = epoll_wait(iptv_epollfd, &ev, 1, -1);
+    if(nfds == -1) {
+      tvhlog(LOG_ERR, "IPTV", "epoll() error -- %s, sleeping 1 second",
+            strerror(errno));
+      sleep(1);
+      continue;
+    }
+
+    if(nfds < 1)
+      continue;
+
+    fd = ev.data.fd;
+    r = read(fd, tsb, 2048);
+
+    // Add RTP support here
+    
+    if((r % 188) != 0)
+      continue; // We expect multiples of a TS packet
+    
+    pthread_mutex_lock(&iptv_recvmutex);
+    
+    LIST_FOREACH(t, &iptv_active_transports, tht_active_link) {
+      if(t->tht_iptv_fd != fd)
+       continue;
+      
+      for(j = 0; j < r; j += 188)
+       iptv_ts_input(t, tsb + j);
+    }
+    pthread_mutex_unlock(&iptv_recvmutex);
   }
 }
 
+
+/**
+ *
+ */
 static int
-iptv_start_feed(th_transport_t *t, unsigned int weight, int status, int force)
+iptv_transport_start(th_transport_t *t, unsigned int weight, int status, 
+                    int force_start)
 {
+  pthread_t tid;
   int fd;
   struct ip_mreqn m;
   struct sockaddr_in sin;
+  struct ifreq ifr;
+  struct epoll_event ev;
+
+  assert(t->tht_iptv_fd == -1);
+
+  if(iptv_thread_running == 0) {
+    iptv_thread_running = 1;
+    iptv_epollfd = epoll_create(10);
+    pthread_create(&tid, NULL, iptv_thread, NULL);
+  }
+
+  /* Now, open the real socket for UDP */
 
   fd = socket(AF_INET, SOCK_DGRAM, 0);
-  if(fd == -1)
+  if(fd == -1) {
+    tvhlog(LOG_ERR, "IPTV", "\"%s\" cannot open socket", t->tht_identifier);
     return -1;
+  }
+
+  /* First, resolve interface name */
+  memset(&ifr, 0, sizeof(ifr));
+  av_strlcpy(ifr.ifr_name, t->tht_iptv_iface, IFNAMSIZ);
+  ifr.ifr_name[IFNAMSIZ - 1] = 0;
+  if(ioctl(fd, SIOCGIFINDEX, &ifr)) {
+    tvhlog(LOG_ERR, "IPTV", "\"%s\" cannot find interface %s", 
+          t->tht_identifier, t->tht_iptv_iface);
+    close(fd);
+    return -1;
+  }
 
+  /* Bind to multicast group */
   memset(&sin, 0, sizeof(sin));
   sin.sin_family = AF_INET;
   sin.sin_port = htons(t->tht_iptv_port);
-  sin.sin_addr.s_addr = t->tht_iptv_group_addr.s_addr;
+  sin.sin_addr.s_addr = t->tht_iptv_group.s_addr;
 
   if(bind(fd, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
-    syslog(LOG_ERR, "iptv: \"%s\" cannot bind %s:%d -- %s", 
-          t->tht_name, inet_ntoa(sin.sin_addr), t->tht_iptv_port,
+    tvhlog(LOG_ERR, "IPTV", "\"%s\" cannot bind %s:%d -- %s", 
+          t->tht_identifier, inet_ntoa(sin.sin_addr), t->tht_iptv_port,
           strerror(errno));
     close(fd);
     return -1;
   }
 
+  /* Join group */
   memset(&m, 0, sizeof(m));
-  m.imr_multiaddr.s_addr = t->tht_iptv_group_addr.s_addr;
-  m.imr_address.s_addr = t->tht_iptv_interface_addr.s_addr;
-  m.imr_ifindex = t->tht_iptv_ifindex;
+  m.imr_multiaddr.s_addr = t->tht_iptv_group.s_addr;
+  m.imr_address.s_addr = 0;
+  m.imr_ifindex = ifr.ifr_ifindex;
 
   if(setsockopt(fd, SOL_IP, IP_ADD_MEMBERSHIP, &m, 
                sizeof(struct ip_mreqn)) == -1) {
-    syslog(LOG_ERR, "iptv: \"%s\" cannot join %s -- %s", 
-          t->tht_name, inet_ntoa(m.imr_multiaddr),
-          strerror(errno));
+    tvhlog(LOG_ERR, "IPTV", "\"%s\" cannot join %s -- %s", 
+          t->tht_identifier, inet_ntoa(m.imr_multiaddr), strerror(errno));
     close(fd);
     return -1;
   }
 
-  t->tht_iptv_fd = fd;
-  t->tht_runstatus = status;
+  memset(&ev, 0, sizeof(ev));
+  ev.events = EPOLLIN;
+  ev.data.fd = fd;
+  if(epoll_ctl(iptv_epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
+    tvhlog(LOG_ERR, "IPTV", "\"%s\" cannot add to epoll set -- %s", 
+          t->tht_identifier, strerror(errno));
+    close(fd);
+    return -1;
+  }
 
-  syslog(LOG_ERR, "iptv: \"%s\" joined group", t->tht_name);
+  t->tht_iptv_fd = fd;
+  t->tht_status = status;
 
-  t->tht_iptv_dispatch_handle = dispatch_addfd(fd, iptv_fd_callback, t,
-                                              DISPATCH_READ);
+  pthread_mutex_lock(&iptv_recvmutex);
+  LIST_INSERT_HEAD(&iptv_active_transports, t, tht_active_link);
+  pthread_mutex_unlock(&iptv_recvmutex);
   return 0;
 }
 
+
+/**
+ *
+ */
 static void
-iptv_stop_feed(th_transport_t *t)
+iptv_transport_refresh(th_transport_t *t)
 {
-  if(t->tht_runstatus == TRANSPORT_IDLE)
-    return;
-
-  t->tht_runstatus = TRANSPORT_IDLE;
-  dispatch_delfd(t->tht_iptv_dispatch_handle);
-  close(t->tht_iptv_fd);
 
-  syslog(LOG_ERR, "iptv: \"%s\" left group", t->tht_name);
 }
 
 
-/*
+/**
  *
  */
-
 static void
-iptv_parse_pmt(struct th_transport *t, th_stream_t *st,
-              uint8_t *table, int table_len)
+iptv_transport_stop(th_transport_t *t)
 {
-  if(table[0] != 2 || t->tht_runstatus != TRANSPORT_PROBING)
-    return;
+  t->tht_status = TRANSPORT_IDLE;
+  pthread_mutex_lock(&iptv_recvmutex);
+  LIST_REMOVE(t, tht_active_link);
+  pthread_mutex_unlock(&iptv_recvmutex);
+
+  assert(t->tht_iptv_fd >= 0);
 
-  psi_parse_pmt(t, table + 3, table_len - 3, 0);
+  close(t->tht_iptv_fd); // Automatically removes fd from epoll set
 
-  iptv_probe_done(t, 0);
+  t->tht_iptv_fd = -1;
 }
 
 
-/*
+/**
  *
  */
-
 static void
-iptv_parse_pat(struct th_transport *t, th_stream_t *st,
-              uint8_t *table, int table_len)
+iptv_transport_save(th_transport_t *t)
 {
-  if(table[0] != 0 || t->tht_runstatus != TRANSPORT_PROBING)
-    return;
+  htsmsg_t *m = htsmsg_create_map();
+  char abuf[INET_ADDRSTRLEN];
 
-  psi_parse_pat(t, table + 3, table_len - 3, iptv_parse_pmt);
-}
+  lock_assert(&global_lock);
 
-/*
- *
- */
-
-int
-iptv_configure_transport(th_transport_t *t, const char *iptv_type,
-                        struct config_head *head, const char *channel_name)
-{
-  const char *s;
-  int fd;
-  char buf[100];
-  char ifname[100];
-  struct ifreq ifr;
-  th_stream_t *st;
-  
-  if(!strcasecmp(iptv_type, "rawudp"))
-    t->tht_iptv_mode = IPTV_MODE_RAWUDP;
-  else
-    return -1;
+  htsmsg_add_u32(m, "pmt", t->tht_pmt_pid);
 
-  t->tht_type = TRANSPORT_IPTV;
-  t->tht_start_feed = iptv_start_feed;
-  t->tht_stop_feed  = iptv_stop_feed;
+  if(t->tht_iptv_port)
+    htsmsg_add_u32(m, "port", t->tht_iptv_port);
 
-  if((s = config_get_str_sub(head, "group-address", NULL)) == NULL)
-    return -1;
-  t->tht_iptv_group_addr.s_addr = inet_addr(s);
+  if(t->tht_iptv_iface)
+    htsmsg_add_str(m, "interface", t->tht_iptv_iface);
 
-  t->tht_iptv_ifindex = 0;
+  if(t->tht_iptv_group.s_addr) {
+    inet_ntop(AF_INET, &t->tht_iptv_group, abuf, sizeof(abuf));
+    htsmsg_add_str(m, "group", abuf);
+  }
 
-  if((s = config_get_str_sub(head, "interface-address", NULL)) != NULL)
-    t->tht_iptv_interface_addr.s_addr = inet_addr(s);
-  else
-    t->tht_iptv_interface_addr.s_addr = INADDR_ANY; 
+  if(t->tht_ch != NULL) {
+    htsmsg_add_str(m, "channelname", t->tht_ch->ch_name);
+    htsmsg_add_u32(m, "mapped", 1);
+  }
   
-  snprintf(ifname, sizeof(ifname), "%s",
-          inet_ntoa(t->tht_iptv_interface_addr));
+  pthread_mutex_lock(&t->tht_stream_mutex);
+  psi_save_transport_settings(m, t);
+  pthread_mutex_unlock(&t->tht_stream_mutex);
+  
+  hts_settings_save(m, "iptvtransports/%s",
+                   t->tht_identifier);
 
-  if((s = config_get_str_sub(head, "interface", NULL)) != NULL) {
+  htsmsg_destroy(m);
+}
 
-    memset(&ifr, 0, sizeof(ifr));
-    strncpy(ifr.ifr_name, s, IFNAMSIZ - 1);
-    ifr.ifr_name[IFNAMSIZ - 1] = 0;
-    
-    fd = socket(PF_INET,SOCK_STREAM,0);
-    if(fd != -1) {
-      if(ioctl(fd, SIOCGIFINDEX, &ifr) == 0) {
-       t->tht_iptv_ifindex = ifr.ifr_ifindex;
-       snprintf(ifname, sizeof(ifname), "%s", s);
-      }
-      close(fd);
-    }
-  }
 
-  if((s = config_get_str_sub(head, "port", NULL)) == NULL)
-    return -1;
-  t->tht_iptv_port = atoi(s);
+/**
+ *
+ */
+static int
+iptv_transport_quality(th_transport_t *t)
+{
+  if(t->tht_iptv_iface == NULL || 
+     t->tht_iptv_group.s_addr == 0 ||
+     t->tht_iptv_port == 0)
+    return 0;
 
-  snprintf(buf, sizeof(buf), "IPTV: %s (%s:%s:%d)", channel_name,
-          ifname, inet_ntoa(t->tht_iptv_group_addr), t->tht_iptv_port);
-  t->tht_name = strdup(buf);
+  return 100;
+}
 
-  st = transport_add_stream(t, 0, HTSTV_PAT);
-  st->st_got_section = iptv_parse_pat;
-  st->st_section_docrc = 1;
 
-  s = config_get_str_sub(head, "provider", NULL);
-  if(s != NULL)
-    t->tht_provider = strdup(s);
-  else
-    t->tht_provider = strdup("IPTV");
+/**
+ * Generate a descriptive name for the source
+ */
+static htsmsg_t *
+iptv_transport_sourceinfo(th_transport_t *t)
+{
+  htsmsg_t *m = htsmsg_create_map();
 
-  snprintf(buf, sizeof(buf), "iptv_%s_%d",
-          inet_ntoa(t->tht_iptv_group_addr), t->tht_iptv_port);
-  t->tht_identifier = strdup(buf);
+  if(t->tht_iptv_iface != NULL)
+    htsmsg_add_str(m, "adapter", t->tht_iptv_iface);
+  htsmsg_add_str(m, "mux", inet_ntoa(t->tht_iptv_group));
+  return m;
+}
 
-  t->tht_chname = strdup(channel_name);
 
-  LIST_INSERT_HEAD(&iptv_probing_transports, t, tht_active_link);
-  startupcounter++;
+/**
+ *
+ */
+th_transport_t *
+iptv_transport_find(const char *id, int create)
+{
+  static int tally;
+  th_transport_t *t;
+  char buf[20];
 
-  if(!dtimer_isarmed(&iptv_probe_timer)) {
-    iptv_probe_transport(t);
-    dtimer_arm(&iptv_probe_timer, iptv_probe_callback, t, 5);
+  if(id != NULL) {
+
+    if(strncmp(id, "iptv_", 5))
+      return NULL;
+
+    LIST_FOREACH(t, &iptv_all_transports, tht_group_link)
+      if(!strcmp(t->tht_identifier, id))
+       return t;
   }
 
-  return 0;
-}
+  if(create == 0)
+    return NULL;
+  
+  if(id == NULL) {
+    tally++;
+    snprintf(buf, sizeof(buf), "iptv_%d", tally);
+    id = buf;
+  } else {
+    tally = MAX(atoi(id + 5), tally);
+  }
 
-static void
-iptv_probe_transport(th_transport_t *t)
-{
-  syslog(LOG_INFO, "iptv: Probing transport %s", t->tht_name);
-  iptv_start_feed(t, 1, TRANSPORT_PROBING, 1);
+  t = transport_create(id, TRANSPORT_IPTV, THT_MPEG_TS);
+
+  t->tht_start_feed    = iptv_transport_start;
+  t->tht_refresh_feed  = iptv_transport_refresh;
+  t->tht_stop_feed     = iptv_transport_stop;
+  t->tht_config_save   = iptv_transport_save;
+  t->tht_sourceinfo    = iptv_transport_sourceinfo;
+  t->tht_quality_index = iptv_transport_quality;
+  t->tht_iptv_fd = -1;
+
+  LIST_INSERT_HEAD(&iptv_all_transports, t, tht_group_link);
+
+  return t;
 }
 
 
+/**
+ * Load config for the given mux
+ */
 static void
-iptv_probe_done(th_transport_t *t, int timeout)
+iptv_transport_load(void)
 {
-  int pidcnt = 0;
-  th_stream_t *st;
-
-  startupcounter--;
+  htsmsg_t *l, *c;
+  htsmsg_field_t *f;
+  uint32_t pmt;
+  const char *s;
+  unsigned int u32;
+  th_transport_t *t;
 
-  dtimer_disarm(&iptv_probe_timer);
+  lock_assert(&global_lock);
 
-  LIST_FOREACH(st, &t->tht_streams, st_link)
-    pidcnt++;
+  if((l = hts_settings_load("iptvtransports")) == NULL)
+    return;
   
-  LIST_REMOVE(t, tht_active_link);
+  HTSMSG_FOREACH(f, l) {
+    if((c = htsmsg_get_map_by_field(f)) == NULL)
+      continue;
 
-  syslog(LOG_INFO, "iptv: Transport %s probed, %d pids found%s", 
-        t->tht_name, pidcnt, timeout ? ", but probe timeouted" : "");
-
-  iptv_stop_feed(t);
+    if(htsmsg_get_u32(c, "pmt", &pmt))
+      continue;
+    
+    t = iptv_transport_find(f->hmf_name, 1);
+    t->tht_pmt_pid = pmt;
 
-  if(!timeout)
-    transport_map_channel(t, NULL);
-  else
-    LIST_INSERT_HEAD(&iptv_stale_transports, t, tht_active_link);
+    tvh_str_update(&t->tht_iptv_iface, htsmsg_get_str(c, "interface"));
 
-  t = LIST_FIRST(&iptv_probing_transports);
-  if(t == NULL)
-    return;
+    if((s = htsmsg_get_str(c, "group")) != NULL)
+      inet_pton(AF_INET, s, &t->tht_iptv_group.s_addr);
+    
+    if(!htsmsg_get_u32(c, "port", &u32))
+      t->tht_iptv_port = u32;
 
-  iptv_probe_transport(t);
-  dtimer_arm(&iptv_probe_timer, iptv_probe_callback, t, 5);
+    pthread_mutex_lock(&t->tht_stream_mutex);
+    psi_load_transport_settings(c, t);
+    pthread_mutex_unlock(&t->tht_stream_mutex);
+    
+    s = htsmsg_get_str(c, "channelname");
+    if(htsmsg_get_u32(c, "mapped", &u32))
+      u32 = 0;
+    
+    if(s && u32)
+      transport_map_channel(t, channel_find_by_name(s, 1), 0);
+  }
+  htsmsg_destroy(l);
 }
 
 
-
-static void
-iptv_probe_callback(void *aux, int64_t now)
+/**
+ *
+ */
+void
+iptv_input_init(void)
 {
-  th_transport_t *t = aux;
-  iptv_probe_done(t, 1);
+  pthread_mutex_init(&iptv_recvmutex, NULL);
+  iptv_transport_load();
 }
index b9918516154d2f7e9bc4a1c8fa92be5097d13b7b..d5020b1e9b7949243f0bb90a3eeb84f7fe4b8b88 100644 (file)
 #ifndef IPTV_INPUT_H_
 #define IPTV_INPUT_H_
 
-int iptv_configure_transport(th_transport_t *t, const char *muxname,
-                            struct config_head *head, 
-                            const char *channel_name);
+void iptv_input_init(void);
 
-extern struct th_transport_list iptv_probing_transports;
-extern struct th_transport_list iptv_stale_transports;
+th_transport_t *iptv_transport_find(const char *id, int create);
+
+extern struct th_transport_list iptv_all_transports;
 
 #endif /* IPTV_INPUT_H_ */
index c62298b210e0606afcc88a08e86953b82a6d09ba..e8ba6f6d0acbb077a94aa12b27aedacc45feb0f6 100644 (file)
@@ -52,6 +52,8 @@
 #include "htsp.h"
 #include "rawtsinput.h"
 #include "avahi.h"
+#include "iptv_input.h"
+#include "transports.h"
 
 #include "parachute.h"
 #include "settings.h"
@@ -327,6 +329,8 @@ main(int argc, char **argv)
 
   xmltv_init();   /* Must be initialized before channels */
 
+  transport_init();
+
   channels_init();
 
   access_init(createdefault);
@@ -335,6 +339,8 @@ main(int argc, char **argv)
 
   dvb_init();
 
+  iptv_input_init();
+
   http_server_init();
 
   webui_init(contentpath);
@@ -507,13 +513,14 @@ tvh_str_set(char **strp, const char *src)
 /**
  *
  */
-void
+int
 tvh_str_update(char **strp, const char *src)
 {
   if(src == NULL)
-    return;
+    return 0;
   free(*strp);
   *strp = strdup(src);
+  return 1;
 }
 
 
index 99d24993d88211394cefc5eaffe617743eab8bf8..32aa9c0b4355cac1109258625ddf5e97f94e9448 100644 (file)
--- a/src/psi.c
+++ b/src/psi.c
@@ -276,12 +276,15 @@ psi_parse_pmt(th_transport_t *t, const uint8_t *ptr, int len, int chksvcid,
   char lang[4];
   int frameduration;
   int update = 0;
+  int had_components;
 
   if(len < 9)
     return -1;
 
   lock_assert(&t->tht_stream_mutex);
 
+  had_components = !!LIST_FIRST(&t->tht_components);
+
   sid     = ptr[0] << 8 | ptr[1];
   
   if((ptr[2] & 1) == 0) {
@@ -442,9 +445,9 @@ psi_parse_pmt(th_transport_t *t, const uint8_t *ptr, int len, int chksvcid,
   }
 
   if(update) {
-    t->tht_config_change(t);
+    transport_request_save(t);
     if(t->tht_status == TRANSPORT_RUNNING)
-      transport_restart(t);
+      transport_restart(t, had_components);
   }
   return 0;
 }
index acc06b75c44e145806dca2975db5a3688f5ad0a6..2c1a4c092efb81ad53509f7b578b6500ea4be301 100644 (file)
@@ -103,7 +103,7 @@ rawts_transport_add(rawts_t *rt, uint16_t sid, int pmt_pid)
 
   char tmp[200];
 
-  LIST_FOREACH(t, &rt->rt_transports, tht_mux_link) {
+  LIST_FOREACH(t, &rt->rt_transports, tht_group_link) {
     if(t->tht_dvb_service_id == sid)
       return t;
   }
@@ -118,7 +118,7 @@ rawts_transport_add(rawts_t *rt, uint16_t sid, int pmt_pid)
 
   t->tht_start_feed = rawts_transport_start;
   t->tht_stop_feed  = rawts_transport_stop;
-  t->tht_config_change = rawts_transport_save;
+  t->tht_config_save = rawts_transport_save;
   t->tht_sourceinfo = rawts_transport_sourceinfo;
   t->tht_quality_index = rawts_transport_quality;
 
@@ -126,7 +126,7 @@ rawts_transport_add(rawts_t *rt, uint16_t sid, int pmt_pid)
 
   tvhlog(LOG_NOTICE, "rawts", "Added service %d (pmt: %d)", sid, pmt_pid);
 
-  LIST_INSERT_HEAD(&rt->rt_transports, t, tht_mux_link);
+  LIST_INSERT_HEAD(&rt->rt_transports, t, tht_group_link);
 
   ch = channel_find_by_name(tmp, 1);
 
@@ -245,7 +245,7 @@ process_ts_packet(rawts_t *rt, uint8_t *tsb)
     return;
   }
   
-  LIST_FOREACH(t, &rt->rt_transports, tht_mux_link)
+  LIST_FOREACH(t, &rt->rt_transports, tht_group_link)
     ts_recv_packet1(t, tsb);
 }
 
index 8a2161d37309d59b19ae02da39b90fde6d61ca9f..feaa9a186ee8f4b2ad4c8f1187ac824313ff6782 100644 (file)
@@ -78,18 +78,20 @@ subscription_link_transport(th_subscription_t *s, th_transport_t *t)
   // Link to transport output
   streaming_target_connect(&t->tht_streaming_pad, &s->ths_input);
 
-  // Send a START message to the subscription client
-  sm = streaming_msg_create_msg(SMT_START, 
-                               transport_build_stream_start_msg(t));
+  if(LIST_FIRST(&t->tht_components) != NULL) {
 
-  streaming_target_deliver(s->ths_output, sm);
+    // Send a START message to the subscription client
+    sm = streaming_msg_create_msg(SMT_START, 
+                                 transport_build_stream_start_msg(t));
 
-  // Send a TRANSPORT_STATUS message to the subscription client
-  if(t->tht_feed_status != TRANSPORT_FEED_UNKNOWN) {
-    sm = streaming_msg_create_code(SMT_TRANSPORT_STATUS, t->tht_feed_status);
     streaming_target_deliver(s->ths_output, sm);
-  }
 
+    // Send a TRANSPORT_STATUS message to the subscription client
+    if(t->tht_feed_status != TRANSPORT_FEED_UNKNOWN) {
+      sm = streaming_msg_create_code(SMT_TRANSPORT_STATUS, t->tht_feed_status);
+      streaming_target_deliver(s->ths_output, sm);
+    }
+  }
   pthread_mutex_unlock(&t->tht_stream_mutex);
 }
 
@@ -108,10 +110,12 @@ subscription_unlink_transport(th_subscription_t *s)
   // Unlink from transport output
   streaming_target_disconnect(&t->tht_streaming_pad, &s->ths_input);
 
-  // Send a STOP message to the subscription client
-  sm = streaming_msg_create_msg(SMT_STOP, htsmsg_create_map());
-  streaming_target_deliver(s->ths_output, sm);
-    
+  if(LIST_FIRST(&t->tht_components) != NULL) {
+    // Send a STOP message to the subscription client
+    sm = streaming_msg_create_msg(SMT_STOP, htsmsg_create_map());
+    streaming_target_deliver(s->ths_output, sm);
+  }
+
   pthread_mutex_unlock(&t->tht_stream_mutex);
 
   LIST_REMOVE(s, ths_transport_link);
index 0f518953277d40ab82a004b3c3b1208ae730a488..df47a9820f7b56458d12898a894f0852e288fd29 100644 (file)
@@ -434,14 +434,12 @@ transport_destroy(th_transport_t *t)
     subscription_unlink_transport(s);
   }
 
-  free((void *)t->tht_name);
-
   if(t->tht_ch != NULL) {
     t->tht_ch = NULL;
     LIST_REMOVE(t, tht_ch_link);
   }
 
-  LIST_REMOVE(t, tht_mux_link);
+  LIST_REMOVE(t, tht_group_link);
   LIST_REMOVE(t, tht_hash_link);
   
   if(t->tht_status != TRANSPORT_IDLE)
@@ -458,6 +456,9 @@ transport_destroy(th_transport_t *t)
     free(st);
   }
 
+  free(t->tht_pat_section);
+  free(t->tht_pmt_section);
+
   transport_unref(t);
 }
 
@@ -536,6 +537,7 @@ transport_stream_create(th_transport_t *t, int pid,
 
   st->st_pid = pid;
   st->st_demuxer_fd = -1;
+  st->st_tb = (AVRational){1, 90000};
 
   TAILQ_INIT(&st->st_ptsq);
   TAILQ_INIT(&st->st_durationq);
@@ -593,12 +595,8 @@ transport_map_channel(th_transport_t *t, channel_t *ch, int save)
     LIST_INSERT_HEAD(&ch->ch_transports, t, tht_ch_link);
   }
 
-  if(!save)
-    return;
-
-  pthread_mutex_lock(&t->tht_stream_mutex);
-  t->tht_config_change(t); // Save config
-  pthread_mutex_unlock(&t->tht_stream_mutex);
+  if(save)
+    t->tht_config_save(t);
 }
 
 
@@ -675,19 +673,24 @@ transport_set_feed_status(th_transport_t *t, transport_feed_status_t newstatus)
  * (i.e. an AC3 stream disappears, etc)
  */
 void
-transport_restart(th_transport_t *t)
+transport_restart(th_transport_t *t, int had_components)
 {
   streaming_message_t *sm;
   lock_assert(&t->tht_stream_mutex);
 
-  sm = streaming_msg_create_msg(SMT_STOP, htsmsg_create_map());
-  streaming_pad_deliver(&t->tht_streaming_pad, sm);
+  if(had_components) {
+    sm = streaming_msg_create_msg(SMT_STOP, htsmsg_create_map());
+    streaming_pad_deliver(&t->tht_streaming_pad, sm);
+  }
 
   t->tht_refresh_feed(t);
 
-  sm = streaming_msg_create_msg(SMT_START, 
-                               transport_build_stream_start_msg(t));
-  streaming_pad_deliver(&t->tht_streaming_pad, sm);
+  if(LIST_FIRST(&t->tht_components) != NULL) {
+
+    sm = streaming_msg_create_msg(SMT_START, 
+                                 transport_build_stream_start_msg(t));
+    streaming_pad_deliver(&t->tht_streaming_pad, sm);
+  }
 }
 
 
@@ -771,8 +774,76 @@ transport_set_enable(th_transport_t *t, int enabled)
     return;
 
   t->tht_enabled = enabled;
+  t->tht_config_save(t);
+}
 
-  pthread_mutex_lock(&t->tht_stream_mutex);
-  t->tht_config_change(t); // Save config
-  pthread_mutex_unlock(&t->tht_stream_mutex);
+
+static pthread_mutex_t pending_save_mutex;
+static pthread_cond_t pending_save_cond;
+static struct th_transport_queue pending_save_queue;
+
+/**
+ *
+ */
+void
+transport_request_save(th_transport_t *t)
+{
+  pthread_mutex_lock(&pending_save_mutex);
+
+  if(!t->tht_ps_onqueue) {
+    t->tht_ps_onqueue = 1;
+    TAILQ_INSERT_TAIL(&pending_save_queue, t, tht_ps_link);
+    transport_ref(t);
+    pthread_cond_signal(&pending_save_cond);
+  }
+
+  pthread_mutex_unlock(&pending_save_mutex);
+}
+
+
+/**
+ *
+ */
+static void *
+transport_saver(void *aux)
+{
+  th_transport_t *t;
+
+  pthread_mutex_lock(&pending_save_mutex);
+
+  while(1) {
+
+    if((t = TAILQ_FIRST(&pending_save_queue)) == NULL) {
+      pthread_cond_wait(&pending_save_cond, &pending_save_mutex);
+      continue;
+    }
+
+    TAILQ_REMOVE(&pending_save_queue, t, tht_ps_link);
+    t->tht_ps_onqueue = 0;
+
+    pthread_mutex_unlock(&pending_save_mutex);
+    pthread_mutex_lock(&global_lock);
+
+    if(t->tht_status != TRANSPORT_ZOMBIE)
+      t->tht_config_save(t);
+
+    transport_unref(t);
+
+    pthread_mutex_unlock(&global_lock);
+    pthread_mutex_lock(&pending_save_mutex);
+  }
+}
+
+
+/**
+ *
+ */
+void
+transport_init(void)
+{
+  pthread_t tid;
+  TAILQ_INIT(&pending_save_queue);
+  pthread_mutex_init(&pending_save_mutex, NULL);
+  pthread_cond_init(&pending_save_cond, NULL);
+  pthread_create(&tid, NULL, transport_saver, NULL);
 }
index e9d0731291c7f97a20317792e255fc642ac71346..d9787a83b729952135a72148bdca570242ba882b 100644 (file)
@@ -23,6 +23,8 @@
 #include "htsmsg.h"
 #include "subscriptions.h"
 
+void transport_init(void);
+
 unsigned int transport_compute_weight(struct th_transport_list *head);
 
 int transport_start(th_transport_t *t, unsigned int weight, int force_start);
@@ -78,8 +80,10 @@ htsmsg_t *transport_build_stream_start_msg(th_transport_t *t);
 
 void transport_set_enable(th_transport_t *t, int enabled);
 
-void transport_restart(th_transport_t *t);
+void transport_restart(th_transport_t *t, int had_components);
 
 void transport_stream_destroy(th_transport_t *t, th_stream_t *st);
 
+void transport_request_save(th_transport_t *t);
+
 #endif /* TRANSPORTS_H */
index 34924456e7fa9092bc37989432cb6231792a35c9..22114db9dfcad3bada34fdeec044c8259e524854 100644 (file)
@@ -356,8 +356,6 @@ typedef enum {
  */
 typedef struct th_transport {
 
-  const char *tht_name;
-
   LIST_ENTRY(th_transport) tht_hash_link;
 
   enum {
@@ -436,7 +434,7 @@ typedef struct th_transport {
   int tht_enabled;
 
   
-  LIST_ENTRY(th_transport) tht_mux_link;
+  LIST_ENTRY(th_transport) tht_group_link;
 
   LIST_ENTRY(th_transport) tht_active_link;
 
@@ -449,7 +447,7 @@ typedef struct th_transport {
 
   void (*tht_stop_feed)(struct th_transport *t);
 
-  void (*tht_config_change)(struct th_transport *t);
+  void (*tht_config_save)(struct th_transport *t);
 
   struct htsmsg *(*tht_sourceinfo)(struct th_transport *t);
 
@@ -511,6 +509,18 @@ typedef struct th_transport {
   int tht_sp_onqueue;
   TAILQ_ENTRY(th_transport) tht_sp_link;
 
+  /**
+   * Pending save.
+   *
+   * transport_request_save() will enqueue the transport here.
+   * We need to do this if we don't hold the global lock.
+   * This happens when we update PMT from within the TS stream itself.
+   * Then we hold the stream mutex, and thus, can not obtain the global lock
+   * as it would cause lock inversion.
+   */
+  int tht_ps_onqueue;
+  TAILQ_ENTRY(th_transport) tht_ps_link;
+
   /**
    * Timer which is armed at transport start. Once it fires
    * it will check if any packets has been parsed. If not the status
@@ -518,6 +528,22 @@ typedef struct th_transport {
    */
   gtimer_t tht_receive_timer;
 
+  /**
+   * IPTV members
+   */
+  char *tht_iptv_iface;
+  struct in_addr tht_iptv_group;
+  uint16_t tht_iptv_port;
+  int tht_iptv_fd;
+
+  /**
+   * For per-transport PAT/PMT parsers, allocated on demand
+   * Free'd by transport_destroy
+   */
+  struct psi_section *tht_pat_section;
+  struct psi_section *tht_pmt_section;
+  
+
   /*********************************************************
    *
    * Streaming part of transport
@@ -614,7 +640,7 @@ static inline unsigned int tvh_strhash(const char *s, unsigned int mod)
 #define MAX(a,b) ((a) > (b) ? (a) : (b))
 
 void tvh_str_set(char **strp, const char *src);
-void tvh_str_update(char **strp, const char *src);
+int tvh_str_update(char **strp, const char *src);
 
 void tvhlog(int severity, const char *subsys, const char *fmt, ...);
 
index ac8dffe7f3209dbf6eac5e3f2271f8780a0f9b43..e1c50f1cf272784dd9286a85314d15168dcccc34 100644 (file)
@@ -24,6 +24,9 @@
 #include <string.h>
 #include <stdarg.h>
 
+#include <arpa/inet.h>
+#include <libavutil/avstring.h>
+
 #include "htsmsg.h"
 #include "htsmsg_json.h"
 
@@ -43,6 +46,7 @@
 #include "serviceprobe.h"
 #include "xmltv.h"
 #include "epg.h"
+#include "iptv_input.h"
 
 extern const char *htsversion;
 extern const char *htsversion_full;
@@ -123,6 +127,7 @@ extjs_root(http_connection_t *hc, const char *remain, void *opaque)
   extjs_load(hq, "static/app/acleditor.js");
   extjs_load(hq, "static/app/cwceditor.js");
   extjs_load(hq, "static/app/dvb.js");
+  extjs_load(hq, "static/app/iptv.js");
   extjs_load(hq, "static/app/chconf.js");
   extjs_load(hq, "static/app/epg.js");
   extjs_load(hq, "static/app/dvr.js");
@@ -1007,7 +1012,7 @@ extjs_dvbadapter(http_connection_t *hc, const char *remain, void *opaque)
           "Service probe started on \"%s\"", tda->tda_displayname);
 
     LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) {
-      LIST_FOREACH(t, &tdmi->tdmi_transports, tht_mux_link) {
+      LIST_FOREACH(t, &tdmi->tdmi_transports, tht_group_link) {
        if(t->tht_enabled)
          serviceprobe_enqueue(t);
       }
@@ -1140,6 +1145,24 @@ extjs_dvbmuxes(http_connection_t *hc, const char *remain, void *opaque)
 }
 
 
+/**
+ *
+ */
+static void
+transport_delete(htsmsg_t *in)
+{
+  htsmsg_field_t *f;
+  th_transport_t *t;
+  const char *id;
+
+  TAILQ_FOREACH(f, &in->hm_fields, hmf_link) {
+    if((id = htsmsg_field_get_string(f)) != NULL &&
+       (t = transport_find_by_identifier(id)) != NULL)
+      transport_destroy(t);
+  }
+}
+
+
 /**
  *
  */
@@ -1213,7 +1236,7 @@ extjs_dvbservices(http_connection_t *hc, const char *remain, void *opaque)
     array = htsmsg_create_list();
 
     LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) {
-      LIST_FOREACH(t, &tdmi->tdmi_transports, tht_mux_link) {
+      LIST_FOREACH(t, &tdmi->tdmi_transports, tht_group_link) {
        count++;
       }
     }
@@ -1221,7 +1244,7 @@ extjs_dvbservices(http_connection_t *hc, const char *remain, void *opaque)
     tvec = alloca(sizeof(th_transport_t *) * count);
 
     LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) {
-      LIST_FOREACH(t, &tdmi->tdmi_transports, tht_mux_link) {
+      LIST_FOREACH(t, &tdmi->tdmi_transports, tht_group_link) {
        tvec[i++] = t;
       }
     }
@@ -1514,8 +1537,153 @@ extjs_mergechannel(http_connection_t *hc, const char *remain, void *opaque)
   htsmsg_destroy(out);
   http_output_content(hc, "text/x-json; charset=UTF-8");
   return 0;
+}
+
+
+
+/**
+ *
+ */
+static void
+transport_update_iptv(htsmsg_t *in)
+{
+  htsmsg_field_t *f;
+  htsmsg_t *c;
+  th_transport_t *t;
+  uint32_t u32;
+  const char *id, *s;
+  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, "port", &u32)) {
+      t->tht_iptv_port = u32;
+      save = 1;
+    }
 
+    if((s = htsmsg_get_str(c, "group")) != NULL) {
+      inet_pton(AF_INET, s, &t->tht_iptv_group.s_addr);
+      save = 1;
+    }
+    
 
+    save |= tvh_str_update(&t->tht_iptv_iface, htsmsg_get_str(c, "interface"));
+    if(save)
+      t->tht_config_save(t); // Save config
+  }
+}
+
+
+
+/**
+ *
+ */
+static htsmsg_t *
+build_record_iptv(th_transport_t *t)
+{
+  htsmsg_t *r = htsmsg_create_map();
+  char abuf[INET_ADDRSTRLEN];
+
+  htsmsg_add_str(r, "id", t->tht_identifier);
+
+  htsmsg_add_str(r, "channelname", t->tht_ch ? t->tht_ch->ch_name : "");
+  htsmsg_add_str(r, "interface", t->tht_iptv_iface ?: "");
+
+  inet_ntop(AF_INET, &t->tht_iptv_group, abuf, sizeof(abuf));
+  htsmsg_add_str(r, "group", t->tht_iptv_group.s_addr ? abuf : "");
+
+  htsmsg_add_u32(r, "port", t->tht_iptv_port);
+  htsmsg_add_u32(r, "enabled", t->tht_enabled);
+  return r;
+}
+
+/**
+ *
+ */
+static int
+iptv_transportcmp(const void *A, const void *B)
+{
+  th_transport_t *a = *(th_transport_t **)A;
+  th_transport_t *b = *(th_transport_t **)B;
+
+  return memcmp(&a->tht_iptv_group, &b->tht_iptv_group, 4);
+}
+
+/**
+ *
+ */
+static int
+extjs_iptvservices(http_connection_t *hc, const char *remain, void *opaque)
+{
+  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);
+
+  in = entries != NULL ? htsmsg_json_deserialize(entries) : NULL;
+
+  if(!strcmp(op, "get")) {
+
+    LIST_FOREACH(t, &iptv_all_transports, tht_group_link)
+      count++;
+    tvec = alloca(sizeof(th_transport_t *) * count);
+    LIST_FOREACH(t, &iptv_all_transports, tht_group_link)
+      tvec[i++] = t;
+
+    out = htsmsg_create_map();
+    array = htsmsg_create_list();
+
+    qsort(tvec, count, sizeof(th_transport_t *), iptv_transportcmp);
+
+    for(i = 0; i < count; i++)
+      htsmsg_add_msg(array, NULL, build_record_iptv(tvec[i]));
+
+    htsmsg_add_msg(out, "entries", array);
+
+  } else if(!strcmp(op, "update")) {
+    if(in != NULL) {
+      transport_update(in);      // Generic transport parameters
+      transport_update_iptv(in); // IPTV speicifc
+    }
+
+    out = htsmsg_create_map();
+
+  } else if(!strcmp(op, "create")) {
+
+    out = build_record_iptv(iptv_transport_find(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;
 }
 
 
@@ -1563,4 +1731,8 @@ extjs_start(void)
 
   http_path_add("/dvb/addmux", 
                NULL, extjs_dvb_addmux, ACCESS_ADMIN);
+
+  http_path_add("/iptv/services", 
+               NULL, extjs_iptvservices, ACCESS_ADMIN);
+
 }
index 2151cc421312986845d85a4636f0ce0eb60858d1..6813760f6a3e8ae65098d558b5d581b5b5fcf858 100644 (file)
     background-image:url(../icons/arrow_join.png) !important;
 }
 
+.iptv {
+    background-image:url(../icons/world.png) !important;
+}
+
+
 
 .x-smallhdr {
     float:left;
diff --git a/src/webui/static/app/iptv.js b/src/webui/static/app/iptv.js
new file mode 100644 (file)
index 0000000..7796cef
--- /dev/null
@@ -0,0 +1,276 @@
+/**
+ * IPTV service grid
+ */
+tvheadend.iptv = function(adapterId) {
+
+    var fm = Ext.form;
+    
+    var enabledColumn = new Ext.grid.CheckColumn({
+       header: "Enabled",
+       dataIndex: 'enabled',
+       width: 45
+    });
+
+    var actions = new Ext.ux.grid.RowActions({
+       header:'',
+       dataIndex: 'actions',
+       width: 45,
+       actions: [
+           {
+               iconCls:'info',
+               qtip:'Detailed information about service',
+               cb: function(grid, record, action, row, col) {
+                   Ext.Ajax.request({
+                       url: "dvb/servicedetails/" + record.id,
+                       success:function(response, options) {
+                           r = Ext.util.JSON.decode(response.responseText);
+                           tvheadend.showTransportDetails(r);
+                       }
+                   })
+               }
+           }
+       ]
+    });
+
+    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: "Interface",
+           dataIndex: 'interface',
+           width: 100,
+           renderer: function(value, metadata, record, row, col, store) {
+               return value ? value :
+                   '<span class="tvh-grid-unset">Unset</span>';
+           },
+           editor: new fm.TextField({allowBlank: false})
+       },
+       {
+           header: "Group",
+           dataIndex: 'group',
+           width: 100,
+           renderer: function(value, metadata, record, row, col, store) {
+               return value ? value :
+                   '<span class="tvh-grid-unset">Unset</span>';
+           },
+           editor: new fm.TextField({allowBlank: false})
+       },
+       {
+           header: "UDP Port",
+           dataIndex: 'port',
+           width: 60,
+           editor: new fm.NumberField({
+               minValue: 1,
+               maxValue: 65535
+           })
+       },
+       {
+           header: "Service ID",
+           dataIndex: 'sid',
+           width: 50,
+           hidden: true
+       },
+       {
+           header: "PMT PID",
+           dataIndex: 'pmt',
+           width: 50,
+           hidden: true
+       },
+       {
+           header: "PCR PID",
+           dataIndex: 'pcr',
+           width: 50,
+           hidden: true
+       }, actions
+    ]);
+
+    cm.defaultSortable = true;
+
+    var rec = Ext.data.Record.create([
+       'id', 'enabled', 'channelname', 'interface', 'group', 'port',
+       'sid', 'pmt', 'pcr'
+    ]);
+
+    var store = new Ext.data.JsonStore({
+       root: 'entries',
+       fields: rec,
+       url: "iptv/services",
+       autoLoad: true,
+       id: 'id',
+       baseParams: {op: "get"},
+       listeners: {
+           'update': function(s, r, o) {
+               d = s.getModifiedRecords().length == 0
+               saveBtn.setDisabled(d);
+               rejectBtn.setDisabled(d);
+           }
+       }
+    });
+
+/*
+    var storeReloader = new Ext.util.DelayedTask(function() {
+       store.reload()
+    });
+  
+    tvheadend.comet.on('dvbService', function(m) {
+       storeReloader.delay(500);
+    });
+*/
+
+    function addRecord() {
+       Ext.Ajax.request({
+           url: "iptv/services", 
+           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: "iptv/services",
+               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: "iptv/services",
+           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: 'IPTV',
+       iconCls: 'iptv',
+       plugins: [enabledColumn, actions],
+       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;
+}
index 3a16abccb43d29ccdbd536c07fa44dd5bad0531c..497f740a671ef1cf6d20277df0c188bdddbb5dd5 100644 (file)
@@ -52,6 +52,7 @@ function accessUpdate(o) {
                    new tvheadend.cteditor,
                    new tvheadend.dvrsettings,
                    new tvheadend.dvb,
+                   new tvheadend.iptv,
                    new tvheadend.acleditor, 
                    new tvheadend.cwceditor]
        });
diff --git a/src/webui/static/icons/world.png b/src/webui/static/icons/world.png
new file mode 100644 (file)
index 0000000..68f21d3
Binary files /dev/null and b/src/webui/static/icons/world.png differ