Merge remote branch 'upstream/master' into pull_request
Conflicts: src/webui/static/app/tvheadend.js
This commit is contained in:
commit
0ac11db415
58 changed files with 1761 additions and 333 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,2 +1,6 @@
|
|||
build.*
|
||||
config.default
|
||||
|
||||
.cproject
|
||||
.project
|
||||
.settings
|
||||
|
|
18
Makefile
18
Makefile
|
@ -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
128
contrib/redhat/tvheadend
Executable 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 $?
|
||||
|
75
contrib/redhat/tvheadend.spec
Normal file
75
contrib/redhat/tvheadend.spec
Normal 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
33
debian/changelog
vendored
|
@ -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
24
debian/control
vendored
|
@ -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
48
debian/rules
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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/
|
|
@ -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
|
118
src/access.c
118
src/access.c
|
@ -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);
|
||||
|
||||
|
|
24
src/access.h
24
src/access.h
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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
127
src/cwc.c
|
@ -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);
|
||||
|
|
|
@ -40,6 +40,8 @@ typedef struct dtable_class {
|
|||
int dtc_read_access;
|
||||
int dtc_write_access;
|
||||
|
||||
pthread_mutex_t *dtc_mutex;
|
||||
|
||||
} dtable_class_t;
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
117
src/dvr/dvr_db.c
117
src/dvr/dvr_db.c
|
@ -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
213
src/epg.c
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -637,7 +637,7 @@ htsmsg_xml_parse_cd0(xmlparser_t *xp,
|
|||
}
|
||||
|
||||
if(cc == NULL) {
|
||||
if(*src <= 32) {
|
||||
if(*src < 32) {
|
||||
src++;
|
||||
continue;
|
||||
}
|
||||
|
|
160
src/htsp.c
160
src/htsp.c
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
37
src/http.c
37
src/http.c
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
11
src/http.h
11
src/http.h
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
79
src/trap.c
79
src/trap.c
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue