Merge remote branch 'upstream/master' into pull_request

Conflicts:
	src/webui/static/app/tvheadend.js
This commit is contained in:
John Törnblom 2011-05-13 21:20:14 +02:00
commit 0ac11db415
58 changed files with 1761 additions and 333 deletions

4
.gitignore vendored
View file

@ -1,2 +1,6 @@
build.*
config.default
.cproject
.project
.settings

View file

@ -159,6 +159,17 @@ CFLAGS_com += -D_FILE_OFFSET_BITS=64
CFLAGS_com += -I${BUILDDIR} -I${CURDIR}/src -I${CURDIR}
CFLAGS_com += -DHTS_VERSION=\"$(VERSION)\"
MKBUNDLE = $(CURDIR)/support/mkbundle
ifndef V
ECHO = printf "$(1)\t%s\n" $(2)
BRIEF = CC MKBUNDLE CXX
MSG = $@
$(foreach VAR,$(BRIEF), \
$(eval $(VAR) = @$$(call ECHO,$(VAR),$$(MSG)); $($(VAR))))
endif
all: ${PROG}
.PHONY: clean distclean
@ -186,7 +197,7 @@ ifneq ($(VERSION), $(CURVERSION))
.PHONY: src/version.c
$(info Version changed)
src/version.c:
echo $(VERSION) >${BUILDDIR}/ver
@echo $(VERSION) >${BUILDDIR}/ver
endif
@ -200,6 +211,5 @@ include support/${OSENV}.mk
$(BUILDDIR)/bundles/%.o: $(BUILDDIR)/bundles/%.c
$(CC) -I${CURDIR}/src -c -o $@ $<
$(BUILDDIR)/bundles/%.c: % $(CURDIR)/support/mkbundle
$(CURDIR)/support/mkbundle \
-o $@ -s $< -d ${BUILDDIR}/bundles/$<.d -p $< -z
$(BUILDDIR)/bundles/%.c: %
$(MKBUNDLE) -o $@ -s $< -d ${BUILDDIR}/bundles/$<.d -p $< -z

128
contrib/redhat/tvheadend Executable file
View file

@ -0,0 +1,128 @@
#!/bin/sh
#
# tvheadend Start/Stop the hts tvheadend daemon.
#
# chkconfig: 345 90 60
# description: Tvheadend is a TV streaming server for Linux supporting
# DVB-S, DVB-S2, DVB-C, DVB-T, ATSC, IPTV, and Analog video
# (V4L) as input sources. It also comes with a powerful and
# easy to use web interface both used for configuration and
# day-to-day operations, such as searching the EPG and
# scheduling recordings.
### BEGIN INIT INFO
# Provides: tvheadend
# Required-Start: $local_fs $syslog
# Required-Stop: $local_fs $syslog
# Default-Start: 345
# Default-Stop: 90
# Short-Description: run tvheadend daemon
# Description: Tvheadend is a TV streaming server for Linux supporting
# DVB-S, DVB-S2, DVB-C, DVB-T, ATSC, IPTV, and Analog video
# (V4L) as input sources. It also comes with a powerful and
# easy to use web interface both used for configuration and
# day-to-day operations, such as searching the EPG and
# scheduling recordings.
### END INIT INFO
[ -f /etc/sysconfig/tvheadend ] || {
[ "$1" = "status" ] && exit 4 || exit 6
}
RETVAL=0
prog="tvheadend"
exec=/usr/local/bin/tvheadend
lockfile=/var/lock/subsys/tvheadend
sysconfig=/etc/sysconfig/tvheadend
# Source function library.
. /etc/rc.d/init.d/functions
[ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog
start() {
[ -x $exec ] || exit 5
[ -f $sysconfig ] || exit 6
echo -n $"Starting $prog: "
daemon $exec $OPTIONS
retval=$?
echo
[ $retval -eq 0 ] && touch $lockfile
}
stop() {
echo -n $"Stopping $prog: "
if [ -n "`pidfileofproc $exec`" ]; then
killproc $exec
RETVAL=3
else
failure $"Stopping $prog"
fi
retval=$?
echo
[ $retval -eq 0 ] && rm -f $lockfile
}
restart() {
stop
start
}
reload() {
echo -n $"Reloading $prog: "
if [ -n "`pidfileofproc $exec`" ]; then
killproc $exec -HUP
else
failure $"Reloading $prog"
fi
retval=$?
echo
}
force_reload() {
# new configuration takes effect after restart
restart
}
rh_status() {
# run checks to determine if the service is running or use generic status
status -p /var/run/tvheadend.pid $prog
}
rh_status_q() {
rh_status >/dev/null 2>&1
}
case "$1" in
start)
rh_status_q && exit 0
$1
;;
stop)
rh_status_q || exit 0
$1
;;
restart)
$1
;;
reload)
rh_status_q || exit 7
$1
;;
force-reload)
force_reload
;;
status)
rh_status
;;
condrestart|try-restart)
rh_status_q || exit 0
restart
;;
*)
echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}"
exit 2
esac
exit $?

View file

@ -0,0 +1,75 @@
Name: tvheadend
Summary: TV streaming server
Version: 2.12.cae47cf
Release: 1%{dist}
License: GPL
Group: Applications/Multimedia
URL: http://www.lonelycoder.com/tvheadend
Packager: Jouk Hettema
Source: tvheadend-%{version}.tar.bz2
Prefix: /usr
BuildRequires: avahi-devel, openssl, glibc, zlib
%description
Tvheadend is a TV streaming server for Linux supporting DVB-S, DVB-S2,
DVB-C, DVB-T, ATSC, IPTV, and Analog video (V4L) as input sources.
%prep
%setup -q
%build
%configure --release --prefix=%{prefix}/bin
%{__make}
%install
%{__rm} -rf %{buildroot}
mkdir -p $RPM_BUILD_ROOT/%{prefix}
%{__install} -d -m0755 %{buildroot}%{prefix}/bin
%{__install} -d -m0755 %{buildroot}/etc/tvheadend
%{__install} -d -m0755 %{buildroot}/etc/sysconfig
%{__install} -d -m0755 %{buildroot}/etc/rc.d/init.d
%{__install} -d -m0755 %{buildroot}%{prefix}/shared/man/man1
%{__install} -m0755 build.Linux/tvheadend %{buildroot}%{prefix}/bin/
%{__install} -m0755 man/tvheadend.1 %{buildroot}%{prefix}/shared/man/man1
%{__install} -m0755 contrib/redhat/tvheadend %{buildroot}/etc/rc.d/init.d
cat >> %{buildroot}/etc/sysconfig/tvheadend << EOF
OPTIONS="-f -u tvheadend -g tvheadend -c /etc/tvheadend -s -C"
EOF
%pre
groupadd -f -r tvheadend >/dev/null 2>&! || :
useradd -s /sbin/nologin -c "Tvheadend" -M -r tvheadend -g tvheadend -G video >/dev/null 2>&! || :
%preun
if [ $1 = 0 ]; then
/sbin/service tvheadend stop >/dev/null 2>&1
/sbin/chkconfig --del tvheadend
fi
%post
/sbin/chkconfig --add tvheadend
if [ "$1" -ge "1" ]; then
/sbin/service tvheadend condrestart >/dev/null 2>&1 || :
fi
%clean
%{__rm} -rf %{buildroot}
%files
%defattr(-,root,root)
%doc debian/changelog LICENSE README
%dir %attr(-,tvheadend,root) %{prefix}
%{prefix}/bin/tvheadend
%{prefix}/shared/man/man1/tvheadend.1*
%{_sysconfdir}/rc.d/init.d/tvheadend
/etc/tvheadend
%config /etc/sysconfig/tvheadend
%changelog
* Fri Feb 18 2011 Jouk Hettema <joukio@gmail.com> - 2.12.cae47cf
- initial build for fedora 14

33
debian/changelog vendored
View file

@ -1,3 +1,36 @@
tvheadend (2.12.99) hts; urgency=low
* Debian package has been renamed from hts-tvheadend to tvheadend
* Add functionality to delete recordings
* Better support for playing in web clients.
Temporary tickets are used instead of username/password authentication
* Remove lock contention in CWC updates
* Fix bug in IPTV PAT parser (Ticket #318)
* Store EPG on disk so it can be reloaded on restart
* Add support for Viaccess EMM
* Add support for DRECrypt EMM
* Depend on OpenSSL for cryptographic features
* RTSP has been dropped. It was too buggy and noone wanted to maintain it
* Fix bug in JSON encoder. Ticket #163
* Fix crash in HTTP service. Ticket #334
* Added http-streaming of services. Tested and working with mplayer.
* Add support for building RPM packages
-- Andreas Öman <andreas@lonelycoder.com> Sat, 19 Feb 2011 12:57:09 +0100
hts-tvheadend (2.12) hts; urgency=low
* Add support for IPTV over IPv6

24
debian/control vendored
View file

@ -1,15 +1,25 @@
Source: hts-tvheadend
Source: tvheadend
Section: main
Priority: extra
Maintainer: Andreas Öman <andreas@lonelycoder.com>
Build-Depends: debhelper (>= 5)
Build-Depends: debhelper (>= 7.0.50)
Standards-Version: 3.7.3
Package: hts-tvheadend
Package: tvheadend
Architecture: any
Depends: ${shlibs:Depends}, libavahi-client3
Recommends: xmltv
Enhances: hts-showtime
Description: HTS Tvheadend
TV backend for use with hts-showtime and various other clients.
Based on ffmpeg 'http://www.ffmpeg.org/' and ExtJS 'http://www.extjs.org/'
Enhances: showtime
Replaces: hts-tvheadend
Homepage: http://www.lonelycoder.com/tvheadend
Description: Tvheadend
TV backend for use with Showtime, XBMC and various other clients.
Uses ExtJS 'http://www.extjs.org/'
Package: tvheadend-dbg
Architecture: any
Section: debug
Priority: extra
Depends: tvheadend (= ${binary:Version}), ${misc:Depends}
Description: Debug symbols for Tvheadend
This package contains the debugging symbols for Tvheadend.

48
debian/rules vendored
View file

@ -1,38 +1,34 @@
#!/usr/bin/make -f
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
DEB_BUILD_GNU_TYPE := $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE)
config.mak: configure
dh_testdir
./configure --release --prefix=/usr
%:
dh $@
clean:
override_dh_clean:
dh_testdir
dh_testroot
rm -rf build.*
dh_clean
dh_clean
override_dh_auto_clean:
dh_clean
build: config.mak
override_dh_auto_configure:
dh_testdir
./configure --release --prefix=/usr
override_dh_auto_build:
$(MAKE)
binary:
dh_testdir
dh_testroot
dh_installdirs
dh_install
$(MAKE) prefix=$(CURDIR)/debian/hts-tvheadend/usr install
dh_installchangelogs
dh_installinit --name tvheadend
dh_installdocs
dh_installdebconf
dh_link
dh_strip
dh_compress
dh_fixperms
dh_makeshlibs
dh_installdeb
dh_shlibdeps
dh_gencontrol
dh_md5sums
dh_builddeb
override_dh_install:
$(MAKE) prefix=$(CURDIR)/debian/tvheadend/usr install
override_dh_installinit:
dh_installinit --name tvheadend
override_dh_strip:
dh_strip --dbg-package=tvheadend-dbg

View file

@ -12,17 +12,17 @@ while [ "$STATE" != 0 -a "$STATE" != 4 ]; do
case "$STATE" in
1)
# Ask for username
db_input high hts-tvheadend/admin_username || true
db_input high tvheadend/admin_username || true
;;
2)
# Ask for password
db_input high hts-tvheadend/admin_password || true
db_input high tvheadend/admin_password || true
;;
3)
# Display a final note
db_input high hts-tvheadend/webinterface || true
db_input high tvheadend/webinterface || true
;;
esac

View file

@ -27,11 +27,11 @@ configure)
echo >>"${HTS_SUPERUSERCONF}" "{"
if db_get hts-tvheadend/admin_username; then
if db_get tvheadend/admin_username; then
echo >>"${HTS_SUPERUSERCONF}" '"username": "'$RET'",'
fi
if db_get hts-tvheadend/admin_password; then
if db_get tvheadend/admin_password; then
echo >>"${HTS_SUPERUSERCONF}" '"password": "'$RET'"'
fi

View file

