Add support for IPTV
This commit is contained in:
parent
ac5ae9e9cd
commit
cf49e03467
20 changed files with 993 additions and 266 deletions
1
Makefile
1
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 \
|
||||
|
|
2
debian/changelog
vendored
2
debian/changelog
vendored
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
551
src/iptv_input.c
551
src/iptv_input.c
|
@ -19,9 +19,10 @@
|
|||
#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>
|
||||
|
@ -31,271 +32,427 @@
|
|||
#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"
|
||||
|
||||
struct th_transport_list iptv_probing_transports;
|
||||
struct th_transport_list iptv_stale_transports;
|
||||
static dtimer_t iptv_probe_timer;
|
||||
static int iptv_thread_running;
|
||||
static int iptv_epollfd;
|
||||
static pthread_mutex_t iptv_recvmutex;
|
||||
|
||||
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);
|
||||
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_fd_callback(int events, void *opaque, int fd)
|
||||
iptv_got_pat(const uint8_t *ptr, int len, void *aux)
|
||||
{
|
||||
th_transport_t *t = opaque;
|
||||
uint8_t buf[2000];
|
||||
int r;
|
||||
uint8_t *tsb = buf;
|
||||
th_transport_t *t = aux;
|
||||
uint16_t prognum, pmt;
|
||||
|
||||
r = read(fd, buf, sizeof(buf));
|
||||
len -= 8;
|
||||
ptr += 8;
|
||||
|
||||
while(r >= 188) {
|
||||
if(len < 4)
|
||||
return;
|
||||
|
||||
prognum = ptr[0] << 8 | ptr[1];
|
||||
pmt = (ptr[2] & 0x1f) << 8 | ptr[3];
|
||||
|
||||
t->tht_pmt_pid = pmt;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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_ts_input(th_transport_t *t, uint8_t *tsb)
|
||||
{
|
||||
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) {
|
||||
|
||||
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);
|
||||
|
||||
} 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
t->tht_iptv_fd = fd;
|
||||
t->tht_runstatus = status;
|
||||
t->tht_status = status;
|
||||
|
||||
syslog(LOG_ERR, "iptv: \"%s\" joined group", t->tht_name);
|
||||
|
||||
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)
|
||||
{
|
||||
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_refresh(th_transport_t *t)
|
||||
{
|
||||
if(table[0] != 2 || t->tht_runstatus != TRANSPORT_PROBING)
|
||||
return;
|
||||
|
||||
psi_parse_pmt(t, table + 3, table_len - 3, 0);
|
||||
|
||||
iptv_probe_done(t, 0);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
static void
|
||||
iptv_parse_pat(struct th_transport *t, th_stream_t *st,
|
||||
uint8_t *table, int table_len)
|
||||
iptv_transport_stop(th_transport_t *t)
|
||||
{
|
||||
if(table[0] != 0 || t->tht_runstatus != TRANSPORT_PROBING)
|
||||
return;
|
||||
|
||||
psi_parse_pat(t, table + 3, table_len - 3, iptv_parse_pmt);
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
t->tht_type = TRANSPORT_IPTV;
|
||||
t->tht_start_feed = iptv_start_feed;
|
||||
t->tht_stop_feed = iptv_stop_feed;
|
||||
|
||||
if((s = config_get_str_sub(head, "group-address", NULL)) == NULL)
|
||||
return -1;
|
||||
t->tht_iptv_group_addr.s_addr = inet_addr(s);
|
||||
|
||||
t->tht_iptv_ifindex = 0;
|
||||
|
||||
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;
|
||||
|
||||
snprintf(ifname, sizeof(ifname), "%s",
|
||||
inet_ntoa(t->tht_iptv_interface_addr));
|
||||
|
||||
if((s = config_get_str_sub(head, "interface", NULL)) != NULL) {
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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");
|
||||
|
||||
snprintf(buf, sizeof(buf), "iptv_%s_%d",
|
||||
inet_ntoa(t->tht_iptv_group_addr), t->tht_iptv_port);
|
||||
t->tht_identifier = strdup(buf);
|
||||
|
||||
t->tht_chname = strdup(channel_name);
|
||||
|
||||
LIST_INSERT_HEAD(&iptv_probing_transports, t, tht_active_link);
|
||||
startupcounter++;
|
||||
|
||||
if(!dtimer_isarmed(&iptv_probe_timer)) {
|
||||
iptv_probe_transport(t);
|
||||
dtimer_arm(&iptv_probe_timer, iptv_probe_callback, t, 5);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
iptv_probe_done(th_transport_t *t, int timeout)
|
||||
{
|
||||
int pidcnt = 0;
|
||||
th_stream_t *st;
|
||||
|
||||
startupcounter--;
|
||||
|
||||
dtimer_disarm(&iptv_probe_timer);
|
||||
|
||||
LIST_FOREACH(st, &t->tht_streams, st_link)
|
||||
pidcnt++;
|
||||
|
||||
t->tht_status = TRANSPORT_IDLE;
|
||||
pthread_mutex_lock(&iptv_recvmutex);
|
||||
LIST_REMOVE(t, tht_active_link);
|
||||
pthread_mutex_unlock(&iptv_recvmutex);
|
||||
|
||||
syslog(LOG_INFO, "iptv: Transport %s probed, %d pids found%s",
|
||||
t->tht_name, pidcnt, timeout ? ", but probe timeouted" : "");
|
||||
assert(t->tht_iptv_fd >= 0);
|
||||
|
||||
iptv_stop_feed(t);
|
||||
close(t->tht_iptv_fd); // Automatically removes fd from epoll set
|
||||
|
||||
if(!timeout)
|
||||
transport_map_channel(t, NULL);
|
||||
else
|
||||
LIST_INSERT_HEAD(&iptv_stale_transports, t, tht_active_link);
|
||||
|
||||
t = LIST_FIRST(&iptv_probing_transports);
|
||||
if(t == NULL)
|
||||
return;
|
||||
|
||||
iptv_probe_transport(t);
|
||||
dtimer_arm(&iptv_probe_timer, iptv_probe_callback, t, 5);
|
||||
t->tht_iptv_fd = -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
iptv_probe_callback(void *aux, int64_t now)
|
||||
iptv_transport_save(th_transport_t *t)
|
||||
{
|
||||
th_transport_t *t = aux;
|
||||
iptv_probe_done(t, 1);
|
||||
htsmsg_t *m = htsmsg_create_map();
|
||||
char abuf[INET_ADDRSTRLEN];
|
||||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
htsmsg_add_u32(m, "pmt", t->tht_pmt_pid);
|
||||
|
||||
if(t->tht_iptv_port)
|
||||
htsmsg_add_u32(m, "port", t->tht_iptv_port);
|
||||
|
||||
if(t->tht_iptv_iface)
|
||||
htsmsg_add_str(m, "interface", t->tht_iptv_iface);
|
||||
|
||||
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(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, "iptvtransports/%s",
|
||||
t->tht_identifier);
|
||||
|
||||
htsmsg_destroy(m);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
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;
|
||||
|
||||
return 100;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a descriptive name for the source
|
||||
*/
|
||||
static htsmsg_t *
|
||||
iptv_transport_sourceinfo(th_transport_t *t)
|
||||
{
|
||||
htsmsg_t *m = htsmsg_create_map();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
th_transport_t *
|
||||
iptv_transport_find(const char *id, int create)
|
||||
{
|
||||
static int tally;
|
||||
th_transport_t *t;
|
||||
char buf[20];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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_transport_load(void)
|
||||
{
|
||||
htsmsg_t *l, *c;
|
||||
htsmsg_field_t *f;
|
||||
uint32_t pmt;
|
||||
const char *s;
|
||||
unsigned int u32;
|
||||
th_transport_t *t;
|
||||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
if((l = hts_settings_load("iptvtransports")) == NULL)
|
||||
return;
|
||||
|
||||
HTSMSG_FOREACH(f, l) {
|
||||
if((c = htsmsg_get_map_by_field(f)) == NULL)
|
||||
continue;
|
||||
|
||||
if(htsmsg_get_u32(c, "pmt", &pmt))
|
||||
continue;
|
||||
|
||||
t = iptv_transport_find(f->hmf_name, 1);
|
||||
t->tht_pmt_pid = pmt;
|
||||
|
||||
tvh_str_update(&t->tht_iptv_iface, htsmsg_get_str(c, "interface"));
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
iptv_input_init(void)
|
||||
{
|
||||
pthread_mutex_init(&iptv_recvmutex, NULL);
|
||||
iptv_transport_load();
|
||||
}
|
||||
|
|
|
@ -19,11 +19,10 @@
|
|||
#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_ */
|
||||
|
|
11
src/main.c
11
src/main.c
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
109
src/transports.c
109
src/transports.c
|
@ -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;
|
||||
|
||||
pthread_mutex_lock(&t->tht_stream_mutex);
|
||||
t->tht_config_change(t); // Save config
|
||||
pthread_mutex_unlock(&t->tht_stream_mutex);
|
||||
t->tht_config_save(t);
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
36
src/tvhead.h
36
src/tvhead.h
|
@ -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, ...);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -185,6 +185,11 @@
|
|||
background-image:url(../icons/arrow_join.png) !important;
|
||||
}
|
||||
|
||||
.iptv {
|
||||
background-image:url(../icons/world.png) !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.x-smallhdr {
|
||||
float:left;
|
||||
|
|
276
src/webui/static/app/iptv.js
Normal file
276
src/webui/static/app/iptv.js
Normal file
|
@ -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;
|
||||
}
|
|
@ -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]
|
||||
});
|
||||
|
|
BIN
src/webui/static/icons/world.png
Normal file
BIN
src/webui/static/icons/world.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 923 B |
Loading…
Add table
Reference in a new issue