@ -1,4 +1,4 @@
Template: hts-tvheadend/admin_username
Template: tvheadend/admin_username
Type: string
Description: Choose a username for Tvheadend administrator.
Tvheadend is accessed via a world reachable web interface (assuming your
@ -8,10 +8,10 @@ Description: Choose a username for Tvheadend administrator.
If you want to change the superuser account you need to reconfigure the
Tvheadend package.
Template: hts-tvheadend/admin_password
Template: tvheadend/admin_password
Type: password
Description: Administrator password.
Template: hts-tvheadend/webinterface
Template: tvheadend/webinterface
Type: note
Description: After installation Tvheadend can be accessed via HTTP on port 9981. From this machine you can point your web-browser to http://localhost:9981/

View file

@ -1,6 +1,6 @@
#! /bin/sh
### BEGIN INIT INFO
# Provides: hts-tvheadend
# Provides: tvheadend
# Required-Start: $local_fs $remote_fs udev
# Required-Stop: $local_fs $remote_fs
# Default-Start: 2 3 4 5

View file

@ -30,6 +30,7 @@
#include <arpa/inet.h>
#include <openssl/sha.h>
#include <openssl/rand.h>
#include "tvheadend.h"
#include "access.h"
@ -37,10 +38,116 @@
#include "settings.h"
struct access_entry_queue access_entries;
struct access_ticket_queue access_tickets;
const char *superuser_username;
const char *superuser_password;
/**
*
*/
static void
access_ticket_destroy(access_ticket_t *at)
{
free(at->at_id);
free(at->at_resource);
TAILQ_REMOVE(&access_tickets, at, at_link);
free(at);
}
/**
*
*/
static access_ticket_t *
access_ticket_find(const char *id)
{
access_ticket_t *at = NULL;
if(id != NULL) {
TAILQ_FOREACH(at, &access_tickets, at_link)
if(!strcmp(at->at_id, id))
return at;
}
return NULL;
}
/**
*
*/
static void
access_ticket_timout(void *aux)
{
access_ticket_t *at = aux;
access_ticket_destroy(at);
}
/**
* Create a new ticket for the requested resource and generate a id for it
*/
const char *
access_ticket_create(const char *resource)
{
uint8_t buf[20];
char id[41];
unsigned int i;
access_ticket_t *at;
static const char hex_string[16] = "0123456789ABCDEF";
at = calloc(1, sizeof(access_ticket_t));
RAND_bytes(buf, 20);
//convert to hexstring
for(i=0; i<sizeof(buf); i++){
id[i*2] = hex_string[((buf[i] >> 4) & 0xF)];
id[(i*2)+1] = hex_string[(buf[i]) & 0x0F];
}
id[40] = '\0';
at->at_id = strdup(id);
at->at_resource = strdup(resource);
TAILQ_INSERT_TAIL(&access_tickets, at, at_link);
gtimer_arm(&at->at_timer, access_ticket_timout, at, 60*5);
return at->at_id;
}
/**
*
*/
int
access_ticket_delete(const char *id)
{
access_ticket_t *at;
if((at = access_ticket_find(id)) == NULL)
return -1;
gtimer_disarm(&at->at_timer);
access_ticket_destroy(at);
return 0;
}
/**
*
*/
int
access_ticket_verify(const char *id, const char *resource)
{
access_ticket_t *at;
if((at = access_ticket_find(id)) == NULL)
return -1;
if(strcmp(at->at_resource, resource))
return -1;
return 0;
}
/**
*
@ -409,6 +516,7 @@ static const dtable_class_t access_dtc = {
.dtc_record_delete = access_record_delete,
.dtc_read_access = ACCESS_ADMIN,
.dtc_write_access = ACCESS_ADMIN,
.dtc_mutex = &global_lock,
};
/**
@ -422,7 +530,17 @@ access_init(int createdefault)
access_entry_t *ae;
const char *s;
static struct {
pid_t pid;
struct timeval tv;
} randseed;
randseed.pid = getpid();
gettimeofday(&randseed.tv, NULL);
RAND_seed(&randseed, sizeof(randseed));
TAILQ_INIT(&access_entries);
TAILQ_INIT(&access_tickets);
dt = dtable_create(&access_dtc, "accesscontrol", NULL);

View file

@ -42,14 +42,36 @@ typedef struct access_entry {
uint32_t ae_netmask; /* derived from ae_prefixlen */
} access_entry_t;
TAILQ_HEAD(access_ticket_queue, access_ticket);
extern struct access_ticket_queue access_tickets;
typedef struct access_ticket {
char *at_id;
TAILQ_ENTRY(access_ticket) at_link;
gtimer_t at_timer;
char *at_resource;
} access_ticket_t;
#define ACCESS_STREAMING 0x1
#define ACCESS_WEB_INTERFACE 0x2
#define ACCESS_RECORDER 0x4
#define ACCESS_ADMIN 0x8
#define ACCESS_FULL 0x3f
/**
* Create a new ticket for the requested resource and generate a id for it
*/
const char* access_ticket_create(const char *resource);
/**
* Verifies that a given ticket id matches a resource
*/
int access_ticket_verify(const char *id, const char *resource);
int access_ticket_delete(const char *ticket_id);
/**
* Verifies that the given user in combination with the source ip
* complies with the requested mask

View file

@ -841,6 +841,7 @@ static const dtable_class_t capmt_dtc = {
.dtc_record_delete = capmt_entry_delete,
.dtc_read_access = ACCESS_ADMIN,
.dtc_write_access = ACCESS_ADMIN,
.dtc_mutex = &global_lock,
};
/**

View file

@ -858,6 +858,7 @@ static const dtable_class_t channel_tags_dtc = {
.dtc_record_delete = channel_tag_record_delete,
.dtc_read_access = ACCESS_ADMIN,
.dtc_write_access = ACCESS_ADMIN,
.dtc_mutex = &global_lock,
};

View file

@ -45,6 +45,7 @@ typedef struct channel {
struct event_tree ch_epg_events;
struct event *ch_epg_current;
struct event *ch_epg_next;
gtimer_t ch_epg_timer_head;
gtimer_t ch_epg_timer_current;

127
src/cwc.c
View file

@ -59,6 +59,8 @@ typedef enum {
CARD_CONAX,
CARD_SECA,
CARD_VIACCESS,
CARD_NAGRA,
CARD_NDS,
CARD_UNKNOWN
} card_type_t;
@ -95,6 +97,7 @@ TAILQ_HEAD(cwc_message_queue, cwc_message);
LIST_HEAD(ecm_section_list, ecm_section);
static struct cwc_queue cwcs;
static pthread_cond_t cwc_config_changed;
static pthread_mutex_t cwc_mutex;
static char *crypt_md5(const char *pw, const char *salt);
/**
@ -202,7 +205,7 @@ typedef struct cwc {
int cwc_writer_running;
struct cwc_message_queue cwc_writeq;
TAILQ_ENTRY(cwc) cwc_link; /* Linkage protected via global_lock */
TAILQ_ENTRY(cwc) cwc_link; /* Linkage protected via cwc_mutex */
struct cwc_service_list cwc_services;
@ -269,12 +272,14 @@ typedef struct cwc {
*/
static void cwc_service_destroy(th_descrambler_t *td);
static void cwc_detecs_card_type(cwc_t *cwc);
static void cwc_detect_card_type(cwc_t *cwc);
void cwc_emm_conax(cwc_t *cwc, uint8_t *data, int len);
void cwc_emm_irdeto(cwc_t *cwc, uint8_t *data, int len);
void cwc_emm_dre(cwc_t *cwc, uint8_t *data, int len);
void cwc_emm_seca(cwc_t *cwc, uint8_t *data, int len);
void cwc_emm_viaccess(cwc_t *cwc, uint8_t *data, int len);
void cwc_emm_nagra(cwc_t *cwc, uint8_t *data, int len);
void cwc_emm_nds(cwc_t *cwc, uint8_t *data, int len);
/**
@ -412,8 +417,8 @@ des_make_login_key(cwc_t *cwc, uint8_t *k)
des14[i] = cwc->cwc_confedkey[i] ^ k[i];
des_key_spread(des14, spread);
DES_key_sched((DES_cblock *)spread, &cwc->cwc_k1);
DES_key_sched((DES_cblock *)(spread+8), &cwc->cwc_k2);
DES_set_key_unchecked((DES_cblock *)spread, &cwc->cwc_k1);
DES_set_key_unchecked((DES_cblock *)(spread+8), &cwc->cwc_k2);
}
/**
@ -431,8 +436,8 @@ des_make_session_key(cwc_t *cwc)
des14[i % 14] ^= k2[i];
des_key_spread(des14, spread);
DES_key_sched((DES_cblock *)spread, &cwc->cwc_k1);
DES_key_sched((DES_cblock *)(spread+8), &cwc->cwc_k2);
DES_set_key_unchecked((DES_cblock *)spread, &cwc->cwc_k1);
DES_set_key_unchecked((DES_cblock *)(spread+8), &cwc->cwc_k2);
}
/**
@ -479,6 +484,9 @@ cwc_send_msg(cwc_t *cwc, const uint8_t *msg, size_t len, int sid, int enq)
pthread_mutex_unlock(&cwc->cwc_writer_mutex);
} else {
n = write(cwc->cwc_fd, buf, len);
if(n != len)
tvhlog(LOG_INFO, "cwc", "write error %s", strerror(errno));
free(cm);
}
return seq & 0xffff;
@ -566,7 +574,7 @@ cwc_decode_card_data_reply(cwc_t *cwc, uint8_t *msg, int len)
cwc->cwc_connected = 1;
cwc_comet_status_update(cwc);
cwc->cwc_caid = (msg[4] << 8) | msg[5];
n = psi_caid2name(cwc->cwc_caid) ?: "Unknown";
n = psi_caid2name(cwc->cwc_caid & 0xff00) ?: "Unknown";
memcpy(cwc->cwc_ua, &msg[6], 8);
@ -578,7 +586,7 @@ cwc_decode_card_data_reply(cwc_t *cwc, uint8_t *msg, int len)
cwc->cwc_ua[0], cwc->cwc_ua[1], cwc->cwc_ua[2], cwc->cwc_ua[3], cwc->cwc_ua[4], cwc->cwc_ua[5], cwc->cwc_ua[6], cwc->cwc_ua[7],
nprov);
cwc_detecs_card_type(cwc);
cwc_detect_card_type(cwc);
msg += 15;
plen -= 12;
@ -644,7 +652,7 @@ cwc_decode_card_data_reply(cwc_t *cwc, uint8_t *msg, int len)
* based on the equivalent in sasc-ng
*/
static void
cwc_detecs_card_type(cwc_t *cwc)
cwc_detect_card_type(cwc_t *cwc)
{
uint8_t c_sys = cwc->cwc_caid >> 8;
@ -674,6 +682,16 @@ cwc_detecs_card_type(cwc_t *cwc)
tvhlog(LOG_INFO, "cwc", "%s: dre card",
cwc->cwc_hostname);
break;
case 0x18:
cwc->cwc_card_type = CARD_NAGRA;
tvhlog(LOG_INFO, "cwc", "%s: nagra card",
cwc->cwc_hostname);
break;
case 0x09:
cwc->cwc_card_type = CARD_NDS;
tvhlog(LOG_INFO, "cwc", "%s: nds card",
cwc->cwc_hostname);
break;
default:
cwc->cwc_card_type = CARD_UNKNOWN;
break;
@ -770,18 +788,16 @@ handle_ecm_reply(cwc_service_t *ct, ecm_section_t *es, uint8_t *msg,
"Obtained key for for service \"%s\" in %lld ms, from %s",
t->s_svcname, delay, ct->cs_cwc->cwc_hostname);
pthread_mutex_lock(&t->s_stream_mutex);
ct->cs_keystate = CS_RESOLVED;
memcpy(ct->cs_cw, msg + 3, 16);
ct->cs_pending_cw_update = 1;
pthread_mutex_unlock(&t->s_stream_mutex);
}
}
/**
* Handle running reply
* global_lock is held
* cwc_mutex is held
*/
static int
cwc_running_reply(cwc_t *cwc, uint8_t msgtype, uint8_t *msg, int len)
@ -836,9 +852,9 @@ cwc_read(cwc_t *cwc, void *buf, size_t len, int timeout)
{
int r;
pthread_mutex_unlock(&global_lock);
pthread_mutex_unlock(&cwc_mutex);
r = tcp_read_timeout(cwc->cwc_fd, buf, len, timeout);
pthread_mutex_lock(&global_lock);
pthread_mutex_lock(&cwc_mutex);
if(cwc_must_break(cwc))
return ECONNABORTED;
@ -1031,23 +1047,23 @@ cwc_thread(void *aux)
struct timespec ts;
int attempts = 0;
pthread_mutex_lock(&global_lock);
pthread_mutex_lock(&cwc_mutex);
while(cwc->cwc_running) {
while(cwc->cwc_running && cwc->cwc_enabled == 0)
pthread_cond_wait(&cwc->cwc_cond, &global_lock);
pthread_cond_wait(&cwc->cwc_cond, &cwc_mutex);
snprintf(hostname, sizeof(hostname), "%s", cwc->cwc_hostname);
port = cwc->cwc_port;
tvhlog(LOG_INFO, "cwc", "Attemping to connect to %s:%d", hostname, port);
pthread_mutex_unlock(&global_lock);
pthread_mutex_unlock(&cwc_mutex);
fd = tcp_connect(hostname, port, errbuf, sizeof(errbuf), 10);
pthread_mutex_lock(&global_lock);
pthread_mutex_lock(&cwc_mutex);
if(fd == -1) {
attempts++;
@ -1092,7 +1108,7 @@ cwc_thread(void *aux)
"%s: Automatic connection attempt in in %d seconds",
cwc->cwc_hostname, d);
pthread_cond_timedwait(&cwc_config_changed, &global_lock, &ts);
pthread_cond_timedwait(&cwc_config_changed, &cwc_mutex, &ts);
}
tvhlog(LOG_INFO, "cwc", "%s destroyed", cwc->cwc_hostname);
@ -1110,7 +1126,7 @@ cwc_thread(void *aux)
free((void *)cwc->cwc_hostname);
free(cwc);
pthread_mutex_unlock(&global_lock);
pthread_mutex_unlock(&cwc_mutex);
return NULL;
}
@ -1164,7 +1180,7 @@ cwc_emm(uint8_t *data, int len)
{
cwc_t *cwc;
lock_assert(&global_lock);
pthread_mutex_lock(&cwc_mutex);
TAILQ_FOREACH(cwc, &cwcs, cwc_link) {
if(cwc->cwc_forward_emm && cwc->cwc_writer_running) {
@ -1184,11 +1200,18 @@ cwc_emm(uint8_t *data, int len)
case CARD_DRE:
cwc_emm_dre(cwc, data, len);
break;
case CARD_NAGRA:
cwc_emm_nagra(cwc, data, len);
break;
case CARD_NDS:
cwc_emm_nds(cwc, data, len);
break;
case CARD_UNKNOWN:
break;
}
}
}
pthread_mutex_unlock(&cwc_mutex);
}
@ -1447,7 +1470,7 @@ cwc_emm_viaccess(cwc_t *cwc, uint8_t *data, int mlen)
}
/**
*
* t->s_streaming_mutex is held
*/
static void
cwc_table_input(struct th_descrambler *td, struct service *t,
@ -1581,6 +1604,51 @@ cwc_emm_dre(cwc_t *cwc, uint8_t *data, int len)
cwc_send_msg(cwc, data, len, 0, 1);
}
void
cwc_emm_nagra(cwc_t *cwc, uint8_t *data, int len)
{
int match = 0;
unsigned char hexserial[4];
if (data[0] == 0x83) { // unique|shared
hexserial[0] = data[5];
hexserial[1] = data[4];
hexserial[2] = data[3];
hexserial[3] = data[6];
if (memcmp(hexserial, &cwc->cwc_ua[4], (data[7] == 0x10) ? 3 : 4) == 0)
match = 1;
}
else if (data[0] == 0x82) { // global
match = 1;
}
if (match)
cwc_send_msg(cwc, data, len, 0, 1);
}
void
cwc_emm_nds(cwc_t *cwc, uint8_t *data, int len)
{
int match = 0;
int i;
int serial_count = ((data[3] >> 4) & 3) + 1;
unsigned char emmtype = (data[3] & 0xC0) >> 6;
if (emmtype == 1 || emmtype == 2) { // unique|shared
for (i = 0; i < serial_count; i++) {
if (memcmp(&data[i * 4 + 4], &cwc->cwc_ua[4], 5 - emmtype) == 0) {
match = 1;
break;
}
}
}
else if (emmtype == 0) { // global
match = 1;
}
if (match)
cwc_send_msg(cwc, data, len, 0, 1);
}
/**
*
@ -1667,7 +1735,7 @@ cwc_descramble(th_descrambler_t *td, service_t *t, struct elementary_stream *st,
}
/**
* global_lock is held
* cwc_mutex is held
* s_stream_mutex is held
*/
static void
@ -1715,7 +1783,7 @@ cwc_find_stream_by_caid(service_t *t, int caid)
/**
* Check if our CAID's matches, and if so, link
*
* global_lock is held
* global_lock is held. Not that we care about that, but either way, it is.
*/
void
cwc_service_start(service_t *t)
@ -1724,7 +1792,7 @@ cwc_service_start(service_t *t)
cwc_service_t *ct;
th_descrambler_t *td;
lock_assert(&global_lock);
pthread_mutex_lock(&cwc_mutex);
TAILQ_FOREACH(cwc, &cwcs, cwc_link) {
if(cwc->cwc_caid == 0)
continue;
@ -1753,6 +1821,7 @@ cwc_service_start(service_t *t)
service_nicename(t), cwc->cwc_hostname, cwc->cwc_port);
}
pthread_mutex_unlock(&cwc_mutex);
}
@ -1762,10 +1831,11 @@ cwc_service_start(service_t *t)
static void
cwc_destroy(cwc_t *cwc)
{
lock_assert(&global_lock);
pthread_mutex_lock(&cwc_mutex);
TAILQ_REMOVE(&cwcs, cwc, cwc_link);
cwc->cwc_running = 0;
pthread_cond_signal(&cwc->cwc_cond);
pthread_mutex_unlock(&cwc_mutex);
}
@ -1890,8 +1960,6 @@ cwc_entry_update(void *opaque, const char *id, htsmsg_t *values, int maycreate)
if((cwc = cwc_entry_find(id, maycreate)) == NULL)
return NULL;
lock_assert(&global_lock);
if((s = htsmsg_get_str(values, "username")) != NULL) {
free(cwc->cwc_username);
cwc->cwc_username = strdup(s);
@ -2025,6 +2093,7 @@ static const dtable_class_t cwc_dtc = {
.dtc_record_delete = cwc_entry_delete,
.dtc_read_access = ACCESS_ADMIN,
.dtc_write_access = ACCESS_ADMIN,
.dtc_mutex = &cwc_mutex,
};
@ -2038,7 +2107,7 @@ cwc_init(void)
dtable_t *dt;
TAILQ_INIT(&cwcs);
pthread_mutex_init(&cwc_mutex, NULL);
pthread_cond_init(&cwc_config_changed, NULL);
dt = dtable_create(&cwc_dtc, "cwc", NULL);

View file

@ -40,6 +40,8 @@ typedef struct dtable_class {
int dtc_read_access;
int dtc_write_access;
pthread_mutex_t *dtc_mutex;
} dtable_class_t;

View file

@ -200,6 +200,11 @@ typedef struct th_dvb_adapter {
int tda_allpids_dmx_fd;
int tda_dump_fd;
uint32_t tda_last_fec;
int tda_unc_is_delta; /* 1 if we believe FE_READ_UNCORRECTED_BLOCKS
* return dela values */
} th_dvb_adapter_t;

View file

@ -40,6 +40,42 @@
#include "notify.h"
#include "dvr/dvr.h"
/**
* Return uncorrected block (since last read)
*
* Some adapters report delta themselfs, some return ever increasing value
* we need to deal with that ourselfs
*/
static int
dvb_fe_get_unc(th_dvb_adapter_t *tda)
{
uint32_t fec;
int d, r;
if(ioctl(tda->tda_fe_fd, FE_READ_UNCORRECTED_BLOCKS, &fec))
return 0; // read failed, just say 0
if(tda->tda_unc_is_delta)
return fec;
d = (int)fec - (int)tda->tda_last_fec;
if(d < 0) {
tda->tda_unc_is_delta = 1;
tvhlog(LOG_DEBUG, "dvb",
"%s: FE_READ_UNCORRECTED_BLOCKS returns delta updates (delta=%d)",
tda->tda_displayname, d);
return fec;
}
r = fec - tda->tda_last_fec;
tda->tda_last_fec = fec;
return r;
}
/**
* Front end monitor
*
@ -79,7 +115,7 @@ dvb_fe_monitor(void *aux)
if(status == -1) { /* We have a lock, don't hold off */
tda->tda_fe_monitor_hold = 0;
/* Reset FEC counter */
ioctl(tda->tda_fe_fd, FE_READ_UNCORRECTED_BLOCKS, &fec);
dvb_fe_get_unc(tda);
} else {
tda->tda_fe_monitor_hold--;
return;
@ -89,8 +125,7 @@ dvb_fe_monitor(void *aux)
if(status == -1) {
/* Read FEC counter (delta) */
if(ioctl(tda->tda_fe_fd, FE_READ_UNCORRECTED_BLOCKS, &fec))
fec = 0;
fec = dvb_fe_get_unc(tda);
tdmi->tdmi_fec_err_histogram[tdmi->tdmi_fec_err_ptr++] = fec;
if(tdmi->tdmi_fec_err_ptr == TDMI_FEC_ERR_HISTOGRAM_SIZE)
@ -277,7 +312,7 @@ static int check_frontend (int fe_fd, int dvr, int human_readable) {
(void)dvr;
fe_status_t status;
uint16_t snr, signal;
uint32_t ber, uncorrected_blocks;
uint32_t ber;
int timeout = 0;
do {
@ -292,15 +327,13 @@ static int check_frontend (int fe_fd, int dvr, int human_readable) {
snr = -2;
if (ioctl(fe_fd, FE_READ_BER, &ber) == -1)
ber = -2;
if (ioctl(fe_fd, FE_READ_UNCORRECTED_BLOCKS, &uncorrected_blocks) == -1)
uncorrected_blocks = -2;
if (human_readable) {
printf ("status %02x | signal %3u%% | snr %3u%% | ber %d | unc %d | ",
status, (signal * 100) / 0xffff, (snr * 100) / 0xffff, ber, uncorrected_blocks);
printf ("status %02x | signal %3u%% | snr %3u%% | ber %d | ",
status, (signal * 100) / 0xffff, (snr * 100) / 0xffff, ber);
} else {
printf ("status %02x | signal %04x | snr %04x | ber %08x | unc %08x | ",
status, signal, snr, ber, uncorrected_blocks);
printf ("status %02x | signal %04x | snr %04x | ber %08x | ",
status, signal, snr, ber);
}
if (status & FE_HAS_LOCK)
printf("FE_HAS_LOCK");

View file

@ -1104,6 +1104,9 @@ dvb_mux_copy(th_dvb_adapter_t *dst, th_dvb_mux_instance_t *tdmi_src)
if(t_src->s_svcname != NULL)
t_dst->s_svcname = strdup(t_src->s_svcname);
if(t_src->s_dvb_default_charset != NULL)
t_dst->s_dvb_default_charset = strdup(t_src->s_dvb_default_charset);
if(t_src->s_ch != NULL)
service_map_channel(t_dst, t_src->s_ch, 0);

View file

@ -218,6 +218,7 @@ static const dtable_class_t satconf_dtc = {
.dtc_record_delete = satconf_entry_delete,
.dtc_read_access = ACCESS_ADMIN,
.dtc_write_access = ACCESS_ADMIN,
.dtc_mutex = &global_lock,
};

View file

@ -74,7 +74,7 @@ dvb_conversion_init(void)
*/
int
dvb_get_string(char *dst, size_t dstlen, const uint8_t *src, size_t srclen)
dvb_get_string(char *dst, size_t dstlen, const uint8_t *src, size_t srclen, char *dvb_default_charset)
{
iconv_t ic;
int len;
@ -121,7 +121,15 @@ dvb_get_string(char *dst, size_t dstlen, const uint8_t *src, size_t srclen)
return -1;
default:
ic = convert_latin1;
if (dvb_default_charset != NULL && sscanf(dvb_default_charset, "ISO8859-%d", &i) > 0) {
if (i > 0 && i < 16) {
ic = convert_iso_8859[i];
} else {
ic = convert_latin1;
}
} else {
ic = convert_latin1;
}
break;
}
@ -175,14 +183,14 @@ dvb_get_string(char *dst, size_t dstlen, const uint8_t *src, size_t srclen)
int
dvb_get_string_with_len(char *dst, size_t dstlen,
const uint8_t *buf, size_t buflen)
const uint8_t *buf, size_t buflen, char *dvb_default_charset)
{
int l = buf[0];
if(l + 1 > buflen)
return -1;
if(dvb_get_string(dst, dstlen, buf + 1, l))
if(dvb_get_string(dst, dstlen, buf + 1, l, dvb_default_charset))
return -1;
return l + 1;

View file

@ -51,10 +51,10 @@
#define DVB_DESC_LOCAL_CHAN 0x83
int dvb_get_string(char *dst, size_t dstlen, const uint8_t *src,
const size_t srclen);
const size_t srclen, char *dvb_default_charset);
int dvb_get_string_with_len(char *dst, size_t dstlen,
const uint8_t *buf, size_t buflen);
const uint8_t *buf, size_t buflen, char *dvb_default_charset);
#define bcdtoint(i) ((((i & 0xf0) >> 4) * 10) + (i & 0x0f))

View file

@ -369,7 +369,8 @@ tdt_add(th_dvb_mux_instance_t *tdmi, struct dmx_sct_filter_params *fparams,
static int
dvb_desc_short_event(uint8_t *ptr, int len,
char *title, size_t titlelen,
char *desc, size_t desclen)
char *desc, size_t desclen,
char *dvb_default_charset)
{
int r;
@ -377,11 +378,11 @@ dvb_desc_short_event(uint8_t *ptr, int len,
return -1;
ptr += 3; len -= 3;
if((r = dvb_get_string_with_len(title, titlelen, ptr, len)) < 0)
if((r = dvb_get_string_with_len(title, titlelen, ptr, len, dvb_default_charset)) < 0)
return -1;
ptr += r; len -= r;
if((r = dvb_get_string_with_len(desc, desclen, ptr, len)) < 0)
if((r = dvb_get_string_with_len(desc, desclen, ptr, len, dvb_default_charset)) < 0)
return -1;
return 0;
@ -394,7 +395,8 @@ static int
dvb_desc_extended_event(uint8_t *ptr, int len,
char *desc, size_t desclen,
char *item, size_t itemlen,
char *text, size_t textlen)
char *text, size_t textlen,
char *dvb_default_charset)
{
int count = ptr[4], r;
uint8_t *localptr = ptr + 5, *items = localptr;
@ -434,7 +436,7 @@ dvb_desc_extended_event(uint8_t *ptr, int len,
count = localptr[0];
/* get text */
if((r = dvb_get_string_with_len(text, textlen, localptr, locallen)) < 0)
if((r = dvb_get_string_with_len(text, textlen, localptr, locallen, dvb_default_charset)) < 0)
return -1;
return 0;
@ -459,11 +461,11 @@ dvb_desc_service(uint8_t *ptr, int len, uint8_t *typep,
ptr++;
len--;
if((r = dvb_get_string_with_len(provider, providerlen, ptr, len)) < 0)
if((r = dvb_get_string_with_len(provider, providerlen, ptr, len, NULL)) < 0)
return -1;
ptr += r; len -= r;
if((r = dvb_get_string_with_len(name, namelen, ptr, len)) < 0)
if((r = dvb_get_string_with_len(name, namelen, ptr, len, NULL)) < 0)
return -1;
ptr += r; len -= r;
return 0;
@ -482,13 +484,7 @@ dvb_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
th_dvb_adapter_t *tda = tdmi->tdmi_adapter;
uint16_t serviceid;
int version;
uint8_t section_number;
uint8_t last_section_number;
uint16_t transport_stream_id;
uint16_t original_network_id;
uint8_t segment_last_section_number;
uint8_t last_table_id;
uint16_t event_id;
time_t start_time, stop_time;
@ -513,13 +509,13 @@ dvb_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
return -1;
serviceid = ptr[0] << 8 | ptr[1];
version = ptr[2] >> 1 & 0x1f;
section_number = ptr[3];
last_section_number = ptr[4];
// version = ptr[2] >> 1 & 0x1f;
// section_number = ptr[3];
// last_section_number = ptr[4];
transport_stream_id = ptr[5] << 8 | ptr[6];
original_network_id = ptr[7] << 8 | ptr[8];
segment_last_section_number = ptr[9];
last_table_id = ptr[10];
// original_network_id = ptr[7] << 8 | ptr[8];
// segment_last_section_number = ptr[9];
// last_table_id = ptr[10];
if((ptr[2] & 1) == 0) {
/* current_next_indicator == next, skip this */
@ -586,7 +582,8 @@ dvb_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
case DVB_DESC_SHORT_EVENT:
if(!dvb_desc_short_event(ptr, dlen,
title, sizeof(title),
desc, sizeof(desc))) {
desc, sizeof(desc),
t->s_dvb_default_charset)) {
changed |= epg_event_set_title(e, title);
changed |= epg_event_set_desc(e, desc);
}
@ -602,7 +599,8 @@ dvb_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
if(!dvb_desc_extended_event(ptr, dlen,
extdesc, sizeof(extdesc),
extitem, sizeof(extitem),
exttext, sizeof(exttext))) {
exttext, sizeof(exttext),
t->s_dvb_default_charset)) {
char language[4];
memcpy(language, &ptr[1], 3);
@ -640,15 +638,9 @@ dvb_sdt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
uint8_t tableid, void *opaque)
{
service_t *t;
int version;
uint8_t section_number;
uint8_t last_section_number;
uint16_t service_id;
uint16_t transport_stream_id;
uint16_t original_network_id;
int reserved;
int running_status, free_ca_mode;
int free_ca_mode;
int dllen;
uint8_t dtag, dlen;
@ -665,11 +657,11 @@ dvb_sdt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
if(tdmi->tdmi_transport_stream_id != transport_stream_id)
return -1;
version = ptr[2] >> 1 & 0x1f;
section_number = ptr[3];
last_section_number = ptr[4];
original_network_id = ptr[5] << 8 | ptr[6];
reserved = ptr[7];
// version = ptr[2] >> 1 & 0x1f;
// section_number = ptr[3];
// last_section_number = ptr[4];
// original_network_id = ptr[5] << 8 | ptr[6];
// reserved = ptr[7];
if((ptr[2] & 1) == 0) {
/* current_next_indicator == next, skip this */
@ -682,8 +674,8 @@ dvb_sdt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
while(len >= 5) {
service_id = ptr[0] << 8 | ptr[1];
reserved = ptr[2];
running_status = (ptr[3] >> 5) & 0x7;
// reserved = ptr[2];
// running_status = (ptr[3] >> 5) & 0x7;
free_ca_mode = (ptr[3] >> 4) & 0x1;
dllen = ((ptr[3] & 0x0f) << 8) | ptr[4];
@ -837,7 +829,6 @@ dvb_cat_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
uint8_t tableid, void *opaque)
{
int tag, tlen;
uint16_t caid;
uint16_t pid;
if((ptr[2] & 1) == 0) {
@ -854,7 +845,7 @@ dvb_cat_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
len -= 2;
switch(tag) {
case DVB_DESC_CA:
caid = ( ptr[0] << 8) | ptr[1];
// caid = ( ptr[0] << 8) | ptr[1];
pid = ((ptr[2] & 0x1f) << 8) | ptr[3];
if(pid == 0)
@ -956,7 +947,7 @@ dvb_table_sat_delivery(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
uint16_t tsid)
{
int freq, symrate;
uint16_t orbital_pos;
// uint16_t orbital_pos;
struct dvb_mux_conf dmc;
if(!tdmi->tdmi_adapter->tda_autodiscovery)
@ -976,7 +967,7 @@ dvb_table_sat_delivery(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
if(!freq)
return -1;
orbital_pos = bcdtoint(ptr[4]) * 100 + bcdtoint(ptr[5]);
// orbital_pos = bcdtoint(ptr[4]) * 100 + bcdtoint(ptr[5]);
symrate =
bcdtoint(ptr[7]) * 100000 + bcdtoint(ptr[8]) * 1000 +
@ -1122,7 +1113,7 @@ dvb_nit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
switch(tag) {
case DVB_DESC_NETWORK_NAME:
if(dvb_get_string(networkname, sizeof(networkname), ptr, tlen))
if(dvb_get_string(networkname, sizeof(networkname), ptr, tlen, NULL))
return -1;
if(strcmp(tdmi->tdmi_network ?: "", networkname))

View file

@ -234,11 +234,14 @@ dvb_transport_load(th_dvb_mux_instance_t *tdmi)
service_make_nicename(t);
psi_load_service_settings(c, t);
pthread_mutex_unlock(&t->s_stream_mutex);
s = htsmsg_get_str(c, "dvb_default_charset");
t->s_dvb_default_charset = s ? strdup(s) : NULL;
s = htsmsg_get_str(c, "channelname");
if(htsmsg_get_u32(c, "mapped", &u32))
u32 = 0;
if(s && u32)
service_map_channel(t, channel_find_by_name(s, 1, 0), 0);
}
@ -271,6 +274,9 @@ dvb_transport_save(service_t *t)
htsmsg_add_str(m, "channelname", t->s_ch->ch_name);
htsmsg_add_u32(m, "mapped", 1);
}
if(t->s_dvb_default_charset != NULL)
htsmsg_add_str(m, "dvb_default_charset", t->s_dvb_default_charset);
pthread_mutex_lock(&t->s_stream_mutex);
psi_save_service_settings(m, t);
@ -431,6 +437,9 @@ dvb_transport_build_msg(service_t *t)
if(t->s_ch != NULL)
htsmsg_add_str(m, "channelname", t->s_ch->ch_name);
if(t->s_dvb_default_charset != NULL)
htsmsg_add_str(m, "dvb_default_charset", t->s_dvb_default_charset);
return m;
}

View file

@ -242,6 +242,8 @@ dvr_entry_t *dvr_entry_create(const char *dvr_config_name,
epg_episode_t *ee, uint8_t content_type,
dvr_prio_t pri);
dvr_entry_t *dvr_entry_update(dvr_entry_t *de, const char* de_title, int de_start, int de_stop);
void dvr_init(void);
void dvr_autorec_init(void);
@ -274,6 +276,10 @@ void dvr_extra_time_pre_set(dvr_config_t *cfg, int d);
void dvr_extra_time_post_set(dvr_config_t *cfg, int d);
void dvr_entry_delete(dvr_entry_t *de);
void dvr_entry_cancel_delete(dvr_entry_t *de);
/**
* Query interface
*/

View file

@ -403,6 +403,7 @@ static const dtable_class_t autorec_dtc = {
.dtc_record_delete = autorec_record_delete,
.dtc_read_access = ACCESS_RECORDER,
.dtc_write_access = ACCESS_RECORDER,
.dtc_mutex = &global_lock,
};
/**

View file

@ -320,6 +320,20 @@ dvr_entry_create(const char *config_name,
}
/**
*
*/
static const char *
longest_string(const char *a, const char *b)
{
if(b == NULL)
return a;
if(a == NULL)
return b;
return strlen(a) > strlen(b) ? a : b;
}
/**
*
*/
@ -328,12 +342,19 @@ dvr_entry_create_by_event(const char *config_name,
event_t *e, const char *creator,
dvr_autorec_entry_t *dae, dvr_prio_t pri)
{
const char *desc = NULL;
if(e->e_channel == NULL || e->e_title == NULL)
return NULL;
// Try to find best description
desc = longest_string(e->e_desc, e->e_ext_desc);
desc = longest_string(desc, e->e_ext_item);
desc = longest_string(desc, e->e_ext_text);
return dvr_entry_create(config_name,
e->e_channel, e->e_start, e->e_stop,
e->e_title, e->e_desc, creator, dae, &e->e_episode,
e->e_title, desc, creator, dae, &e->e_episode,
e->e_content_type, pri);
}
@ -589,6 +610,26 @@ dvr_timer_expire(void *aux)
}
/**
*
*/
dvr_entry_t *
dvr_entry_update(dvr_entry_t *de, const char* de_title, int de_start, int de_stop)
{
de->de_title = strdup(de_title);
de->de_start = de_start;
de->de_stop = de_stop;
dvr_entry_save(de);
htsp_dvr_entry_update(de);
dvr_entry_notify(de);
tvhlog(LOG_INFO, "dvr", "\"%s\" on \"%s\": Updated Timer", de->de_title, de->de_channel->ch_name);
return de;
}
/**
*
@ -834,9 +875,9 @@ dvr_init(void)
}
}
dvr_autorec_init();
dvr_db_load();
dvr_autorec_init();
}
/**
@ -1160,3 +1201,73 @@ dvr_val2pri(dvr_prio_t v)
{
return val2str(v, priotab) ?: "invalid";
}
/**
*
*/
void
dvr_entry_delete(dvr_entry_t *de)
{
if(de->de_filename != NULL) {
if(unlink(de->de_filename) && errno != ENOENT)
tvhlog(LOG_WARNING, "dvr", "Unable to remove file '%s' from disk -- %s",
de->de_filename, strerror(errno));
/* Also delete directories, if they were created for the recording and if they are empty */
dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name);
char path[500];
snprintf(path, sizeof(path), "%s", cfg->dvr_storage);
if(cfg->dvr_flags & DVR_DIR_PER_TITLE || cfg->dvr_flags & DVR_DIR_PER_CHANNEL || cfg->dvr_flags & DVR_DIR_PER_DAY) {
char *p;
int l;
l = strlen(de->de_filename);
p = alloca(l + 1);
memcpy(p, de->de_filename, l);
p[l--] = 0;
for(; l >= 0; l--) {
if(p[l] == '/') {
p[l] = 0;
if(strncmp(p, cfg->dvr_storage, strlen(p)) == 0)
break;
if(rmdir(p) == -1)
break;
}
}
}
}
dvr_entry_remove(de);
}
/**
*
*/
void
dvr_entry_cancel_delete(dvr_entry_t *de)
{
switch(de->de_sched_state) {
case DVR_SCHEDULED:
dvr_entry_remove(de);
break;
case DVR_RECORDING:
de->de_dont_reschedule = 1;
dvr_stop_recording(de, SM_CODE_ABORTED);
case DVR_COMPLETED:
dvr_entry_delete(de);
break;
case DVR_MISSED_TIME:
dvr_entry_remove(de);
break;
default:
abort();
}
}

213
src/epg.c
View file

@ -16,6 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sys/mman.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
@ -25,9 +27,11 @@
#include "tvheadend.h"
#include "channels.h"
#include "settings.h"
#include "epg.h"
#include "dvr/dvr.h"
#include "htsp.h"
#include "htsmsg_binary.h"
#define EPG_MAX_AGE 86400
@ -49,16 +53,20 @@ e_ch_cmp(const event_t *a, const event_t *b)
*
*/
static void
epg_set_current(channel_t *ch, event_t *e)
epg_set_current(channel_t *ch, event_t *e, event_t *next)
{
if(ch->ch_epg_current == e)
if(next == NULL && e != NULL)
next = RB_NEXT(e, e_channel_link);
if(ch->ch_epg_current == e && ch->ch_epg_next == next)
return;
ch->ch_epg_current = e;
ch->ch_epg_next = next;
if(e != NULL)
gtimer_arm_abs(&ch->ch_epg_timer_current, epg_ch_check_current_event,
ch, MAX(e->e_stop, dispatch_clock + 1));
htsp_event_update(ch, e);
htsp_channgel_update_current(ch);
}
/**
@ -69,17 +77,16 @@ epg_ch_check_current_event(void *aux)
{
channel_t *ch = aux;
event_t skel, *e = ch->ch_epg_current;
if(e != NULL) {
if(e->e_start <= dispatch_clock && e->e_stop > dispatch_clock) {
epg_set_current(ch, e);
epg_set_current(ch, e, NULL);
return;
}
if((e = RB_NEXT(e, e_channel_link)) != NULL) {
if(e->e_start <= dispatch_clock && e->e_stop > dispatch_clock) {
epg_set_current(ch, e);
epg_set_current(ch, e, NULL);
return;
}
}
@ -87,22 +94,22 @@ epg_ch_check_current_event(void *aux)
e = epg_event_find_by_time(ch, dispatch_clock);
if(e != NULL) {
epg_set_current(ch, e);
epg_set_current(ch, e, NULL);
return;
}
epg_set_current(ch, NULL);
skel.e_start = dispatch_clock;
e = RB_FIND_GT(&ch->ch_epg_events, &skel, e_channel_link, e_ch_cmp);
if(e != NULL) {
gtimer_arm_abs(&ch->ch_epg_timer_current, epg_ch_check_current_event,
ch, MAX(e->e_start, dispatch_clock + 1));
epg_set_current(ch, NULL, e);
} else {
epg_set_current(ch, NULL, NULL);
}
}
/**
*
*/
@ -299,11 +306,13 @@ epg_remove_event_from_channel(channel_t *ch, event_t *e)
epg_event_unref(e);
if(ch->ch_epg_current == e) {
epg_set_current(ch, NULL);
epg_set_current(ch, NULL, n);
if(n != NULL)
gtimer_arm_abs(&ch->ch_epg_timer_current, epg_ch_check_current_event,
ch, n->e_start);
} else if(ch->ch_epg_next == e) {
epg_set_current(ch, ch->ch_epg_current, NULL);
}
if(wasfirst && (e = RB_FIRST(&ch->ch_epg_events)) != NULL) {
@ -527,12 +536,192 @@ epg_content_group_find_by_name(const char *name)
}
/*
/**
*
*/
static int
epg_event_create_by_msg(htsmsg_t *c, time_t now)
{
channel_t *ch;
event_t *e = NULL;
uint32_t ch_id = 0;
uint32_t e_start = 0;
uint32_t e_stop = 0;
int e_dvb_id = 0, v;
const char *s;
// Now create the event
if(htsmsg_get_u32(c, "ch_id", &ch_id))
return 0;
if((ch = channel_find_by_identifier(ch_id)) == NULL)
return 0;
if(htsmsg_get_u32(c, "start", &e_start))
return 0;
if(htsmsg_get_u32(c, "stop", &e_stop))
return 0;
if(e_stop < now)
return 0;
if(htsmsg_get_s32(c, "dvb_id", &e_dvb_id))
e_dvb_id = -1;
e = epg_event_create(ch, e_start, e_stop, e_dvb_id, NULL);
if((s = htsmsg_get_str(c, "title")) != NULL)
epg_event_set_title(e, s);
if((s = htsmsg_get_str(c, "desc")) != NULL)
epg_event_set_desc(e, s);
if(!htsmsg_get_s32(c, "season", &v))
e->e_episode.ee_season = v;
if(!htsmsg_get_s32(c, "episode", &v))
e->e_episode.ee_episode = v;
if(!htsmsg_get_s32(c, "part", &v))
e->e_episode.ee_part = v;
if((s = htsmsg_get_str(c, "epname")) != NULL)
tvh_str_set(&e->e_episode.ee_onscreen, s);
return 1;
}
/**
*
*/
static void
epg_load(void)
{
struct stat st;
int fd = hts_settings_open_file(0, "epgdb");
time_t now;
int created = 0;
time(&now);
if(fd == -1)
return;
if(fstat(fd, &st)) {
close(fd);
return;
}
uint8_t *mem = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
if(mem == MAP_FAILED) {
close(fd);
return;
}
const uint8_t *rp = mem;
size_t remain = st.st_size;
while(remain > 4) {
int msglen = (rp[0] << 24) | (rp[1] << 16) | (rp[2] << 8) | rp[3];
remain -= 4;
rp += 4;
if(msglen > remain) {
tvhlog(LOG_ERR, "EPG", "Malformed EPG database, skipping some data");
break;
}
htsmsg_t *m = htsmsg_binary_deserialize(rp, msglen, NULL);
created += epg_event_create_by_msg(m, now);
htsmsg_destroy(m);
rp += msglen;
remain -= msglen;
}
munmap(mem, st.st_size);
close(fd);
tvhlog(LOG_NOTICE, "EPG", "Injected %d event from disk database", created);
}
/**
*
*/
void
epg_init(void)
{
channel_t *ch;
epg_load();
RB_FOREACH(ch, &channel_name_tree, ch_name_link)
epg_ch_check_current_event(ch);
}
/**
* Save the epg on disk
*/
void
epg_save(void)
{
event_t *e;
int num_saved = 0;
size_t msglen;
void *msgdata;
channel_t *ch;
int fd = hts_settings_open_file(1, "epgdb");
RB_FOREACH(ch, &channel_name_tree, ch_name_link) {
RB_FOREACH(e, &ch->ch_epg_events, e_channel_link) {
if(!e->e_start || !e->e_stop)
continue;
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_u32(m, "ch_id", ch->ch_id);
htsmsg_add_u32(m, "start", e->e_start);
htsmsg_add_u32(m, "stop", e->e_stop);
if(e->e_title != NULL)
htsmsg_add_str(m, "title", e->e_title);
if(e->e_desc != NULL)
htsmsg_add_str(m, "desc", e->e_desc);
if(e->e_dvb_id)
htsmsg_add_u32(m, "dvb_id", e->e_dvb_id);
if(e->e_episode.ee_season)
htsmsg_add_u32(m, "season", e->e_episode.ee_season);
if(e->e_episode.ee_episode)
htsmsg_add_u32(m, "episode", e->e_episode.ee_episode);
if(e->e_episode.ee_part)
htsmsg_add_u32(m, "part", e->e_episode.ee_part);
if(e->e_episode.ee_onscreen)
htsmsg_add_str(m, "epname", e->e_episode.ee_onscreen);
int r = htsmsg_binary_serialize(m, &msgdata, &msglen, 0x10000);
htsmsg_destroy(m);
if(!r) {
ssize_t written = write(fd, msgdata, msglen);
int err = errno;
free(msgdata);
if(written != msglen) {
tvhlog(LOG_DEBUG, "epg", "Failed to store EPG on disk -- %s",
strerror(err));
close(fd);
hts_settings_remove("epgdb");
return;
}
}
num_saved++;
}
}
close(fd);
tvhlog(LOG_DEBUG, "EPG", "Stored EPG data for %d events on disk", num_saved);
}

View file

@ -20,6 +20,7 @@
#define EPG_H
#include "channels.h"
#include "settings.h"
@ -68,6 +69,8 @@ typedef struct event {
*/
void epg_init(void);
void epg_save(void);
/**
* All the epg_event_set_ function return 1 if it actually changed
* the EPG records. otherwise it returns 0.
@ -79,11 +82,9 @@ void epg_init(void);
* can combine multiple set()'s into one update
*
*/
int epg_event_set_title(event_t *e, const char *title)
__attribute__ ((warn_unused_result));
int epg_event_set_title(event_t *e, const char *title);
int epg_event_set_desc(event_t *e, const char *desc)
__attribute__ ((warn_unused_result));
int epg_event_set_desc(event_t *e, const char *desc);
int epg_event_set_ext_desc(event_t *e, int ext_dn, const char *desc)
__attribute__ ((warn_unused_result));

View file

@ -637,7 +637,7 @@ htsmsg_xml_parse_cd0(xmlparser_t *xp,
}
if(cc == NULL) {
if(*src <= 32) {
if(*src < 32) {
src++;
continue;
}

View file

@ -309,6 +309,8 @@ htsp_build_channel(channel_t *ch, const char *method)
htsmsg_add_u32(out, "eventId",
ch->ch_epg_current != NULL ? ch->ch_epg_current->e_id : 0);
htsmsg_add_u32(out, "nextEventId",
ch->ch_epg_next ? ch->ch_epg_next->e_id : 0);
LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link) {
ct = ctm->ctm_tag;
@ -497,21 +499,55 @@ htsp_method_addDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
dvr_entry_t *de;
dvr_entry_sched_state_t dvr_status;
const char *dvr_config_name;
if(htsmsg_get_u32(in, "eventId", &eventid))
return htsp_error("Missing argument 'eventId'");
if((e = epg_event_find_by_id(eventid)) == NULL)
return htsp_error("Event does not exist");
if((dvr_config_name = htsmsg_get_str(in, "configName")) == NULL)
dvr_config_name = "";
//create the dvr entry
de = dvr_entry_create_by_event(dvr_config_name,e,
htsp->htsp_username ?
htsp->htsp_username : "anonymous",
NULL, DVR_PRIO_NORMAL);
if(htsmsg_get_u32(in, "eventId", &eventid))
eventid = -1;
if ((e = epg_event_find_by_id(eventid)) == NULL)
{
uint32_t iChannelId, iStartTime, iStopTime, iPriority;
channel_t *channel;
const char *strTitle = NULL, *strDescription = NULL, *strCreator = NULL;
// no event found with this event id.
// check if there is at least a start time, stop time, channel id and title set
if (htsmsg_get_u32(in, "channelId", &iChannelId) ||
htsmsg_get_u32(in, "start", &iStartTime) ||
htsmsg_get_u32(in, "stop", &iStopTime) ||
(strTitle = htsmsg_get_str(in, "title")) == NULL)
{
// not enough info available to create a new entry
return htsp_error("Invalid arguments");
}
// invalid channel
if ((channel = channel_find_by_identifier(iChannelId)) == NULL)
return htsp_error("Channel does not exist");
// get the optional attributes
if (htsmsg_get_u32(in, "priority", &iPriority))
iPriority = DVR_PRIO_NORMAL;
if ((strDescription = htsmsg_get_str(in, "description")) == NULL)
strDescription = "";
if ((strCreator = htsmsg_get_str(in, "creator")) == NULL || strcmp(strCreator, "") == 0)
strCreator = htsp->htsp_username ? htsp->htsp_username : "anonymous";
// create the dvr entry
de = dvr_entry_create(dvr_config_name, channel, iStartTime, iStopTime, strTitle, strDescription, strCreator, NULL, NULL, 0, iPriority);
}
else
{
//create the dvr entry
de = dvr_entry_create_by_event(dvr_config_name,e,
htsp->htsp_username ?
htsp->htsp_username : "anonymous",
NULL, DVR_PRIO_NORMAL);
}
dvr_status = de != NULL ? de->de_sched_state : DVR_NOSTATE;
@ -535,15 +571,53 @@ htsp_method_addDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
}
/**
* delete a Dvrentry
* update a Dvrentry
*/
static htsmsg_t *
htsp_method_deleteDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
static htsmsg_t *
htsp_method_updateDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
{
htsmsg_t *out;
uint32_t dvrEntryId;
dvr_entry_t *de;
uint32_t start;
uint32_t stop;
const char *title = NULL;
if(htsmsg_get_u32(in, "id", &dvrEntryId))
return htsp_error("Missing argument 'id'");
if( (de = dvr_entry_find_by_id(dvrEntryId)) == NULL)
return htsp_error("id not found");
if(htsmsg_get_u32(in, "start", &start))
start = de->de_start;
if(htsmsg_get_u32(in, "stop", &stop))
stop = de->de_stop;
title = htsmsg_get_str(in, "title");
if (title == NULL)
title = de->de_title;
de = dvr_entry_update(de, title, start, stop);
//create response
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);
return out;
}
/**
* cancel a Dvrentry
*/
static htsmsg_t *
htsp_method_cancelDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
{
htsmsg_t *out;
uint32_t dvrEntryId;
dvr_entry_t *de;
if(htsmsg_get_u32(in, "id", &dvrEntryId))
return htsp_error("Missing argument 'id'");
@ -555,7 +629,32 @@ htsp_method_deleteDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
//create response
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);
return out;
}
/**
* delete a Dvrentry
*/
static htsmsg_t *
htsp_method_deleteDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
{
htsmsg_t *out;
uint32_t dvrEntryId;
dvr_entry_t *de;
if(htsmsg_get_u32(in, "id", &dvrEntryId))
return htsp_error("Missing argument 'id'");
if( (de = dvr_entry_find_by_id(dvrEntryId)) == NULL)
return htsp_error("id not found");
dvr_entry_cancel_delete(de);
//create response
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);
return out;
}
@ -925,6 +1024,8 @@ struct {
{ "unsubscribe", htsp_method_unsubscribe, ACCESS_STREAMING},
{ "subscriptionChangeWeight", htsp_method_change_weight, ACCESS_STREAMING},
{ "addDvrEntry", htsp_method_addDvrEntry, ACCESS_RECORDER},
{ "updateDvrEntry", htsp_method_updateDvrEntry, ACCESS_RECORDER},
{ "cancelDvrEntry", htsp_method_cancelDvrEntry, ACCESS_RECORDER},
{ "deleteDvrEntry", htsp_method_deleteDvrEntry, ACCESS_RECORDER},
{ "epgQuery", htsp_method_epgQuery, ACCESS_STREAMING},
@ -1147,8 +1248,13 @@ htsp_write_scheduler(void *aux)
/* ignore return value */
r = write(htsp->htsp_fd, dptr, dlen);
if(r != dlen)
tvhlog(LOG_INFO, "htsp", "%s: Write error -- %s",
htsp->htsp_logname, strerror(errno));
free(dptr);
pthread_mutex_lock(&htsp->htsp_out_mutex);
if(r != dlen)
break;
}
pthread_mutex_unlock(&htsp->htsp_out_mutex);
@ -1256,7 +1362,7 @@ htsp_async_send(htsmsg_t *m)
* global_lock is held
*/
void
htsp_event_update(channel_t *ch, event_t *e)
htsp_channgel_update_current(channel_t *ch)
{
htsmsg_t *m;
time_t now;
@ -1266,10 +1372,10 @@ htsp_event_update(channel_t *ch, event_t *e)
htsmsg_add_str(m, "method", "channelUpdate");
htsmsg_add_u32(m, "channelId", ch->ch_id);
if(e == NULL)
e = epg_event_find_by_time(ch, now);
htsmsg_add_u32(m, "eventId", e ? e->e_id : 0);
htsmsg_add_u32(m, "eventId",
ch->ch_epg_current ? ch->ch_epg_current->e_id : 0);
htsmsg_add_u32(m, "nextEventId",
ch->ch_epg_next ? ch->ch_epg_next->e_id : 0);
htsp_async_send(m);
}
@ -1525,6 +1631,18 @@ htsp_subscription_start(htsp_subscription_t *hs, const streaming_start_t *ss)
htsmsg_add_u32(c, "width", ssc->ssc_width);
if(ssc->ssc_height)
htsmsg_add_u32(c, "height", ssc->ssc_height);
if (ssc->ssc_aspect_num)
htsmsg_add_u32(c, "aspect_num", ssc->ssc_aspect_num);
if (ssc->ssc_aspect_den)
htsmsg_add_u32(c, "aspect_den", ssc->ssc_aspect_den);
}
if (SCT_ISAUDIO(ssc->ssc_type))
{
if (ssc->ssc_channels)
htsmsg_add_u32(c, "channels", ssc->ssc_channels);
if (ssc->ssc_sri)
htsmsg_add_u32(c, "rate", ssc->ssc_sri);
}
htsmsg_add_msg(streams, NULL, c);

View file

@ -24,7 +24,7 @@
void htsp_init(void);
void htsp_event_update(channel_t *ch, event_t *e);
void htsp_channgel_update_current(channel_t *ch);
void htsp_channel_add(channel_t *ch);
void htsp_channel_update(channel_t *ch);

View file

@ -123,7 +123,8 @@ static const char *
http_rc2str(int code)
{
switch(code) {
case HTTP_STATUS_OK: return "Ok";
case HTTP_STATUS_OK: return "OK";
case HTTP_STATUS_PARTIAL_CONTENT: return "Partial Content";
case HTTP_STATUS_NOT_FOUND: return "Not found";
case HTTP_STATUS_UNAUTHORIZED: return "Unauthorized";
case HTTP_STATUS_BAD_REQUEST: return "Bad request";
@ -149,8 +150,10 @@ static const char *cachemonths[12] = {
*/
void
http_send_header(http_connection_t *hc, int rc, const char *content,
int contentlen, const char *encoding, const char *location,
int maxage, const char *range)
int64_t contentlen,
const char *encoding, const char *location,
int maxage, const char *range,
const char *disposition)
{
struct tm tm0, *tm;
htsbuf_queue_t hdrs;
@ -204,12 +207,15 @@ http_send_header(http_connection_t *hc, int rc, const char *content,
htsbuf_qprintf(&hdrs, "Content-Type: %s\r\n", content);
if(contentlen > 0)
htsbuf_qprintf(&hdrs, "Content-Length: %d\r\n", contentlen);
htsbuf_qprintf(&hdrs, "Content-Length: %"PRId64"\r\n", contentlen);
if(range) {
htsbuf_qprintf(&hdrs, "Accept-Ranges: %s\r\n", "bytes");
htsbuf_qprintf(&hdrs, "Content-Range: %s\r\n", range);
}
if(disposition != NULL)
htsbuf_qprintf(&hdrs, "Content-Disposition: %s\r\n", disposition);
htsbuf_qprintf(&hdrs, "\r\n");
@ -226,7 +232,7 @@ http_send_reply(http_connection_t *hc, int rc, const char *content,
const char *encoding, const char *location, int maxage)
{
http_send_header(hc, rc, content, hc->hc_reply.hq_size,
encoding, location, maxage, 0);
encoding, location, maxage, 0, NULL);
if(hc->hc_no_output)
return;
@ -308,6 +314,15 @@ http_redirect(http_connection_t *hc, const char *location)
int
http_access_verify(http_connection_t *hc, int mask)
{
const char *ticket_id = http_arg_get(&hc->hc_req_args, "ticket");
if(!access_ticket_verify(ticket_id, hc->hc_url)) {
tvhlog(LOG_INFO, "HTTP", "%s: using ticket %s for %s",
inet_ntoa(hc->hc_peer->sin_addr), ticket_id,
hc->hc_url);
return 0;
}
return access_verify(hc->hc_username, hc->hc_password,
(struct sockaddr *)hc->hc_peer, mask);
}
@ -318,7 +333,7 @@ http_access_verify(http_connection_t *hc, int mask)
* Returns 1 if we should disconnect
*
*/
static void
static int
http_exec(http_connection_t *hc, http_path_t *hp, char *remain)
{
int err;
@ -328,8 +343,12 @@ http_exec(http_connection_t *hc, http_path_t *hp, char *remain)
else
err = hp->hp_callback(hc, remain, hp->hp_opaque);
if(err == -1)
return 1;
if(err)
http_error(hc, err);
return 0;
}
@ -353,8 +372,7 @@ http_cmd_get(http_connection_t *hc)
if(args != NULL)
http_parse_get_args(hc, args);
http_exec(hc, hp, remain);
return 0;
return http_exec(hc, hp, remain);
}
@ -416,8 +434,7 @@ http_cmd_post(http_connection_t *hc, htsbuf_queue_t *spill)
http_error(hc, HTTP_STATUS_NOT_FOUND);
return 0;
}
http_exec(hc, hp, remain);
return 0;
return http_exec(hc, hp, remain);
}

View file

@ -30,6 +30,7 @@ typedef struct http_arg {
} http_arg_t;
#define HTTP_STATUS_OK 200
#define HTTP_STATUS_PARTIAL_CONTENT 206
#define HTTP_STATUS_FOUND 302
#define HTTP_STATUS_BAD_REQUEST 400
#define HTTP_STATUS_UNAUTHORIZED 401
@ -80,11 +81,6 @@ typedef struct http_connection {
char *hc_username;
char *hc_password;
int hc_authenticated; /* Used by RTSP, it seems VLC does not
send authentication headers for each
command, so we just say that it's ok
if it has authenticated at least once */
struct config_head *hc_user_config;
int hc_no_output;
@ -116,8 +112,9 @@ void http_output_content(http_connection_t *hc, const char *content);
void http_redirect(http_connection_t *hc, const char *location);
void http_send_header(http_connection_t *hc, int rc, const char *content,
int contentlen, const char *encoding,
const char *location, int maxage, const char *range);
int64_t contentlen, const char *encoding,
const char *location, int maxage, const char *range,
const char *disposition);
typedef int (http_callback_t)(http_connection_t *hc,
const char *remain, void *opaque);

View file

@ -60,13 +60,18 @@ iptv_got_pat(const uint8_t *ptr, size_t len, void *aux)
len -= 8;
ptr += 8;
if(len < 4)
return;
while(len >= 4) {
prognum = ptr[0] << 8 | ptr[1];
pmt = (ptr[2] & 0x1f) << 8 | ptr[3];
prognum = ptr[0] << 8 | ptr[1];
pmt = (ptr[2] & 0x1f) << 8 | ptr[3];
t->s_pmt_pid = pmt;
if(prognum != 0) {
t->s_pmt_pid = pmt;
return;
}
ptr += 4;
len -= 4;
}
}
@ -119,7 +124,7 @@ iptv_ts_input(service_t *t, const uint8_t *tsb)
static void *
iptv_thread(void *aux)
{
int nfds, fd, r, j;
int nfds, fd, r, j, hlen;
uint8_t tsb[65536], *buf;
struct epoll_event ev;
service_t *t;
@ -153,7 +158,17 @@ iptv_thread(void *aux)
if((tsb[1] & 0x7f) != 33)
continue;
int hlen = (tsb[0] & 0xf) * 4 + 12;
hlen = (tsb[0] & 0xf) * 4 + 12;
if(tsb[0] & 0x10) {
// Extension (X bit) == true
if(r < hlen + 4)
continue; // Packet size < hlen + extension header
// Skip over extension header (last 2 bytes of header is length)
hlen += ((tsb[hlen + 2] << 8) | tsb[hlen + 3]) * 4;
}
if(r < hlen || (r - hlen) % 188 != 0)
continue;

View file

@ -357,6 +357,8 @@ main(int argc, char **argv)
pthread_mutex_lock(&global_lock);
time(&dispatch_clock);
trap_init(argv[0]);
/**
@ -433,6 +435,8 @@ main(int argc, char **argv)
mainloop();
epg_save();
tvhlog(LOG_NOTICE, "STOP", "Exiting HTS Tvheadend");
if(forkaway)

View file

@ -348,7 +348,7 @@ h264_decode_slice_header(elementary_stream_t *st, bitstream_t *bs, int *pkttype,
int *duration, int *isfield)
{
h264_private_t *p;
int slice_type, pps_id, sps_id, fnum;
int slice_type, pps_id, sps_id;
if((p = st->es_priv) == NULL)
return -1;
@ -378,7 +378,7 @@ h264_decode_slice_header(elementary_stream_t *st, bitstream_t *bs, int *pkttype,
if(p->sps[sps_id].max_frame_num_bits == 0)
return -1;
fnum = read_bits(bs, p->sps[sps_id].max_frame_num_bits);
skip_bits(bs, p->sps[sps_id].max_frame_num_bits);
int field = 0;
int timebase = 180000;

View file

@ -306,6 +306,9 @@ psi_desc_ca(service_t *t, const uint8_t *buffer, int size)
i += nanolen;
}
break;
case 0x4a00://DRECrypt
provid = size < 4 ? 0 : buffer[4];
break;
default:
provid = 0;
break;

View file

@ -465,6 +465,7 @@ service_destroy(service_t *t)
free(t->s_identifier);
free(t->s_svcname);
free(t->s_provider);
free(t->s_dvb_default_charset);
while((st = TAILQ_FIRST(&t->s_components)) != NULL) {
TAILQ_REMOVE(&t->s_components, st, es_link);
@ -503,6 +504,7 @@ service_create(const char *identifier, int type, int source_type)
t->s_refcount = 1;
t->s_enabled = 1;
t->s_pcr_last = PTS_UNSET;
t->s_dvb_default_charset = NULL;
TAILQ_INIT(&t->s_components);
streaming_pad_init(&t->s_streaming_pad);
@ -674,6 +676,23 @@ service_map_channel(service_t *t, channel_t *ch, int save)
t->s_config_save(t);
}
/**
*
*/
void
service_set_dvb_default_charset(service_t *t, const char *dvb_default_charset)
{
lock_assert(&global_lock);
if(t->s_dvb_default_charset != NULL && !strcmp(t->s_dvb_default_charset, dvb_default_charset))
return;
free(t->s_dvb_default_charset);
t->s_dvb_default_charset = strdup(dvb_default_charset);
t->s_config_save(t);
}
/**
*
*/
@ -720,6 +739,14 @@ service_is_tv(service_t *t)
t->s_servicetype == ST_AC_HDTV;
}
/**
*
*/
int
service_is_radio(service_t *t)
{
return t->s_servicetype == ST_RADIO;
}
/**
*

View file

@ -476,6 +476,12 @@ typedef struct service {
int64_t s_current_pts;
/**
* DVB default charset
* used to overide the default ISO6937 per service
*/
char *s_dvb_default_charset;
} service_t;
@ -516,6 +522,8 @@ const char *service_servicetype_txt(service_t *t);
int service_is_tv(service_t *t);
int service_is_radio(service_t *t);
void service_destroy(service_t *t);
void service_remove_subscriber(service_t *t, struct th_subscription *s,
@ -559,5 +567,6 @@ uint16_t service_get_encryption(service_t *t);
int service_get_signal_status(service_t *t, signal_status_t *status);
void service_set_dvb_default_charset(service_t *t, const char *dvb_default_charset);
#endif // SERVICE_H__

View file

@ -44,8 +44,8 @@ static pthread_cond_t serviceprobe_cond;
void
serviceprobe_enqueue(service_t *t)
{
if(!service_is_tv(t))
return; /* Don't even consider non-tv channels */
if(!service_is_tv(t) && !service_is_radio(t))
return; /* Don't even consider non-tv/non-radio channels */
if(t->s_sp_onqueue)
return;
@ -170,9 +170,11 @@ serviceprobe_thread(void *aux)
tvhlog(LOG_INFO, "serviceprobe", "%20s: mapped to channel \"%s\"",
t->s_svcname, t->s_svcname);
channel_tag_map(ch, channel_tag_find_by_name("TV channels", 1), 1);
tvhlog(LOG_INFO, "serviceprobe", "%20s: joined tag \"%s\"",
t->s_svcname, "TV channels");
if(service_is_tv(t)) {
channel_tag_map(ch, channel_tag_find_by_name("TV channels", 1), 1);
tvhlog(LOG_INFO, "serviceprobe", "%20s: joined tag \"%s\"",
t->s_svcname, "TV channels");
}
switch(t->s_servicetype) {
case ST_SDTV:
@ -183,6 +185,9 @@ serviceprobe_thread(void *aux)
case ST_AC_HDTV:
str = "HDTV";
break;
case ST_RADIO:
str = "Radio";
break;
default:
str = NULL;
}

View file

@ -284,3 +284,57 @@ hts_settings_remove(const char *pathfmt, ...)
return;
unlink(fullpath);
}
/**
*
*/
int
hts_settings_open_file(int for_write, const char *pathfmt, ...)
{
char path[256];
char fullpath[256];
int x, l;
va_list ap;
struct stat st;
char *n;
if(settingspath == NULL)
return -1;
va_start(ap, pathfmt);
vsnprintf(path, sizeof(path), pathfmt, ap);
va_end(ap);
n = path;
while(*n) {
if(*n == ':' || *n == '?' || *n == '*' || *n > 127 || *n < 32)
*n = '_';
n++;
}
l = strlen(path);
for(x = 0; x < l; x++) {
if(path[x] == '/') {
/* It's a directory here */
path[x] = 0;
snprintf(fullpath, sizeof(fullpath), "%s/%s", settingspath, path);
if(stat(fullpath, &st) && mkdir(fullpath, 0700)) {
tvhlog(LOG_ALERT, "settings", "Unable to create dir \"%s\": %s",
fullpath, strerror(errno));
return -1;
}
path[x] = '/';
}
}
snprintf(fullpath, sizeof(fullpath), "%s/%s", settingspath, path);
int flags = for_write ? O_CREAT | O_TRUNC | O_WRONLY : O_RDONLY;
return tvh_open(fullpath, flags, 0700);
}

View file

@ -32,4 +32,6 @@ void hts_settings_remove(const char *pathfmt, ...);
const char *hts_settings_get_root(void);
int hts_settings_open_file(int for_write, const char *pathfmt, ...);
#endif /* HTSSETTINGS_H__ */

View file

@ -181,18 +181,18 @@ int
tcp_write_queue(int fd, htsbuf_queue_t *q)
{
htsbuf_data_t *hd;
int l, r;
int l, r = 0;
while((hd = TAILQ_FIRST(&q->hq_q)) != NULL) {
TAILQ_REMOVE(&q->hq_q, hd, hd_link);
l = hd->hd_data_len - hd->hd_data_off;
r = write(fd, hd->hd_data + hd->hd_data_off, l);
r |= !!write(fd, hd->hd_data + hd->hd_data_off, l);
free(hd->hd_data);
free(hd);
}
q->hq_size = 0;
return 0;
return r;
}

View file

@ -375,7 +375,7 @@ tt_subtitle_deliver(service_t *t, elementary_stream_t *parent, tt_mag_t *ttm)
static void
tt_decode_line(service_t *t, elementary_stream_t *st, uint8_t *buf)
{
uint8_t mpag, line, s12, s34, c;
uint8_t mpag, line, s12, c;
int page, magidx, i;
tt_mag_t *ttm;
tt_private_t *ttp;
@ -416,7 +416,6 @@ tt_decode_line(service_t *t, elementary_stream_t *st, uint8_t *buf)
ttm->ttm_curpage = page;
s12 = ham_decode(buf[4], buf[5]);
s34 = ham_decode(buf[6], buf[7]);
c = ham_decode(buf[8], buf[9]);
ttm->ttm_lang = c >> 5;

View file

@ -30,6 +30,7 @@ char tvh_binshasum[20];
#include <string.h>
#include <signal.h>
#include <ucontext.h>
#include <limits.h>
#if ENABLE_EXECINFO
#include <execinfo.h>
#endif
@ -49,6 +50,7 @@ char tvh_binshasum[20];
static char line1[200];
static char tmpbuf[1024];
static char libs[1024];
static char self[PATH_MAX];
static void
sappend(char *buf, size_t l, const char *fmt, ...)
@ -62,10 +64,76 @@ sappend(char *buf, size_t l, const char *fmt, ...)
/**
*
*/
static int
add2lineresolve(const char *binary, void *addr, char *buf0, size_t buflen)
{
char *buf = buf0;
int fd[2], r, f;
const char *argv[5];
pid_t p;
char addrstr[30], *cp;
argv[0] = "addr2line";
argv[1] = "-e";
argv[2] = binary;
argv[3] = addrstr;
argv[4] = NULL;
snprintf(addrstr, sizeof(addrstr), "%p", (void *)((intptr_t)addr-1));
if(pipe(fd) == -1)
return -1;
if((p = fork()) == -1)
return -1;
if(p == 0) {
close(0);
close(2);
close(fd[0]);
dup2(fd[1], 1);
close(fd[1]);
if((f = open("/dev/null", O_RDWR)) == -1)
exit(1);
dup2(f, 0);
dup2(f, 2);
close(f);
execve("/usr/bin/addr2line", (char *const *) argv, environ);
exit(2);
}
close(fd[1]);
*buf = 0;
while(buflen > 1) {
r = read(fd[0], buf, buflen);
if(r < 1)
break;
buf += r;
buflen -= r;
*buf = 0;
cp = strchr(buf0, '\n');
if(cp != NULL) {
*cp = 0;
break;
}
}
close(fd[0]);
return 0;
}
static void
traphandler(int sig, siginfo_t *si, void *UC)
{
ucontext_t *uc = UC;
char buf[200];
#if ENABLE_EXECINFO
static void *frames[MAXFRAMES];
int nframes = backtrace(frames, MAXFRAMES);
@ -122,6 +190,11 @@ traphandler(int sig, siginfo_t *si, void *UC)
continue;
}
if(self[0] && !add2lineresolve(self, frames[i], buf, sizeof(buf))) {
tvhlog_spawn(LOG_ALERT, "CRASH", "%s %p", buf, frames[i]);
continue;
}
if(dli.dli_fname != NULL && dli.dli_fbase != NULL) {
tvhlog_spawn(LOG_ALERT, "CRASH", "%s %p",
dli.dli_fname,
@ -153,6 +226,7 @@ extern const char *htsversion_full;
void
trap_init(const char *ver)
{
int r;
uint8_t digest[20];
struct sigaction sa, old;
char path[256];
@ -160,6 +234,11 @@ trap_init(const char *ver)
SHA_CTX binsum;
int fd;
r = readlink("/proc/self/exe", self, sizeof(self) - 1);
if(r == -1)
self[0] = 0;
else
self[r] = 0;
if((fd = open("/proc/self/exe", O_RDONLY)) != -1) {
struct stat st;

View file

@ -152,7 +152,7 @@ extjs_root(http_connection_t *hc, const char *remain, void *opaque)
"\tpadding:0;\n"
"\tborder:0 none;\n"
"\toverflow:hidden;\n"
"\theight:100%;\n"
"\theight:100%%;\n"
"}\n"
"#systemlog {\n"
"\tfont:normal 12px courier; font-weight: bold;\n"
@ -230,7 +230,7 @@ extjs_tablemgr(http_connection_t *hc, const char *remain, void *opaque)
in = entries != NULL ? htsmsg_json_deserialize(entries) : NULL;
pthread_mutex_lock(&global_lock);
pthread_mutex_lock(dt->dt_dtc->dtc_mutex);
if(!strcmp(op, "create")) {
if(http_access_verify(hc, dt->dt_dtc->dtc_write_access))
@ -264,15 +264,15 @@ extjs_tablemgr(http_connection_t *hc, const char *remain, void *opaque)
} else {
bad:
pthread_mutex_unlock(&global_lock);
pthread_mutex_unlock(dt->dt_dtc->dtc_mutex);
return HTTP_STATUS_BAD_REQUEST;
noaccess:
pthread_mutex_unlock(&global_lock);
pthread_mutex_unlock(dt->dt_dtc->dtc_mutex);
return HTTP_STATUS_BAD_REQUEST;
}
pthread_mutex_unlock(&global_lock);
pthread_mutex_unlock(dt->dt_dtc->dtc_mutex);
if(in != NULL)
htsmsg_destroy(in);
@ -805,6 +805,19 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque)
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);
} else if(!strcmp(op, "deleteEntry")) {
s = http_arg_get(&hc->hc_req_args, "entryId");
if((de = dvr_entry_find_by_id(atoi(s))) == NULL) {
pthread_mutex_unlock(&global_lock);
return HTTP_STATUS_BAD_REQUEST;
}
dvr_entry_delete(de);
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);
} else if(!strcmp(op, "createEntry")) {
const char *config_name = http_arg_get(&hc->hc_req_args, "config_name");
@ -1103,6 +1116,7 @@ service_update(htsmsg_t *in)
uint32_t u32;
const char *id;
const char *chname;
const char *dvb_default_charset;
TAILQ_FOREACH(f, &in->hm_fields, hmf_link) {
if((c = htsmsg_get_map_by_field(f)) == NULL ||
@ -1117,6 +1131,9 @@ service_update(htsmsg_t *in)
if((chname = htsmsg_get_str(c, "channelname")) != NULL)
service_map_channel(t, channel_find_by_name(chname, 1, 0), 1);
if((dvb_default_charset = htsmsg_get_str(c, "dvb_default_charset")) != NULL)
service_set_dvb_default_charset(t, dvb_default_charset);
}
}
@ -1198,6 +1215,9 @@ extjs_servicedetails(http_connection_t *hc,
htsmsg_add_msg(out, "streams", streams);
if(t->s_dvb_default_charset != NULL)
htsmsg_add_str(out, "dvb_default_charset", t->s_dvb_default_charset);
pthread_mutex_unlock(&global_lock);
htsmsg_json_serialize(out, hq, 0);
@ -1422,6 +1442,7 @@ extjs_service_update(htsmsg_t *in)
uint32_t u32;
const char *id;
const char *chname;
const char *dvb_default_charset;
TAILQ_FOREACH(f, &in->hm_fields, hmf_link) {
if((c = htsmsg_get_map_by_field(f)) == NULL ||
@ -1436,6 +1457,9 @@ extjs_service_update(htsmsg_t *in)
if((chname = htsmsg_get_str(c, "channelname")) != NULL)
service_map_channel(t, channel_find_by_name(chname, 1, 0), 1);
if((dvb_default_charset = htsmsg_get_str(c, "dvb_default_charset")) != NULL)
service_set_dvb_default_charset(t, dvb_default_charset);
}
}

View file

@ -383,7 +383,7 @@ page_status(http_connection_t *hc,
if (DVR_SCHEDULED == de->de_sched_state)
{
timelefttemp = (int) (de->de_start - now) / 60; // output minutes
timelefttemp = (int) ((de->de_start - now) / 60) - de->de_start_extra; // output minutes
if (timelefttemp < timeleft)
timeleft = timelefttemp;
}
@ -398,19 +398,23 @@ page_status(http_connection_t *hc,
"<date>%02d/%02d/%02d</date>"
"<time>%02d:%02d</time>"
"<unixtime>%d</unixtime>"
"<extra_start>%d</extra_start>"
"</start>"
"<stop>"
"<date>%02d/%02d/%02d</date>"
"<time>%02d:%02d</time>"
"<unixtime>%d</unixtime>"
"<extra_stop>%d</extra_stop>"
"</stop>"
"<title>%s</title>",
a.tm_year + 1900, a.tm_mon, a.tm_mday,
a.tm_hour, a.tm_min,
de->de_start,
de->de_start_extra,
b.tm_year+1900, b.tm_mon, b.tm_mday,
b.tm_hour, b.tm_min,
de->de_stop,
de->de_stop_extra,
de->de_title);
rstatus = val2str(de->de_sched_state, recstatustxt);

View file

@ -30,6 +30,7 @@ tvheadend.channels = new Ext.data.JsonStore({
fields: ['name', 'chid', 'xmltvsrc', 'tags', 'ch_icon',
'epg_pre_start', 'epg_post_end', 'number'],
id: 'chid',
sortInfo: { field: 'number', direction: "ASC" },
url: "channels",
baseParams: {
op: 'list'
@ -163,7 +164,7 @@ tvheadend.chconf = function()
dataIndex: 'chid',
width: 50,
renderer: function(value, metadata, record, row, col, store) {
url = 'stream/channelid/' + value
url = 'playlist/channelid/' + value
return "<a href=\"javascript:tvheadend.VLC('"+url+"')\">Play</a>"
}
},

View file

@ -371,7 +371,7 @@ tvheadend.dvb_services = function(adapterId) {
dataIndex: 'id',
width: 50,
renderer: function(value, metadata, record, row, col, store) {
url = makeRTSPprefix() + 'service/' + value
url = 'stream/service/' + value
return '<a href="'+url+'">Play</a>'
}
},
@ -394,6 +394,45 @@ tvheadend.dvb_services = function(adapterId) {
displayField:'name'
})
},
{
header: "DVB default charset",
dataIndex: 'dvb_default_charset',
width: 200,
renderer: function(value, metadata, record, row, col, store) {
return value ? value :
'<span class="tvh-grid-unset">ISO6937</span>';
},
editor: new fm.ComboBox({
mode: 'local',
store: new Ext.data.SimpleStore({
fields: ['key','value'],
data: [
['ISO6937','default'],
['ISO6937','ISO6937'],
['ISO8859-1','ISO8859-1'],
['ISO8859-2','ISO8859-2'],
['ISO8859-3','ISO8859-3'],
['ISO8859-4','ISO8859-4'],
['ISO8859-5','ISO8859-5'],
['ISO8859-6','ISO8859-6'],
['ISO8859-7','ISO8859-7'],
['ISO8859-8','ISO8859-8'],
['ISO8859-9','ISO8859-9'],
['ISO8859-10','ISO8859-10'],
['ISO8859-11','ISO8859-11'],
['ISO8859-12','ISO8859-12'],
['ISO8859-13','ISO8859-13'],
['ISO8859-14','ISO8859-14'],
['ISO8859-15','ISO8859-15']
]
}),
typeAhead: true,
lazyRender: true,
triggerAction: 'all',
displayField:'value',
valueField:'key'
})
},
{
header: "Type",
dataIndex: 'type',
@ -440,7 +479,7 @@ tvheadend.dvb_services = function(adapterId) {
root: 'entries',
fields: Ext.data.Record.create([
'id', 'enabled', 'type', 'sid', 'pmt', 'pcr',
'svcname', 'network', 'provider', 'mux', 'channelname'
'svcname', 'network', 'provider', 'mux', 'channelname', 'dvb_default_charset'
]),
url: "dvb/services/" + adapterId,
autoLoad: true,

View file

@ -69,7 +69,7 @@ tvheadend.dvrDetails = function(entry) {
content += '<div class="x-epg-meta">' +
'<a href="' + entry.url + '" target="_blank">Download</a> '+
parseInt(entry.filesize/1000000) + ' MB<br>' +
"<a href=\"javascript:tvheadend.VLC('" + entry.url + "')\">Play</a>" +
"<a href=\"javascript:tvheadend.VLC('dvrfile/" + entry.id + "')\">Play</a>" +
'</div>';
}
@ -98,6 +98,13 @@ tvheadend.dvrDetails = function(entry) {
text: "Abort recording"
});
break;
case 'completedError':
case 'completed':
win.addButton({
handler: deleteEvent,
text: "Delete recording"
});
break;
}
@ -120,6 +127,21 @@ tvheadend.dvrDetails = function(entry) {
});
}
function deleteEvent() {
Ext.Ajax.request({
url: 'dvr',
params: {entryId: entry.id, op: 'deleteEntry'},
success:function(response, options) {
win.close();v
},
failure:function(response, options) {
Ext.MessageBox.alert('DVR', response.statusText);
}
});
}
}
/**

View file

@ -31,19 +31,29 @@ tvheadend.help = function(title, pagename) {
* Displays a mediaplayer using VLC plugin
*/
tvheadend.VLC = function(url) {
function randomString() {
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
var string_length = 8;
var randomstring = '';
for (var i=0; i<string_length; i++) {
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum,rnum+1);
}
return randomstring;
}
var vlc = document.createElement('embed');
vlc.setAttribute('type', 'application/x-vlc-plugin');
vlc.setAttribute('pluginspage', 'http://www.videolan.org');
vlc.setAttribute('version', 'version="VideoLAN.VLCPlugin.2');
vlc.setAttribute('width', '507');
vlc.setAttribute('height', '384');
vlc.setAttribute('autoplay', 'yes');
vlc.setAttribute('id', 'vlc');
if(url) {
vlc.setAttribute('src', url);
} else {
vlc.style.display = 'none';
}
vlc.setAttribute('version', 'VideoLAN.VLCPlugin.2');
vlc.setAttribute('width', '100%');
vlc.setAttribute('height', '100%');
vlc.setAttribute('autoplay', 'no');
vlc.setAttribute('id', randomString());
var missingPlugin = document.createElement('div');
missingPlugin.style.display = 'none';
missingPlugin.style.padding = '5px';
var missingPlugin = document.createElement('div');
missingPlugin.style.display = 'none';
@ -61,32 +71,25 @@ tvheadend.VLC = function(url) {
});
selectChannel.on('select', function(c, r) {
var url = 'stream/channelid/' + r.data.chid;
var chName = r.data.name;
if (!chName.length) {
chName = 'the channel';
}
var streamurl = 'stream/channelid/' + r.data.chid;
var playlisturl = 'playlist/channelid/' + r.data.chid;
// if the player was initialised, but not yet shown, make it visible
if (vlc.playlist && (vlc.style.display == 'none'))
vlc.style.display = 'block';
if(!vlc.playlist || vlc.playlist == 'undefined') {
var chUrl = '<a href="' + url + '">' + chName + '</a>';
missingPlugin.innerHTML = '<p>You are missing a plugin for your browser.</p>';
missingPlugin.innerHTML += '<p>You can still watch ' + chUrl + ' using an external player.</p>';
missingPlugin.style.display = 'block';
return;
missingPlugin.innerHTML = '<p>Embedded player could not be started. <br> You are probably missing VLC Mozilla plugin for your browser.</p>';
missingPlugin.innerHTML += '<p><a href="' + playlisturl + '">M3U Playlist</a></p>';
missingPlugin.innerHTML += '<p><a href="' + streamurl + '">Direct URL</a></p>';
}
vlc.style.display = 'block';
if(vlc.playlist && vlc.playlist.isPlaying) {
else {
vlc.playlist.stop();
}
if(vlc.playlist && vlc.playlist.items.count) {
vlc.playlist.items.clear();
}
vlc.playlist.add(url, chName, "");
vlc.playlist.play();
vlc.audio.volume = slider.getValue();
vlc.playlist.items.clear();
vlc.playlist.add(streamurl);
vlc.playlist.playItem(0);
vlc.audio.volume = slider.getValue();
}
}
);
@ -117,7 +120,7 @@ tvheadend.VLC = function(url) {
height: 384 + 56,
constrainHeader: true,
iconCls: 'eye',
resizable: false,
resizable: true,
tbar: [
selectChannel,
'-',
@ -143,9 +146,8 @@ tvheadend.VLC = function(url) {
iconCls: 'control_stop',
tooltip: 'Stop',
handler: function() {
if(vlc.playlist && vlc.playlist.items.count) {
if(vlc.playlist) {
vlc.playlist.stop();
vlc.style.display = 'none';
}
}
},
@ -154,9 +156,12 @@ tvheadend.VLC = function(url) {
iconCls: 'control_fullscreen',
tooltip: 'Fullscreen',
handler: function() {
if(vlc.playlist && vlc.playlist.isPlaying) {
if(vlc.playlist && vlc.playlist.isPlaying && (vlc.VersionInfo.substr(0,3) != '1.1')) {
vlc.video.toggleFullscreen();
}
else if (vlc.VersionInfo.substr(0,3) == '1.1') {
alert('Fullscreen mode is broken in VLC 1.1.x');
}
}
},
'-',
@ -169,12 +174,45 @@ tvheadend.VLC = function(url) {
items: [vlc, missingPlugin]
});
win.on('render', function() {
win.on('beforeShow', function() {
win.getTopToolbar().add(slider);
win.getTopToolbar().add(new Ext.Toolbar.Spacer());
win.getTopToolbar().add(new Ext.Toolbar.Spacer());
win.getTopToolbar().add(new Ext.Toolbar.Spacer());
win.getTopToolbar().add(sliderLabel);
// check if vlc plugin wasn't initialised correctly
if(!vlc.playlist || (vlc.playlist == 'undefined')) {
vlc.style.display = 'none';
missingPlugin.innerHTML = '<p>Embedded player could not be started. <br> You are probably missing VLC Mozilla plugin for your browser.</p>';
if (url) {
var channelid = url.substr(url.lastIndexOf('/'));
var streamurl = 'stream/channelid/' + channelid;
var playlisturl = 'playlist/channelid/' + channelid;
missingPlugin.innerHTML += '<p><a href="' + playlisturl + '">M3U Playlist</a></p>';
missingPlugin.innerHTML += '<p><a href="' + streamurl + '">Direct URL</a></p>';
}
missingPlugin.style.display = 'block';
}
else {
// check if the window was opened with an url-parameter
if (url) {
vlc.playlist.items.clear();
vlc.playlist.add(url);
vlc.playlist.playItem(0);
//enable yadif2x deinterlacer for vlc > 1.1
var point1 = vlc.VersionInfo.indexOf('.');
var point2 = vlc.VersionInfo.indexOf('.', point1+1);
var majVersion = vlc.VersionInfo.substring(0,point1);
var minVersion = vlc.VersionInfo.substring(point1+1,point2);
if ((majVersion >= 1) && (minVersion >= 1))
vlc.video.deinterlace.enable("yadif2x");
}
}
});
win.show();

View file

@ -115,41 +115,12 @@ page_static_file(http_connection_t *hc, const char *remain, void *opaque)
return 404;
}
http_send_header(hc, 200, content, st.st_size, NULL, NULL, 10, 0);
http_send_header(hc, 200, content, st.st_size, NULL, NULL, 10, 0, NULL);
sendfile(hc->hc_fd, fd, NULL, st.st_size);
close(fd);
return 0;
}
/**
* Playlist (.pls format) for the rtsp streams
*/
static int
page_rtsp_playlist(http_connection_t *hc, const char *remain, void *opaque)
{
htsbuf_queue_t *hq = &hc->hc_reply;
channel_t *ch = NULL;
int i = 0;
const char *host = http_arg_get(&hc->hc_args, "Host");
htsbuf_qprintf(hq, "[playlist]\n");
scopedgloballock();
RB_FOREACH(ch, &channel_name_tree, ch_name_link) {
i++;
htsbuf_qprintf(hq, "File%d=rtsp://%s/channelid/%d\n", i, host, ch->ch_id);
htsbuf_qprintf(hq, "Title%d=%s\n", i, ch->ch_name);
htsbuf_qprintf(hq, "Length%d=%d\n\n", i, -1);
}
htsbuf_qprintf(hq, "NumberOfEntries=%d\n\n", i);
htsbuf_qprintf(hq, "Version=2");
http_output_content(hc, "audio/x-scpls; charset=UTF-8");
return 0;
}
/**
* HTTP stream loop
*/
@ -219,6 +190,10 @@ http_stream_run(http_connection_t *hc, streaming_queue_t *sq)
pat_ts[4] = 0x00;
run = (write(hc->hc_fd, pat_ts, 188) == 188);
if(!run) {
break;
}
//Send PMT
memset(pmt_ts, 0xff, 188);
psi_build_pmt(ss, pmt_ts+5, 183, pcrpid);
@ -262,12 +237,15 @@ http_stream_run(http_connection_t *hc, streaming_queue_t *sq)
}
/**
* Playlist with http streams (.m3u format)
* Output a playlist with http streams for a channel (.m3u format)
*/
static void
http_stream_playlist(http_connection_t *hc)
static int
http_stream_playlist(http_connection_t *hc, channel_t *channel)
{
htsbuf_queue_t *hq = &hc->hc_reply;
char buf[255];
const char *ticket_id = NULL;
channel_t *ch = NULL;
const char *host = http_arg_get(&hc->hc_args, "Host");
@ -275,13 +253,148 @@ http_stream_playlist(http_connection_t *hc)
htsbuf_qprintf(hq, "#EXTM3U\n");
RB_FOREACH(ch, &channel_name_tree, ch_name_link) {
htsbuf_qprintf(hq, "#EXTINF:-1,%s\n", ch->ch_name);
htsbuf_qprintf(hq, "http://%s/stream/channelid/%d\n", host, ch->ch_id);
if (channel == NULL || ch == channel) {
htsbuf_qprintf(hq, "#EXTINF:-1,%s\n", ch->ch_name);
snprintf(buf, sizeof(buf), "/stream/channelid/%d", ch->ch_id);
ticket_id = access_ticket_create(buf);
htsbuf_qprintf(hq, "http://%s%s?ticket=%s\n", host, buf, ticket_id);
}
}
http_output_content(hc, "application/x-mpegURL");
http_output_content(hc, "audio/x-mpegurl");
pthread_mutex_unlock(&global_lock);
return 0;
}
/**
* Output a playlist with a http stream for a dvr entry (.m3u format)
*/
static int
http_dvr_playlist(http_connection_t *hc, int dvr_id)
{
htsbuf_queue_t *hq = &hc->hc_reply;
char buf[255];
const char *ticket_id = NULL;
dvr_entry_t *de = NULL;
time_t durration = 0;
off_t fsize = 0;
int bandwidth = 0;
const char *host = http_arg_get(&hc->hc_args, "Host");
pthread_mutex_lock(&global_lock);
de = dvr_entry_find_by_id(dvr_id);
if(de) {
durration = de->de_stop - de->de_start;
durration += (de->de_stop_extra + de->de_start_extra)*60;
fsize = dvr_get_filesize(de);
if(fsize) {
bandwidth = ((8*fsize) / (durration*1024.0));
htsbuf_qprintf(hq, "#EXTM3U\n");
htsbuf_qprintf(hq, "#EXTINF:%d,%s\n", durration, de->de_title);
htsbuf_qprintf(hq, "#EXT-X-TARGETDURATION:%d\n", durration);
htsbuf_qprintf(hq, "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=%d\n", bandwidth);
snprintf(buf, sizeof(buf), "/dvrfile/%d", dvr_id);
ticket_id = access_ticket_create(buf);
htsbuf_qprintf(hq, "http://%s%s?ticket=%s\n", host, buf, ticket_id);
http_output_content(hc, "application/x-mpegURL");
}
}
pthread_mutex_unlock(&global_lock);
if(!de || !fsize) {
http_error(hc, HTTP_STATUS_BAD_REQUEST);
return HTTP_STATUS_BAD_REQUEST;
} else {
return 0;
}
}
/**
* Handle requests for playlists
*/
static int
page_http_playlist(http_connection_t *hc, const char *remain, void *opaque)
{
char *components[2];
channel_t *ch = NULL;
int dvr_id = -1;
if(remain == NULL) {
http_stream_playlist(hc, NULL);
return 0;
}
if(http_tokenize((char *)remain, components, 2, '/') != 2) {
http_error(hc, HTTP_STATUS_BAD_REQUEST);
return HTTP_STATUS_BAD_REQUEST;
}
http_deescape(components[1]);
pthread_mutex_lock(&global_lock);
if(!strcmp(components[0], "channelid")) {
ch = channel_find_by_identifier(atoi(components[1]));
} else if(!strcmp(components[0], "channel")) {
ch = channel_find_by_name(components[1], 0, 0);
} else if(!strcmp(components[0], "dvrid")) {
dvr_id = atoi(components[1]);
}
pthread_mutex_unlock(&global_lock);
if(ch)
return http_stream_playlist(hc, ch);
else if(dvr_id >= 0)
return http_dvr_playlist(hc, dvr_id);
else {
http_error(hc, HTTP_STATUS_BAD_REQUEST);
return HTTP_STATUS_BAD_REQUEST;
}
}
/**
* Subscribes to a service and starts the streaming loop
*/
static int
http_stream_service(http_connection_t *hc, service_t *service)
{
streaming_queue_t sq;
th_subscription_t *s;
pthread_mutex_lock(&global_lock);
streaming_queue_init(&sq, ~SMT_TO_MASK(SUBSCRIPTION_RAW_MPEGTS));
s = subscription_create_from_service(service,
"HTTP", &sq.sq_st,
SUBSCRIPTION_RAW_MPEGTS);
pthread_mutex_unlock(&global_lock);
//We won't get a START command, send http-header here.
http_output_content(hc, "video/mp2t");
http_stream_run(hc, &sq);
pthread_mutex_lock(&global_lock);
subscription_unsubscribe(s);
pthread_mutex_unlock(&global_lock);
streaming_queue_deinit(&sq);
return 0;
}
/**
@ -319,23 +432,20 @@ http_stream_channel(http_connection_t *hc, channel_t *ch)
/**
* Handle the http request. http://tvheadend/stream/channelid/<chid>
* http://tvheadend/stream/channel/<chname>
* http://tvheadend/stream/service/<servicename>
*/
static int
http_stream(http_connection_t *hc, const char *remain, void *opaque)
{
char *components[2];
channel_t *ch = NULL;
if(http_access_verify(hc, ACCESS_STREAMING)) {
http_error(hc, HTTP_STATUS_UNAUTHORIZED);
return HTTP_STATUS_UNAUTHORIZED;
}
service_t *service = NULL;
hc->hc_keep_alive = 0;
if(remain == NULL) {
http_stream_playlist(hc);
return 0;
http_error(hc, HTTP_STATUS_BAD_REQUEST);
return HTTP_STATUS_BAD_REQUEST;
}
if(http_tokenize((char *)remain, components, 2, '/') != 2) {
@ -351,16 +461,20 @@ http_stream(http_connection_t *hc, const char *remain, void *opaque)
ch = channel_find_by_identifier(atoi(components[1]));
} else if(!strcmp(components[0], "channel")) {
ch = channel_find_by_name(components[1], 0, 0);
} else if(!strcmp(components[0], "service")) {
service = service_find_by_identifier(components[1]);
}
pthread_mutex_unlock(&global_lock);
if(ch == NULL) {
if(ch != NULL) {
return http_stream_channel(hc, ch);
} else if(service != NULL) {
return http_stream_service(hc, service);
} else {
http_error(hc, HTTP_STATUS_BAD_REQUEST);
return HTTP_STATUS_BAD_REQUEST;
}
return http_stream_channel(hc, ch);
}
@ -373,7 +487,6 @@ page_static_bundle(http_connection_t *hc, const char *remain, void *opaque)
const struct filebundle *fb = opaque;
const struct filebundle_entry *fbe;
const char *content = NULL, *postfix;
int n;
if(remain == NULL)
return 404;
@ -389,9 +502,11 @@ page_static_bundle(http_connection_t *hc, const char *remain, void *opaque)
if(!strcmp(fbe->filename, remain)) {
http_send_header(hc, 200, content, fbe->size,
fbe->original_size == -1 ? NULL : "gzip", NULL, 10, 0);
fbe->original_size == -1 ? NULL : "gzip", NULL, 10, 0,
NULL);
/* ignore return value */
n = write(hc->hc_fd, fbe->data, fbe->size);
if(write(hc->hc_fd, fbe->data, fbe->size) != fbe->size)
return -1;
return 0;
}
}
@ -405,24 +520,21 @@ page_static_bundle(http_connection_t *hc, const char *remain, void *opaque)
static int
page_dvrfile(http_connection_t *hc, const char *remain, void *opaque)
{
int fd;
int fd, i;
struct stat st;
const char *content = NULL, *postfix, *range;
dvr_entry_t *de;
char *fname;
char range_buf[255];
off_t content_len, file_start, file_end;
char disposition[256];
off_t content_len, file_start, file_end, chunk;
ssize_t r;
if(remain == NULL)
return 404;
pthread_mutex_lock(&global_lock);
if(http_access_verify(hc, ACCESS_RECORDER)) {
pthread_mutex_unlock(&global_lock);
return HTTP_STATUS_UNAUTHORIZED;
}
de = dvr_entry_find_by_id(atoi(remain));
if(de == NULL || de->de_filename == NULL) {
pthread_mutex_unlock(&global_lock);
@ -468,19 +580,42 @@ page_dvrfile(http_connection_t *hc, const char *remain, void *opaque)
content_len = file_end - file_start+1;
sprintf(range_buf, "bytes %"PRId64"-%"PRId64"/%"PRId64"", file_start, file_end, st.st_size);
sprintf(range_buf, "bytes %"PRId64"-%"PRId64"/%"PRId64"",
file_start, file_end, st.st_size);
if(file_start > 0)
lseek(fd, file_start, SEEK_SET);
http_send_header(hc, 200, content, content_len, NULL, NULL, 10, range_buf);
sendfile(hc->hc_fd, fd, NULL, content_len);
close(fd);
if(de->de_title != NULL) {
snprintf(disposition, sizeof(disposition),
"attachment; filename=%s.mkv", de->de_title);
i = 20;
while(disposition[i]) {
if(disposition[i] == ' ')
disposition[i] = '_';
i++;
}
} else {
disposition[0] = 0;
}
if(range)
return 206;
else
return 0;
http_send_header(hc, range ? HTTP_STATUS_PARTIAL_CONTENT : HTTP_STATUS_OK,
content, content_len, NULL, NULL, 10,
range ? range_buf : NULL,
disposition[0] ? disposition : NULL);
if(!hc->hc_no_output) {
while(content_len > 0) {
chunk = MIN(1024 * 1024 * 1024, content_len);
r = sendfile(hc->hc_fd, fd, NULL, chunk);
if(r == -1)
return -1;
content_len -= r;
}
}
close(fd);
return 0;
}
@ -546,7 +681,7 @@ webui_init(const char *contentpath)
http_path_add("/dvrfile", NULL, page_dvrfile, ACCESS_WEB_INTERFACE);
http_path_add("/favicon.ico", NULL, favicon, ACCESS_WEB_INTERFACE);
http_path_add("/channels.pls", NULL, page_rtsp_playlist, ACCESS_WEB_INTERFACE);
http_path_add("/playlist", NULL, page_http_playlist, ACCESS_WEB_INTERFACE);
http_path_add("/state", NULL, page_statedump, ACCESS_ADMIN);

View file

@ -1,16 +1,24 @@
prefix ?= $(INSTALLPREFIX)
INSTBIN= $(prefix)/bin
INSTMAN= $(prefix)/share/man1
INSTBIN= ${DESTDIR}${INSTALLPREFIX}/bin
INSTMAN= ${DESTDIR}${INSTALLPREFIX}/share/man1
INSTDBG= ${DESTDIR}/usr/lib/debug/${INSTALLPREFIX}/bin
MAN=man/tvheadend.1
install: ${PROG} ${MAN}
mkdir -p ${DESTDIR}$(INSTBIN)
install -s ${PROG} ${DESTDIR}$(INSTBIN)
mkdir -p ${INSTBIN}
mkdir -p ${INSTDBG}
install -T ${PROG} ${INSTBIN}/tvheadend
mkdir -p ${DESTDIR}$(INSTMAN)
install ${MAN} ${DESTDIR}$(INSTMAN)
objcopy --only-keep-debug ${INSTBIN}/tvheadend ${INSTDBG}/tvheadend.debug
strip -g ${INSTBIN}/tvheadend
objcopy --add-gnu-debuglink=${INSTDBG}/tvheadend.debug ${INSTBIN}/tvheadend
mkdir -p ${INSTMAN}
install ${MAN} ${INSTMAN}
uninstall:
rm -f ${DESTDIR}$(INSTBIN)/${PROG}
rm -f ${DESTDIR}$(INSTMAN)/${MAN}
rm -f ${INSTBIN}/tvheadend
rm -f ${INSTDBG}/tvheadend.debug
rm -f ${INSTMAN}/tvheadend.1