mirror of
https://projects.vdr-developer.org/git/vdr-plugin-streamdev.git
synced 2023-10-10 19:16:51 +02:00
Streamdev 0.3.4
This commit is contained in:
parent
7576173547
commit
31df0eaf8e
55
CONTRIBUTORS
55
CONTRIBUTORS
@ -1,8 +1,11 @@
|
|||||||
Special thanks go to the following persons (if you think your name is missing
|
Special thanks go to the following persons (if you think your name is missing
|
||||||
here, please send an email to sascha@akv-soft.de):
|
here, please send an email to vdrdev@schmirler.de):
|
||||||
|
|
||||||
|
Sascha Volkenandt, the original author,
|
||||||
|
for this great plugin
|
||||||
|
|
||||||
The Metzler Brothers
|
The Metzler Brothers
|
||||||
because I took a whole lot of code from their libdvbmpeg package
|
as a lot of code has been taken from their libdvbmpeg package
|
||||||
|
|
||||||
Angelus (DOm)
|
Angelus (DOm)
|
||||||
for providing italian language texts
|
for providing italian language texts
|
||||||
@ -13,9 +16,57 @@ Michal
|
|||||||
|
|
||||||
Rolf Ahrenberg
|
Rolf Ahrenberg
|
||||||
for providing finnish language texts
|
for providing finnish language texts
|
||||||
|
for adding externremux.sh commandline parameter
|
||||||
|
for silencing compiler warnings
|
||||||
|
for adding PAT, PMT, PCR and EIT to HTTP TS streams
|
||||||
|
for fixing a memory leak in buffer overflow situations
|
||||||
|
for adding a return code check to vasprintf()
|
||||||
|
for suggesting a fix of the Makefile's default target
|
||||||
|
for a TS PAT repacker based on Petri Laine's VDR TS recording patch
|
||||||
|
for making it possible to pass parameters to externremux.sh
|
||||||
|
|
||||||
Rantanen Teemu
|
Rantanen Teemu
|
||||||
for providing vdr-incompletesections.diff
|
for providing vdr-incompletesections.diff
|
||||||
|
|
||||||
Thomas Keil
|
Thomas Keil
|
||||||
for providing vdr-localchannelprovide.diff
|
for providing vdr-localchannelprovide.diff
|
||||||
|
for maintaining the plugin for a while
|
||||||
|
|
||||||
|
Artur Skawina
|
||||||
|
for fixing an fd leak
|
||||||
|
|
||||||
|
Norad
|
||||||
|
for reporting a problem terminated externremux.sh children
|
||||||
|
|
||||||
|
Udo Richter
|
||||||
|
for fixing streamdev-server shutdown
|
||||||
|
for speeding up cPluginStreamdevServer::Active()
|
||||||
|
for adapting to VDR 1.5.0 API
|
||||||
|
|
||||||
|
greenman
|
||||||
|
for reporting that the log could get flooded on connection failures.
|
||||||
|
|
||||||
|
Petri Hintukainen
|
||||||
|
for making section filtering work
|
||||||
|
for fixing a segfault with VDR 1.5
|
||||||
|
for fixing high CPU load when data stream is disconnected
|
||||||
|
for adding PAT, PMT and PCR to HTTP TS streams
|
||||||
|
for fixing a segfault / deadlock when shutting down
|
||||||
|
for fixing compiler warnings
|
||||||
|
for adding M3U playlists
|
||||||
|
|
||||||
|
ollo
|
||||||
|
for suggesting support for WMM capable WLAN accesspoints
|
||||||
|
|
||||||
|
vdr-freak
|
||||||
|
for reporting connection aborts when externremux ringbuffer is full
|
||||||
|
|
||||||
|
alexw
|
||||||
|
for reporting client reconnect problems after a server restart
|
||||||
|
for a workaround for tuning problems with 1.5.x clients
|
||||||
|
|
||||||
|
Olli Lammi
|
||||||
|
for fixing a busy wait when client isn't accepting data fast enough
|
||||||
|
|
||||||
|
Joerg Pulz
|
||||||
|
for his FreeBSD compatibility patch
|
||||||
|
88
HISTORY
88
HISTORY
@ -1,6 +1,94 @@
|
|||||||
VDR Plugin 'streamdev' Revision History
|
VDR Plugin 'streamdev' Revision History
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
|
2008-03-31: Version 0.3.4
|
||||||
|
|
||||||
|
- added possibility to pass parameter to externremux.sh (thanks to Rolf
|
||||||
|
Ahrenberg)
|
||||||
|
- use HTTP host header in absolute URLs for DNAT / reverse proxy support
|
||||||
|
- rewrite of the HTTP menu part
|
||||||
|
- added M3U playlists (thanks to Petri Hinutkainen)
|
||||||
|
- enable section filtering only with compatible clients (thanks to Petri
|
||||||
|
Hintukainen)
|
||||||
|
- fixed compiler warning
|
||||||
|
- added EIT to HTTP TS streams (thanks to Rolf Ahrenberg)
|
||||||
|
- compatibility for FreeBSD (thanks to Joerg Pulz)
|
||||||
|
- added TS PAT repacker (thanks to Rolf Ahrenberg)
|
||||||
|
- fixed Makefile's default target (suggested by Rolf Ahrenberg)
|
||||||
|
- workaround for tuning problems on 1.5.x clients (thanks to alexw)
|
||||||
|
- added VTP support for PS, PES and EXTERN (PS requested by mpanczyk)
|
||||||
|
- fixed gcc-4.3.0 warnings (thanks to Petri Hintukainen)
|
||||||
|
- fixed busy wait when client isn't accepting data fast enough (thanks to
|
||||||
|
Olli Lammi)
|
||||||
|
- fixed client reconnect after server restart (reported by alexw)
|
||||||
|
- added lock in ~cStreamdevDevice (thanks to Petri Hintukainen)
|
||||||
|
- externremux: check for ringbuffer full condition (reported by
|
||||||
|
vdr-freak@vdrportal)
|
||||||
|
- diffserv support for traffic shaping and WMM capable WLAN accesspoint
|
||||||
|
(suggested by ollo@vdrportal)
|
||||||
|
- check vasprintf() return code (thanks to Rolf Ahrenberg)
|
||||||
|
- fixed memory leak in buffer overflow situations (thanks to Rolf Ahrenberg)
|
||||||
|
- added PAT, PMT and PCR to HTTP TS streams (thanks to Petri Hintukainen and
|
||||||
|
Rolf Ahrenberg)
|
||||||
|
- detect data stream disconnections. Fixes high CPU load (thanks to Petri
|
||||||
|
Hintukainen)
|
||||||
|
- fixed segfault with VDR 1.5 (thanks to Petri Hintukainen)
|
||||||
|
- made section filtering work (thanks to Petri Hintukainen)
|
||||||
|
- added compiler flag -Wall and fixed corresponding warnings (thanks to
|
||||||
|
Rolf Ahrenberg)
|
||||||
|
- close pipe when externremux is gone. Fixes high CPU load problem
|
||||||
|
- close connection when client is gone. Fixes high CPU load problem
|
||||||
|
- silenced compiler warnings (thanks to Rolf Ahrenberg)
|
||||||
|
- added commandline parameter for externremux script (thanks to Rolf
|
||||||
|
Ahrenberg)
|
||||||
|
- detach receivers before switching transponders
|
||||||
|
- API changes for VDR 1.5.0 (thanks to Udo Richter)
|
||||||
|
- log connections failures only every 10s (reported by greenman@vdrportal)
|
||||||
|
- replaced uint64 by uint64_t
|
||||||
|
- added Recursion patch for vdr 1.4
|
||||||
|
- added LocalChannelProvide for vdr 1.4.x
|
||||||
|
- added respect_ca patch
|
||||||
|
- speedup cPluginStreamdevServer::Active() by caching translation (thanks
|
||||||
|
to Udo Richter)
|
||||||
|
- periodically check if streamdev-server needs to shutdown (thanks to Udo
|
||||||
|
Richter)
|
||||||
|
- collect terminated externremux.sh processes (reported by Norad@vdrportal)
|
||||||
|
- avoid fd leaks when we fail to spawn externremux.sh
|
||||||
|
- detach all receivers before tuning to a different transponder
|
||||||
|
- Re-enabled logging for the Detach()/Attach() issue
|
||||||
|
- Added -fPIC compiler flag required on AMD64 architectures
|
||||||
|
|
||||||
|
2006-08-17: End of maintenance by Thomas Keil
|
||||||
|
|
||||||
|
- updated Finish translation (thanks to Rolf Ahrenberg)
|
||||||
|
- fixed fd leak (thanks to Artur Skawina)
|
||||||
|
- re-enabled Detach/Attach to temporarily release the device used by
|
||||||
|
streamdev while checking if we can switch transponders (thanks to
|
||||||
|
PanamaJack@vdrportal)
|
||||||
|
- adopted to VDR 1.4.x
|
||||||
|
|
||||||
|
2006-01-26: End of maintenance by Sascha Volkenandt
|
||||||
|
|
||||||
|
- fixed http error response
|
||||||
|
- added class forward declaration for gcc >= 4.0
|
||||||
|
- adopted to VDR >= 1.3.36
|
||||||
|
- added LocalChannelProvide for vdr 1.3.24
|
||||||
|
- fixed missing include
|
||||||
|
- added TS compatibility mode
|
||||||
|
- deleting whole block instead of fractions now
|
||||||
|
- fixed wrong remux usage
|
||||||
|
- added finish translations (thanks to Rolf Ahrenberg)
|
||||||
|
- protected cStreamer::Stop() from being called concurrently
|
||||||
|
- some compilers complained about missing declarations, added <ctype.h>
|
||||||
|
- removed assembler and thus saving one ringbuffer
|
||||||
|
- fixed destruction order on channel switch (fixes one crash that happens
|
||||||
|
occasionally when switching)
|
||||||
|
- removed client menu code temporarily
|
||||||
|
- streamer now gets stopped when connection terminates unexpectedly
|
||||||
|
- fixed recursive delete in streamer
|
||||||
|
- fixed pure virtual crash in server
|
||||||
|
- audio track selection for http
|
||||||
|
|
||||||
2004-??-??: Version 0.3.3
|
2004-??-??: Version 0.3.3
|
||||||
|
|
||||||
- dropped support for non-ts streaming in vdr-to-vdr clients
|
- dropped support for non-ts streaming in vdr-to-vdr clients
|
||||||
|
18
Makefile
18
Makefile
@ -1,7 +1,7 @@
|
|||||||
#
|
#
|
||||||
# Makefile for a Video Disk Recorder plugin
|
# Makefile for a Video Disk Recorder plugin
|
||||||
#
|
#
|
||||||
# $Id: Makefile,v 1.8 2007/04/16 11:01:02 schmirl Exp $
|
# $Id: Makefile,v 1.12 2008/03/31 10:34:26 schmirl Exp $
|
||||||
|
|
||||||
# The official name of this plugin.
|
# The official name of this plugin.
|
||||||
# This name will be used in the '-P...' option of VDR to load the plugin.
|
# This name will be used in the '-P...' option of VDR to load the plugin.
|
||||||
@ -61,7 +61,7 @@ SERVEROBJS = $(PLUGIN)-server.o \
|
|||||||
server/server.o server/connectionVTP.o server/connectionHTTP.o \
|
server/server.o server/connectionVTP.o server/connectionHTTP.o \
|
||||||
server/componentHTTP.o server/componentVTP.o server/connection.o \
|
server/componentHTTP.o server/componentVTP.o server/connection.o \
|
||||||
server/component.o server/suspend.o server/setup.o server/streamer.o \
|
server/component.o server/suspend.o server/setup.o server/streamer.o \
|
||||||
server/livestreamer.o server/livefilter.o \
|
server/livestreamer.o server/livefilter.o server/menuHTTP.o \
|
||||||
\
|
\
|
||||||
remux/tsremux.o remux/ts2ps.o remux/ts2es.o remux/extern.o
|
remux/tsremux.o remux/ts2ps.o remux/ts2es.o remux/extern.o
|
||||||
|
|
||||||
@ -85,8 +85,10 @@ ifeq ($(shell test -f $(VDRDIR)/sections.c ; echo $$?),0)
|
|||||||
DEFINES += -DHAVE_AUTOPID
|
DEFINES += -DHAVE_AUTOPID
|
||||||
endif
|
endif
|
||||||
|
|
||||||
libdvbmpeg/libdvbmpegtools.a: libdvbmpeg/*.c libdvbmpeg/*.cc libdvbmpeg/*.h libdvbmpeg/*.hh
|
### The main target:
|
||||||
make -C ./libdvbmpeg libdvbmpegtools.a
|
|
||||||
|
.PHONY: all dist clean
|
||||||
|
all: libvdr-$(PLUGIN)-client.so libvdr-$(PLUGIN)-server.so
|
||||||
|
|
||||||
### Implicit rules:
|
### Implicit rules:
|
||||||
|
|
||||||
@ -113,7 +115,9 @@ endif
|
|||||||
|
|
||||||
### Targets:
|
### Targets:
|
||||||
|
|
||||||
all: libvdr-$(PLUGIN)-client.so libvdr-$(PLUGIN)-server.so
|
libdvbmpeg/libdvbmpegtools.a: libdvbmpeg/*.c libdvbmpeg/*.cc libdvbmpeg/*.h libdvbmpeg/*.hh
|
||||||
|
$(MAKE) -C ./libdvbmpeg libdvbmpegtools.a
|
||||||
|
|
||||||
|
|
||||||
libvdr-$(PLUGIN)-client.so: $(CLIENTOBJS) $(COMMONOBJS) libdvbmpeg/libdvbmpegtools.a
|
libvdr-$(PLUGIN)-client.so: $(CLIENTOBJS) $(COMMONOBJS) libdvbmpeg/libdvbmpegtools.a
|
||||||
libvdr-$(PLUGIN)-server.so: $(SERVEROBJS) $(COMMONOBJS) libdvbmpeg/libdvbmpegtools.a
|
libvdr-$(PLUGIN)-server.so: $(SERVEROBJS) $(COMMONOBJS) libdvbmpeg/libdvbmpegtools.a
|
||||||
@ -126,10 +130,10 @@ dist: clean
|
|||||||
@-rm -rf $(TMPDIR)/$(ARCHIVE)
|
@-rm -rf $(TMPDIR)/$(ARCHIVE)
|
||||||
@mkdir $(TMPDIR)/$(ARCHIVE)
|
@mkdir $(TMPDIR)/$(ARCHIVE)
|
||||||
@cp -a * $(TMPDIR)/$(ARCHIVE)
|
@cp -a * $(TMPDIR)/$(ARCHIVE)
|
||||||
@tar czf $(PACKAGE).tgz --exclude SCCS -C $(TMPDIR) $(ARCHIVE)
|
@tar czf $(PACKAGE).tgz --exclude CVS -C $(TMPDIR) $(ARCHIVE)
|
||||||
@-rm -rf $(TMPDIR)/$(ARCHIVE)
|
@-rm -rf $(TMPDIR)/$(ARCHIVE)
|
||||||
@echo Distribution package created as $(PACKAGE).tgz
|
@echo Distribution package created as $(PACKAGE).tgz
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@-rm -f $(COMMONOBJS) $(CLIENTOBJS) $(SERVEROBJS) $(DEPFILE) *.so *.tgz core* *~
|
@-rm -f $(COMMONOBJS) $(CLIENTOBJS) $(SERVEROBJS) $(DEPFILE) *.so *.tgz core* *~
|
||||||
make -C ./libdvbmpeg clean
|
$(MAKE) -C ./libdvbmpeg clean
|
||||||
|
168
README
168
README
@ -1,10 +1,12 @@
|
|||||||
This is a "plugin" for the Video Disk Recorder (VDR).
|
This is a "plugin" for the Video Disk Recorder (VDR).
|
||||||
|
|
||||||
Written by: Sascha Volkenandt <sascha@akv-soft.de>
|
Written by: Sascha Volkenandt <sascha@akv-soft.de>
|
||||||
|
Current maintainer: Frank Schmirler <vdrdev@schmirler.de>
|
||||||
|
|
||||||
Project's homepage: http://www.magoa.net/linux/
|
Project's homepage: http://streamdev.vdr-developer.org/
|
||||||
|
Former project homepage: http://linux.kompiliert.net/
|
||||||
|
|
||||||
Latest version available at: http://www.magoa.net/linux/index.php?view=streamdev
|
Latest version available at: http://streamdev.vdr-developer.org/
|
||||||
|
|
||||||
See the file COPYING for license information.
|
See the file COPYING for license information.
|
||||||
|
|
||||||
@ -14,16 +16,12 @@ Contents:
|
|||||||
1. Description
|
1. Description
|
||||||
2. Installation
|
2. Installation
|
||||||
2.1 VDR 1.2.X
|
2.1 VDR 1.2.X
|
||||||
2.2 VDR 1.3.X
|
2.2 VDR 1.3.X and above
|
||||||
3. Usage
|
3. Usage
|
||||||
3.1 Usage VDR-to-VDR server
|
3.1 Usage HTTP server
|
||||||
3.2 Usage HTTP server
|
3.2 Usage VDR-to-VDR server
|
||||||
3.3 Usage VDR-to-VDR client
|
3.3 Usage VDR-to-VDR client
|
||||||
3.4 General Usage Notes
|
4. Other useful Plugins
|
||||||
4. VDR-to-VDR client notes (PLEASE READ IF YOU HAVE ONE)
|
|
||||||
4.1 EPG data [OUTDATED]
|
|
||||||
4.2 Teletext / OSD Teletext
|
|
||||||
4.3 AnalogTV [OUTDATED]
|
|
||||||
5. Known Problems
|
5. Known Problems
|
||||||
|
|
||||||
|
|
||||||
@ -102,8 +100,8 @@ patch -p1 <PLUGINS/src/streamdev/patches/thread.c.diff
|
|||||||
make [options, if necessary] vdr
|
make [options, if necessary] vdr
|
||||||
make [options, if necessary] plugins
|
make [options, if necessary] plugins
|
||||||
|
|
||||||
2.2 VDR 1.3.X:
|
2.2 VDR 1.3.X and above:
|
||||||
--------------
|
------------------------
|
||||||
|
|
||||||
cd vdr-1.X.X/PLUGINS/src
|
cd vdr-1.X.X/PLUGINS/src
|
||||||
tar xvfz vdr-streamdev-0.3.1.tgz
|
tar xvfz vdr-streamdev-0.3.1.tgz
|
||||||
@ -134,23 +132,13 @@ prevents the client from switching transponders. If you set "Client may
|
|||||||
suspend" to yes, the client can suspend the server remotely (this only applies
|
suspend" to yes, the client can suspend the server remotely (this only applies
|
||||||
if "Offer suspend mode" is selected).
|
if "Offer suspend mode" is selected).
|
||||||
|
|
||||||
|
|
||||||
3.1 Usage VDR-to-VDR server:
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
You can activate the VDR-to-VDR server part in the PlugIn's Setup Menu. It is
|
|
||||||
deactivated by default. The Parameter "VDR-to-VDR Server Port" specifies the
|
|
||||||
port where you want the server to listen for incoming connections. The server
|
|
||||||
will be activated when you push the OK button inside the setup menu, so there's
|
|
||||||
no need to restart VDR.
|
|
||||||
|
|
||||||
NOTE: This mainly applies to One-Card-Systems, since with multiple cards there
|
NOTE: This mainly applies to One-Card-Systems, since with multiple cards there
|
||||||
is no need to switch transponders on the primary interface, if the secondary
|
is no need to switch transponders on the primary interface, if the secondary
|
||||||
can stream a given channel (i.e. if it is not blocked by a recording). If both
|
can stream a given channel (i.e. if it is not blocked by a recording). If both
|
||||||
cards are in use (i.e. when something is recorded, or by multiple clients),
|
cards are in use (i.e. when something is recorded, or by multiple clients),
|
||||||
this applies to Multiple-Card-Systems as well.
|
this applies to Multiple-Card-Systems as well.
|
||||||
|
|
||||||
3.2 Usage HTTP server:
|
3.1 Usage HTTP server:
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
You can use the HTTP part by accessing the server with a HTTP-capable media
|
You can use the HTTP part by accessing the server with a HTTP-capable media
|
||||||
@ -164,8 +152,17 @@ TS Transport Stream (i.e. a dump from the device)
|
|||||||
PES Packetized Elemetary Stream (VDR's native recording format)
|
PES Packetized Elemetary Stream (VDR's native recording format)
|
||||||
PS Program Stream (SVCD, DVD like stream)
|
PS Program Stream (SVCD, DVD like stream)
|
||||||
ES Elementary Stream (only Video, if available, otherwise only Audio)
|
ES Elementary Stream (only Video, if available, otherwise only Audio)
|
||||||
|
EXTERN Pass stream through external script (e.g. for converting with mencoder)
|
||||||
|
|
||||||
If you leave the default port (3000), you can access the streams like this:
|
Assuming that you leave the default port (3000), point your web browser to
|
||||||
|
|
||||||
|
http://hostname:3000/
|
||||||
|
|
||||||
|
You will be presented a menu with links to various channel lists, including M3U
|
||||||
|
playlist formats.
|
||||||
|
|
||||||
|
If you don't want to use the HTML menu or the M3U playlists, you can access the
|
||||||
|
streams directly like this:
|
||||||
|
|
||||||
http://hostname:3000/3
|
http://hostname:3000/3
|
||||||
http://hostname:3000/S19.2E-0-12480-898
|
http://hostname:3000/S19.2E-0-12480-898
|
||||||
@ -178,12 +175,26 @@ http://hostname:3000/TS/3
|
|||||||
http://hostname:3000/PES/S19.2E-0-12480-898
|
http://hostname:3000/PES/S19.2E-0-12480-898
|
||||||
|
|
||||||
The first one would deliver the stream in TS, the second one in PES format.
|
The first one would deliver the stream in TS, the second one in PES format.
|
||||||
Possible values are 'PES', 'TS', 'PS' and 'ES'. You need to specify the ES
|
Possible values are 'PES', 'TS', 'PS', 'ES' and 'EXTERN'. You need to specify
|
||||||
format explicitly if you want to listen to radio channels. Play them pack i.e.
|
the ES format explicitly if you want to listen to radio channels. Play them
|
||||||
with mpg123.
|
back i.e. with mpg123.
|
||||||
|
|
||||||
mpg123 http://hostname:3000/ES/200
|
mpg123 http://hostname:3000/ES/200
|
||||||
|
|
||||||
|
With 'EXTERN' you can also add a parameter which is passed as argument to the
|
||||||
|
externremux script.
|
||||||
|
|
||||||
|
http://hostname:3000/EXTERN;some_parameter/3
|
||||||
|
|
||||||
|
3.2 Usage VDR-to-VDR server:
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
You can activate the VDR-to-VDR server part in the PlugIn's Setup Menu. It is
|
||||||
|
deactivated by default. The Parameter "VDR-to-VDR Server Port" specifies the
|
||||||
|
port where you want the server to listen for incoming connections. The server
|
||||||
|
will be activated when you push the OK button inside the setup menu, so there's
|
||||||
|
no need to restart VDR.
|
||||||
|
|
||||||
3.3 Usage VDR-to-VDR client:
|
3.3 Usage VDR-to-VDR client:
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
@ -216,84 +227,71 @@ that channel, the client will show it until you switch again, or until the
|
|||||||
server needs that card (if no other is free) for a recording on a different
|
server needs that card (if no other is free) for a recording on a different
|
||||||
transponder.
|
transponder.
|
||||||
|
|
||||||
You can choose a remote streamtype in the setup. I'd suggest TS streaming as
|
Only the needed PIDs are transferred, and additional PIDs can be turned on
|
||||||
it has a much shorter delay than PES streaming (compared to live-view of the
|
during an active transfer. This makes it possible to switch languages, receive
|
||||||
same channel on the server), and transmits more information such as AC3 and
|
additional channels (for recording on the client) and use plugins that use
|
||||||
teletext data.
|
receivers themselves (like osdteletext).
|
||||||
|
|
||||||
When setting the parameter "MultiPID streaming" to yes (the default) (only
|
With "Filter Streaming" enabled, the client will receive meta information like
|
||||||
applies if the streamtype is TS), only the needed PIDs are transferred, and
|
EPG data and service information, just as if the client had its own DVB card.
|
||||||
additional PIDs can be turned on during an active transfer. This makes it
|
Link channels and even a client-side EPG scan have been reported to work.
|
||||||
possible to switch languages, receive additional channels (for recording on
|
|
||||||
the client) and use plugins that use receivers themselves (like osdteletext).
|
|
||||||
|
|
||||||
The last parameter, "Synchronize EPG", will have the client synchronize it's
|
The last parameter, "Synchronize EPG", will have the client synchronize it's
|
||||||
program table with the server every now and then, but not regularly. This
|
program table with the server every now and then, but not regularly. This
|
||||||
happens when starting the client, and everytime VDR does its housekeeping
|
happens when starting the client, and everytime VDR does its housekeeping
|
||||||
tasks. The only thing that's guaranteed is, that there will be a minimum
|
tasks. The only thing that's guaranteed is, that there will be a minimum
|
||||||
interval of ten seconds between each EPG synchronization.
|
interval of ten seconds between each EPG synchronization. With "Filter
|
||||||
|
Streaming" this option has been obsoleted. If you still need to synchronize
|
||||||
|
EPG as additional information is available from the server, you should use the
|
||||||
|
epgsync-plugin instead (http://vdr.schmirler.de).
|
||||||
|
|
||||||
The client has a Main Menu entry called "Streaming Control". This is used to
|
4. Other useful Plugins:
|
||||||
control various aspects of the remote server VDR. Inside, you will find
|
|
||||||
"Remote Timers", "Remote Recordings", "Suspend server" and "Synchronize EPG".
|
|
||||||
|
|
||||||
The "Remote Timers" entry gives you the possibility to edit, create and delete
|
|
||||||
the server's timers remotely. Every timer is synchronized before the requested
|
|
||||||
action actually takes place. This only leaves a very short time-span (of a few
|
|
||||||
milliseconds) in which a race-condition could happen.
|
|
||||||
|
|
||||||
"Remote Recordings" shows up all recordings that the server can access. Only
|
|
||||||
deleting recordings is implemented, yet.
|
|
||||||
|
|
||||||
With "Suspend Server", you can send the server into suspend mode remotely, if
|
|
||||||
the server is set to "Offer suspend mode" and allows the client to suspend.
|
|
||||||
|
|
||||||
Last but not least, "Synchronize EPG" starts a synchronization in case you
|
|
||||||
don't want to do it regularly, or in case you just activated it and can't wait
|
|
||||||
for the first synchronization to happen by itself.
|
|
||||||
|
|
||||||
3.4 General Usage Notes:
|
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
If there's still some debug output on stdout, please ignore it ;)
|
4.1 Plugins for VDR-to-VDR clients:
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
The following plugins are useful for VDR-to-VDR clients (i.e. VDRs running the
|
||||||
|
streamdev-client):
|
||||||
|
|
||||||
4. VDR-to-VDR client notes:
|
* remotetimers (http://vdr.schmirler.de/)
|
||||||
---------------------------
|
Add, edit, delete timers on client and server
|
||||||
|
|
||||||
4.1 EPG data:
|
* timersync (http://phivdr.dyndns.org/vdr/vdr-timersync/)
|
||||||
--------------
|
Automatically syncronizes timer lists of client and server. All recordings will
|
||||||
|
be made on the server
|
||||||
|
|
||||||
[ OUTDATED, see "Synchronize EPG" in 3.2 ]
|
* remoteosd (http://vdr.schmirler.de/)
|
||||||
|
Provides access to the server's OSD menu
|
||||||
|
|
||||||
4.2 Teletext / OSD Teletext:
|
* epgsync (http://vdr.schmirler.de/)
|
||||||
-----------------------------
|
Import EPG from server VDR
|
||||||
|
|
||||||
Usual teletext will probably not work on the client, if it has no DVB hardware.
|
* femon (http://www.saunalahti.fi/~rahrenbe/vdr/femon/)
|
||||||
I never tried, and probably I never will, so don't ask about it please ;)
|
Display signal information from server's DVB card. SVDRP support must be enabled
|
||||||
|
in femon's setup
|
||||||
|
|
||||||
Osdteletext-0.3.1 (and later) definitely work when used in MultiPID Streaming
|
4.2 Plugins for Server:
|
||||||
mode.
|
-----------------------
|
||||||
|
|
||||||
|
* dummydevice (http://phivdr.dyndns.org/vdr/vdr-dummydevice/)
|
||||||
|
Recommended on a headless server (i.e. a server with no real output device).
|
||||||
|
Without this plugin, a budget DVB card could become VDR's primary device. This
|
||||||
|
causes unwanted sideeffects in certain situations.
|
||||||
|
|
||||||
4.3 AnalogTV
|
4.3 Alternatives:
|
||||||
------------
|
-----------------
|
||||||
|
|
||||||
Works with ivtv and analogue cards according to Andreas Kool.
|
|
||||||
|
|
||||||
|
* xineliboutput (http://phivdr.dyndns.org/vdr/vdr-xineliboutput/)
|
||||||
|
With its networking option, xineliboutput provides an alternative to streamdev.
|
||||||
|
You will get the picture of the server VDR, including its OSD. However you
|
||||||
|
won't get independent clients, as they all share the same output.
|
||||||
|
|
||||||
5. Known Problems:
|
5. Known Problems:
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
- Recordings & Timers on the client side could endanger Timers & Recordings on
|
* In VDR-to-VDR setup, the availability of a channel is checked with a different
|
||||||
the server, as they will have the same priority (by default). Set the
|
priority than the actual channel switch. The later always uses priority 0.
|
||||||
default priority to i.e. 40 if you want the server to supersede the client.
|
Usually a channel switch for live TV has priority 0 anyway, so it is not a
|
||||||
|
problem here. However timers usually have a higher priority. Either avoid
|
||||||
- Sometimes, if you reload VDR too often (for example while recompiling), the
|
client side recordings or set the priority of client side timers to 0.
|
||||||
driver can get "stuck" in some situations. Try a driver restart if anything
|
|
||||||
you think should work doesn't before sending a bug-report :-).
|
|
||||||
[ ADDITION ]
|
|
||||||
In the meantime I have discovered that this error is caused by the all-
|
|
||||||
mysterical UPT (unknown picture type) error :-(.
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* $Id: device.c,v 1.14 2007/07/20 06:46:47 schmirl Exp $
|
* $Id: device.c,v 1.15 2007/12/12 12:22:45 schmirl Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "client/device.h"
|
#include "client/device.h"
|
||||||
@ -212,8 +212,16 @@ void cStreamdevDevice::CloseDvrInt(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Dprintf("cStreamdevDevice::CloseDvrInt(): Closing DVR connection\n");
|
Dprintf("cStreamdevDevice::CloseDvrInt(): Closing DVR connection\n");
|
||||||
|
#if VDRVERSNUM < 10500
|
||||||
DELETENULL(m_TSBuffer);
|
DELETENULL(m_TSBuffer);
|
||||||
ClientSocket.CloseDvr();
|
ClientSocket.CloseDvr();
|
||||||
|
#else
|
||||||
|
// Hack for VDR 1.5.x clients (sometimes sending ABRT after TUNE)
|
||||||
|
// TODO: Find a clean solution to fix this
|
||||||
|
ClientSocket.SetChannelDevice(m_Channel);
|
||||||
|
ClientSocket.CloseDvr();
|
||||||
|
DELETENULL(m_TSBuffer);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void cStreamdevDevice::CloseDvr(void) {
|
void cStreamdevDevice::CloseDvr(void) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* $Id: socket.c,v 1.8 2007/04/24 10:57:34 schmirl Exp $
|
* $Id: socket.c,v 1.9 2008/03/13 16:01:17 schmirl Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <tools/select.h>
|
#include <tools/select.h>
|
||||||
@ -140,8 +140,14 @@ bool cClientSocket::CheckConnection(void) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
isyslog("Streamdev: Connected to server %s:%d using capabilities TSPIDS",
|
const char *Filters = "";
|
||||||
RemoteIp().c_str(), RemotePort());
|
#if VDRVERSNUM >= 10300
|
||||||
|
if(Command("CAPS FILTERS", 220))
|
||||||
|
Filters = ",FILTERS";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
isyslog("Streamdev: Connected to server %s:%d using capabilities TSPIDS%s",
|
||||||
|
RemoteIp().c_str(), RemotePort(), Filters);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
4
common.c
4
common.c
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* $Id: common.c,v 1.5 2007/09/21 11:55:56 schmirl Exp $
|
* $Id: common.c,v 1.6 2008/03/31 10:34:26 schmirl Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <vdr/channels.h>
|
#include <vdr/channels.h>
|
||||||
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
const char *VERSION = "0.3.3-20070921";
|
const char *VERSION = "0.3.4";
|
||||||
|
|
||||||
const char *StreamTypes[st_Count] = {
|
const char *StreamTypes[st_Count] = {
|
||||||
"TS",
|
"TS",
|
||||||
|
8
common.h
8
common.h
@ -1,10 +1,16 @@
|
|||||||
/*
|
/*
|
||||||
* $Id: common.h,v 1.8 2007/04/24 10:50:13 schmirl Exp $
|
* $Id: common.h,v 1.9 2008/03/12 09:36:27 schmirl Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef VDR_STREAMDEV_COMMON_H
|
#ifndef VDR_STREAMDEV_COMMON_H
|
||||||
#define VDR_STREAMDEV_COMMON_H
|
#define VDR_STREAMDEV_COMMON_H
|
||||||
|
|
||||||
|
/* FreeBSD has it's own version of isnumber(),
|
||||||
|
but VDR's version is incompatible */
|
||||||
|
#ifdef __FreeBSD__
|
||||||
|
#undef isnumber
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <vdr/tools.h>
|
#include <vdr/tools.h>
|
||||||
#include <vdr/plugin.h>
|
#include <vdr/plugin.h>
|
||||||
|
|
||||||
|
@ -2060,7 +2060,11 @@ void split_mpg(char *name, uint64_t size)
|
|||||||
if (break_up_filename(name,base_name,path,ext) < 0) exit(1);
|
if (break_up_filename(name,base_name,path,ext) < 0) exit(1);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __FreeBSD__
|
||||||
|
if ( (fdin = open(name, O_RDONLY)) < 0){
|
||||||
|
#else
|
||||||
if ( (fdin = open(name, O_RDONLY|O_LARGEFILE)) < 0){
|
if ( (fdin = open(name, O_RDONLY|O_LARGEFILE)) < 0){
|
||||||
|
#endif
|
||||||
fprintf(stderr,"Can't open %s\n",name);
|
fprintf(stderr,"Can't open %s\n",name);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@ -2101,8 +2105,12 @@ void split_mpg(char *name, uint64_t size)
|
|||||||
sprintf(new_name,"%s-%03d.%s",base_name,i,ext);
|
sprintf(new_name,"%s-%03d.%s",base_name,i,ext);
|
||||||
printf("writing %s\n",new_name);
|
printf("writing %s\n",new_name);
|
||||||
|
|
||||||
|
#ifdef __FreeBSD__
|
||||||
|
if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC,
|
||||||
|
#else
|
||||||
if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC
|
if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC
|
||||||
|O_LARGEFILE,
|
|O_LARGEFILE,
|
||||||
|
#endif
|
||||||
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|
|
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|
|
||||||
S_IROTH|S_IWOTH)) < 0){
|
S_IROTH|S_IWOTH)) < 0){
|
||||||
fprintf(stderr,"Can't open %s\n",new_name);
|
fprintf(stderr,"Can't open %s\n",new_name);
|
||||||
@ -2114,8 +2122,12 @@ void split_mpg(char *name, uint64_t size)
|
|||||||
sprintf(new_name,"%s-%03d.%s",base_name,i,ext);
|
sprintf(new_name,"%s-%03d.%s",base_name,i,ext);
|
||||||
printf("writing %s\n",new_name);
|
printf("writing %s\n",new_name);
|
||||||
|
|
||||||
|
#ifdef __FreeBSD__
|
||||||
|
if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC,
|
||||||
|
#else
|
||||||
if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC
|
if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC
|
||||||
|O_LARGEFILE,
|
|O_LARGEFILE,
|
||||||
|
#endif
|
||||||
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|
|
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|
|
||||||
S_IROTH|S_IWOTH)) < 0){
|
S_IROTH|S_IWOTH)) < 0){
|
||||||
fprintf(stderr,"Can't open %s\n",new_name);
|
fprintf(stderr,"Can't open %s\n",new_name);
|
||||||
@ -2144,7 +2156,11 @@ void cut_mpg(char *name, uint64_t size)
|
|||||||
if (break_up_filename(name,base_name,path,ext) < 0) exit(1);
|
if (break_up_filename(name,base_name,path,ext) < 0) exit(1);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __FreeBSD__
|
||||||
|
if ( (fdin = open(name, O_RDONLY)) < 0){
|
||||||
|
#else
|
||||||
if ( (fdin = open(name, O_RDONLY|O_LARGEFILE)) < 0){
|
if ( (fdin = open(name, O_RDONLY|O_LARGEFILE)) < 0){
|
||||||
|
#endif
|
||||||
fprintf(stderr,"Can't open %s\n",name);
|
fprintf(stderr,"Can't open %s\n",name);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@ -2182,8 +2198,12 @@ void cut_mpg(char *name, uint64_t size)
|
|||||||
sprintf(new_name,"%s-1.%s",base_name,ext);
|
sprintf(new_name,"%s-1.%s",base_name,ext);
|
||||||
printf("writing %s\n",new_name);
|
printf("writing %s\n",new_name);
|
||||||
|
|
||||||
|
#ifdef __FreeBSD__
|
||||||
|
if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC,
|
||||||
|
#else
|
||||||
if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC
|
if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC
|
||||||
|O_LARGEFILE,
|
|O_LARGEFILE,
|
||||||
|
#endif
|
||||||
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|
|
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|
|
||||||
S_IROTH|S_IWOTH)) < 0){
|
S_IROTH|S_IWOTH)) < 0){
|
||||||
fprintf(stderr,"Can't open %s\n",new_name);
|
fprintf(stderr,"Can't open %s\n",new_name);
|
||||||
@ -2195,8 +2215,12 @@ void cut_mpg(char *name, uint64_t size)
|
|||||||
sprintf(new_name,"%s-2.%s",base_name,ext);
|
sprintf(new_name,"%s-2.%s",base_name,ext);
|
||||||
printf("writing %s\n",new_name);
|
printf("writing %s\n",new_name);
|
||||||
|
|
||||||
|
#ifdef __FreeBSD__
|
||||||
|
if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC,
|
||||||
|
#else
|
||||||
if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC
|
if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC
|
||||||
|O_LARGEFILE,
|
|O_LARGEFILE,
|
||||||
|
#endif
|
||||||
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|
|
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|
|
||||||
S_IROTH|S_IWOTH)) < 0){
|
S_IROTH|S_IWOTH)) < 0){
|
||||||
fprintf(stderr,"Can't open %s\n",new_name);
|
fprintf(stderr,"Can't open %s\n",new_name);
|
||||||
|
@ -19,13 +19,13 @@ protected:
|
|||||||
virtual void Action(void);
|
virtual void Action(void);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
cTSExt(cRingBufferLinear *ResultBuffer);
|
cTSExt(cRingBufferLinear *ResultBuffer, std::string Parameter);
|
||||||
virtual ~cTSExt();
|
virtual ~cTSExt();
|
||||||
|
|
||||||
void Put(const uchar *Data, int Count);
|
void Put(const uchar *Data, int Count);
|
||||||
};
|
};
|
||||||
|
|
||||||
cTSExt::cTSExt(cRingBufferLinear *ResultBuffer):
|
cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, std::string Parameter):
|
||||||
m_ResultBuffer(ResultBuffer),
|
m_ResultBuffer(ResultBuffer),
|
||||||
m_Active(false),
|
m_Active(false),
|
||||||
m_Process(0),
|
m_Process(0),
|
||||||
@ -67,9 +67,8 @@ cTSExt::cTSExt(cRingBufferLinear *ResultBuffer):
|
|||||||
for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++)
|
for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++)
|
||||||
close(i); //close all dup'ed filedescriptors
|
close(i); //close all dup'ed filedescriptors
|
||||||
|
|
||||||
//printf("starting externremux.sh\n");
|
std::string cmd = std::string(g_ExternRemux) + " " + Parameter;
|
||||||
execl("/bin/sh", "sh", "-c", g_ExternRemux, NULL);
|
execl("/bin/sh", "sh", "-c", cmd.c_str(), NULL);
|
||||||
//printf("failed externremux.sh\n");
|
|
||||||
_exit(-1);
|
_exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,9 +149,9 @@ void cTSExt::Put(const uchar *Data, int Count)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cExternRemux::cExternRemux(int VPid, const int *APids, const int *Dpids, const int *SPids):
|
cExternRemux::cExternRemux(int VPid, const int *APids, const int *Dpids, const int *SPids, std::string Parameter):
|
||||||
m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE, TS_SIZE * 2)),
|
m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE, TS_SIZE * 2)),
|
||||||
m_Remux(new cTSExt(m_ResultBuffer))
|
m_Remux(new cTSExt(m_ResultBuffer, Parameter))
|
||||||
{
|
{
|
||||||
m_ResultBuffer->SetTimeouts(500, 100);
|
m_ResultBuffer->SetTimeouts(500, 100);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "remux/tsremux.h"
|
#include "remux/tsremux.h"
|
||||||
#include <vdr/ringbuffer.h>
|
#include <vdr/ringbuffer.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
extern const char *g_ExternRemux;
|
extern const char *g_ExternRemux;
|
||||||
|
|
||||||
@ -14,7 +15,7 @@ private:
|
|||||||
cTSExt *m_Remux;
|
cTSExt *m_Remux;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
cExternRemux(int VPid, const int *APids, const int *Dpids, const int *SPids);
|
cExternRemux(int VPid, const int *APids, const int *Dpids, const int *SPids, std::string Parameter);
|
||||||
virtual ~cExternRemux();
|
virtual ~cExternRemux();
|
||||||
|
|
||||||
int Put(const uchar *Data, int Count);
|
int Put(const uchar *Data, int Count);
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
/*
|
/*
|
||||||
* $Id: connectionHTTP.c,v 1.12 2007/05/09 09:12:42 schmirl Exp $
|
* $Id: connectionHTTP.c,v 1.13 2008/03/28 15:11:40 schmirl Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
|
||||||
#include "server/connectionHTTP.h"
|
#include "server/connectionHTTP.h"
|
||||||
|
#include "server/menuHTTP.h"
|
||||||
#include "server/setup.h"
|
#include "server/setup.h"
|
||||||
|
|
||||||
cConnectionHTTP::cConnectionHTTP(void):
|
cConnectionHTTP::cConnectionHTTP(void):
|
||||||
cServerConnection("HTTP"),
|
cServerConnection("HTTP"),
|
||||||
m_Status(hsRequest),
|
m_Status(hsRequest),
|
||||||
m_LiveStreamer(NULL),
|
m_LiveStreamer(NULL),
|
||||||
|
m_StreamerParameter(""),
|
||||||
m_Channel(NULL),
|
m_Channel(NULL),
|
||||||
m_Apid(0),
|
m_Apid(0),
|
||||||
m_StreamType((eStreamType)StreamdevServerSetup.HTTPStreamType),
|
m_StreamType((eStreamType)StreamdevServerSetup.HTTPStreamType),
|
||||||
m_ListChannel(NULL)
|
m_ChannelList(NULL)
|
||||||
{
|
{
|
||||||
Dprintf("constructor hsRequest\n");
|
Dprintf("constructor hsRequest\n");
|
||||||
}
|
}
|
||||||
@ -39,6 +41,10 @@ bool cConnectionHTTP::Command(char *Cmd)
|
|||||||
m_Status = hsBody;
|
m_Status = hsBody;
|
||||||
return ProcessRequest();
|
return ProcessRequest();
|
||||||
}
|
}
|
||||||
|
if (strncasecmp(Cmd, "Host:", 5) == 0) {
|
||||||
|
Dprintf("Host-Header\n");
|
||||||
|
m_Host = (std::string) skipspace(Cmd + 5);
|
||||||
|
}
|
||||||
Dprintf("header\n");
|
Dprintf("header\n");
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
@ -53,11 +59,9 @@ bool cConnectionHTTP::ProcessRequest(void)
|
|||||||
if (m_Request.substr(0, 4) == "GET " && CmdGET(m_Request.substr(4))) {
|
if (m_Request.substr(0, 4) == "GET " && CmdGET(m_Request.substr(4))) {
|
||||||
switch (m_Job) {
|
switch (m_Job) {
|
||||||
case hjListing:
|
case hjListing:
|
||||||
return Respond("HTTP/1.0 200 OK")
|
if (m_ChannelList)
|
||||||
&& Respond("Content-Type: text/html")
|
return Respond("%s", true, m_ChannelList->HttpHeader().c_str());
|
||||||
&& Respond("")
|
break;
|
||||||
&& Respond("<html><head><title>VDR Channel Listing</title></head>")
|
|
||||||
&& Respond("<body><ul>");
|
|
||||||
|
|
||||||
case hjTransfer:
|
case hjTransfer:
|
||||||
if (m_Channel == NULL) {
|
if (m_Channel == NULL) {
|
||||||
@ -65,7 +69,7 @@ bool cConnectionHTTP::ProcessRequest(void)
|
|||||||
return Respond("HTTP/1.0 404 not found");
|
return Respond("HTTP/1.0 404 not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
m_LiveStreamer = new cStreamdevLiveStreamer(0);
|
m_LiveStreamer = new cStreamdevLiveStreamer(0, m_StreamerParameter);
|
||||||
cDevice *device = GetDevice(m_Channel, 0);
|
cDevice *device = GetDevice(m_Channel, 0);
|
||||||
if (device != NULL) {
|
if (device != NULL) {
|
||||||
device->SwitchChannel(m_Channel, false);
|
device->SwitchChannel(m_Channel, false);
|
||||||
@ -106,43 +110,21 @@ void cConnectionHTTP::Flushed(void)
|
|||||||
|
|
||||||
switch (m_Job) {
|
switch (m_Job) {
|
||||||
case hjListing:
|
case hjListing:
|
||||||
if (m_ListChannel == NULL) {
|
if (m_ChannelList) {
|
||||||
Respond("</ul></body></html>");
|
if (m_ChannelList->HasNext()) {
|
||||||
|
if (!Respond("%s", true, m_ChannelList->Next().c_str()))
|
||||||
DeferClose();
|
DeferClose();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
DELETENULL(m_ChannelList);
|
||||||
m_Status = hsFinished;
|
m_Status = hsFinished;
|
||||||
|
DeferClose();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// should never be reached
|
||||||
if (m_ListChannel->GroupSep())
|
esyslog("streamdev-server cConnectionHTTP::Flushed(): no channel list");
|
||||||
line = (std::string)"<li>--- " + m_ListChannel->Name() + "---</li>";
|
m_Status = hsFinished;
|
||||||
else {
|
|
||||||
int index = 1;
|
|
||||||
line = (std::string)"<li><a href=\"http://" + LocalIp() + ":"
|
|
||||||
+ (const char*)itoa(StreamdevServerSetup.HTTPServerPort) + "/"
|
|
||||||
+ StreamTypes[m_StreamType] + "/"
|
|
||||||
+ (const char*)m_ListChannel->GetChannelID().ToString() + "\">"
|
|
||||||
+ m_ListChannel->Name() + "</a> ";
|
|
||||||
for (int i = 0; m_ListChannel->Apid(i) != 0; ++i, ++index) {
|
|
||||||
line += "<a href=\"http://" + LocalIp() + ":"
|
|
||||||
+ (const char*)itoa(StreamdevServerSetup.HTTPServerPort) + "/"
|
|
||||||
+ StreamTypes[m_StreamType] + "/"
|
|
||||||
+ (const char*)m_ListChannel->GetChannelID().ToString() + "+"
|
|
||||||
+ (const char*)itoa(index) + "\">("
|
|
||||||
+ m_ListChannel->Alang(i) + ")</a> ";
|
|
||||||
}
|
|
||||||
for (int i = 0; m_ListChannel->Dpid(i) != 0; ++i, ++index) {
|
|
||||||
line += "<a href=\"http://" + LocalIp() + ":"
|
|
||||||
+ (const char*)itoa(StreamdevServerSetup.HTTPServerPort) + "/"
|
|
||||||
+ StreamTypes[m_StreamType] + "/"
|
|
||||||
+ (const char*)m_ListChannel->GetChannelID().ToString() + "+"
|
|
||||||
+ (const char*)itoa(index) + "\">("
|
|
||||||
+ m_ListChannel->Dlang(i) + ")</a> ";
|
|
||||||
}
|
|
||||||
line += "</li>";
|
|
||||||
}
|
|
||||||
if (!Respond(line.c_str()))
|
|
||||||
DeferClose();
|
|
||||||
m_ListChannel = Channels.Next(m_ListChannel);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case hjTransfer:
|
case hjTransfer:
|
||||||
@ -155,49 +137,131 @@ void cConnectionHTTP::Flushed(void)
|
|||||||
|
|
||||||
bool cConnectionHTTP::CmdGET(const std::string &Opts)
|
bool cConnectionHTTP::CmdGET(const std::string &Opts)
|
||||||
{
|
{
|
||||||
const char *sp = Opts.c_str(), *ptr = sp, *ep;
|
const char *ptr, *sp, *pp, *fp, *xp, *qp, *ep;
|
||||||
const cChannel *chan;
|
const cChannel *chan;
|
||||||
int apid = 0;
|
int apid = 0;
|
||||||
|
|
||||||
ptr = skipspace(ptr);
|
ptr = Opts.c_str();
|
||||||
while (*ptr == '/')
|
|
||||||
++ptr;
|
|
||||||
|
|
||||||
if (strncasecmp(ptr, "PS/", 3) == 0) {
|
// find begin of URL
|
||||||
|
sp = skipspace(ptr);
|
||||||
|
// find end of URL (\0 or first space character)
|
||||||
|
for (ep = sp; *ep && !isspace(*ep); ep++)
|
||||||
|
;
|
||||||
|
// find begin of query string (first ?)
|
||||||
|
for (qp = sp; qp < ep && *qp != '?'; qp++)
|
||||||
|
;
|
||||||
|
// find begin of filename (last /)
|
||||||
|
for (fp = qp; fp > sp && *fp != '/'; --fp)
|
||||||
|
;
|
||||||
|
// find begin of section params (first ;)
|
||||||
|
for (pp = sp; pp < fp && *pp != ';'; pp++)
|
||||||
|
;
|
||||||
|
// find filename extension (first .)
|
||||||
|
for (xp = fp; xp < qp && *xp != '.'; xp++)
|
||||||
|
;
|
||||||
|
if (qp - xp > 5) // too long for a filename extension
|
||||||
|
xp = qp;
|
||||||
|
|
||||||
|
std::string type, filespec, fileext, query;
|
||||||
|
// Streamtype with leading / stripped off
|
||||||
|
if (pp > sp)
|
||||||
|
type = Opts.substr(sp - ptr + 1, pp - sp - 1);
|
||||||
|
// Section parameters with leading ; stripped off
|
||||||
|
if (fp > pp)
|
||||||
|
m_StreamerParameter = Opts.substr(pp - ptr + 1, fp - pp - 1);
|
||||||
|
// file basename with leading / stripped off
|
||||||
|
if (xp > fp)
|
||||||
|
filespec = Opts.substr(fp - ptr + 1, xp - fp - 1);
|
||||||
|
// file extension including leading .
|
||||||
|
fileext = Opts.substr(xp - ptr, qp - xp);
|
||||||
|
// query string including leading ?
|
||||||
|
query = Opts.substr(qp - ptr, ep - qp);
|
||||||
|
|
||||||
|
Dprintf("before channelfromstring: type(%s) param(%s) filespec(%s) fileext(%s) query(%s)\n", type.c_str(), m_StreamerParameter.c_str(), filespec.c_str(), fileext.c_str(), query.c_str());
|
||||||
|
|
||||||
|
const char* pType = type.c_str();
|
||||||
|
if (strcasecmp(pType, "PS") == 0) {
|
||||||
m_StreamType = stPS;
|
m_StreamType = stPS;
|
||||||
ptr += 3;
|
} else if (strcasecmp(pType, "PES") == 0) {
|
||||||
} else if (strncasecmp(ptr, "PES/", 4) == 0) {
|
|
||||||
m_StreamType = stPES;
|
m_StreamType = stPES;
|
||||||
ptr += 4;
|
} else if (strcasecmp(pType, "TS") == 0) {
|
||||||
} else if (strncasecmp(ptr, "TS/", 3) == 0) {
|
|
||||||
m_StreamType = stTS;
|
m_StreamType = stTS;
|
||||||
ptr += 3;
|
} else if (strcasecmp(pType, "ES") == 0) {
|
||||||
} else if (strncasecmp(ptr, "ES/", 3) == 0) {
|
|
||||||
m_StreamType = stES;
|
m_StreamType = stES;
|
||||||
ptr += 3;
|
} else if (strcasecmp(pType, "Extern") == 0) {
|
||||||
} else if (strncasecmp(ptr, "Extern/", 3) == 0) {
|
|
||||||
m_StreamType = stExtern;
|
m_StreamType = stExtern;
|
||||||
ptr += 7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while (*ptr == '/')
|
std::string groupTarget;
|
||||||
++ptr;
|
cChannelIterator *iterator = NULL;
|
||||||
for (ep = ptr + strlen(ptr); ep >= ptr && !isspace(*ep); --ep)
|
|
||||||
;
|
|
||||||
|
|
||||||
std::string filespec = Opts.substr(ptr - sp, ep - ptr);
|
if (filespec.compare("tree") == 0) {
|
||||||
Dprintf("substr: %s\n", filespec.c_str());
|
const cChannel* c = NULL;
|
||||||
|
size_t groupIndex = query.find("group=");
|
||||||
|
if (groupIndex != std::string::npos)
|
||||||
|
c = cChannelList::GetGroup(atoi(query.c_str() + groupIndex + 6));
|
||||||
|
iterator = new cListTree(c);
|
||||||
|
groupTarget = filespec + fileext;
|
||||||
|
} else if (filespec.compare("groups") == 0) {
|
||||||
|
iterator = new cListGroups();
|
||||||
|
groupTarget = (std::string) "group" + fileext;
|
||||||
|
} else if (filespec.compare("group") == 0) {
|
||||||
|
const cChannel* c = NULL;
|
||||||
|
size_t groupIndex = query.find("group=");
|
||||||
|
if (groupIndex != std::string::npos)
|
||||||
|
c = cChannelList::GetGroup(atoi(query.c_str() + groupIndex + 6));
|
||||||
|
iterator = new cListGroup(c);
|
||||||
|
} else if (filespec.compare("channels") == 0) {
|
||||||
|
iterator = new cListChannels();
|
||||||
|
} else if (filespec.compare("all") == 0 ||
|
||||||
|
(filespec.empty() && fileext.empty())) {
|
||||||
|
iterator = new cListAll();
|
||||||
|
}
|
||||||
|
|
||||||
Dprintf("before channelfromstring\n");
|
if (iterator) {
|
||||||
if (filespec == "" || filespec.substr(0, 12) == "channels.htm") {
|
if (filespec.empty() || fileext.compare(".htm") == 0 || fileext.compare(".html") == 0) {
|
||||||
m_ListChannel = Channels.First();
|
m_ChannelList = new cHtmlChannelList(iterator, m_StreamType, (filespec + fileext + query).c_str(), groupTarget.c_str());
|
||||||
m_Job = hjListing;
|
m_Job = hjListing;
|
||||||
|
} else if (fileext.compare(".m3u") == 0) {
|
||||||
|
std::string base;
|
||||||
|
if (*(m_Host.c_str()))
|
||||||
|
base = "http://" + m_Host + "/";
|
||||||
|
else
|
||||||
|
base = (std::string) "http://" + LocalIp() + ":" +
|
||||||
|
(const char*) itoa(StreamdevServerSetup.HTTPServerPort) + "/";
|
||||||
|
if (type.empty())
|
||||||
|
{
|
||||||
|
switch (m_StreamType)
|
||||||
|
{
|
||||||
|
case stTS: base += "TS/"; break;
|
||||||
|
case stPS: base += "PS/"; break;
|
||||||
|
case stPES: base += "PES/"; break;
|
||||||
|
case stES: base += "ES/"; break;
|
||||||
|
case stExtern: base += "Extern/"; break;
|
||||||
|
default: break;
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
base += type;
|
||||||
|
if (!m_StreamerParameter.empty())
|
||||||
|
base += ";" + m_StreamerParameter;
|
||||||
|
base += "/";
|
||||||
|
}
|
||||||
|
m_ChannelList = new cM3uChannelList(iterator, base.c_str());
|
||||||
|
m_Job = hjListing;
|
||||||
|
} else {
|
||||||
|
delete iterator;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if ((chan = ChannelFromString(filespec.c_str(), &apid)) != NULL) {
|
} else if ((chan = ChannelFromString(filespec.c_str(), &apid)) != NULL) {
|
||||||
m_Channel = chan;
|
m_Channel = chan;
|
||||||
m_Apid = apid;
|
m_Apid = apid;
|
||||||
Dprintf("Apid is %d\n", apid);
|
Dprintf("Apid is %d\n", apid);
|
||||||
m_Job = hjTransfer;
|
m_Job = hjTransfer;
|
||||||
}
|
} else
|
||||||
|
return false;
|
||||||
|
|
||||||
Dprintf("after channelfromstring\n");
|
Dprintf("after channelfromstring\n");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* $Id: connectionHTTP.h,v 1.4 2007/04/02 10:32:34 schmirl Exp $
|
* $Id: connectionHTTP.h,v 1.5 2008/03/28 15:11:40 schmirl Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef VDR_STREAMDEV_SERVERS_CONNECTIONHTTP_H
|
#ifndef VDR_STREAMDEV_SERVERS_CONNECTIONHTTP_H
|
||||||
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
class cChannel;
|
class cChannel;
|
||||||
class cStreamdevLiveStreamer;
|
class cStreamdevLiveStreamer;
|
||||||
|
class cChannelList;
|
||||||
|
|
||||||
class cConnectionHTTP: public cServerConnection {
|
class cConnectionHTTP: public cServerConnection {
|
||||||
private:
|
private:
|
||||||
@ -28,16 +29,18 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::string m_Request;
|
std::string m_Request;
|
||||||
|
std::string m_Host;
|
||||||
//std::map<std::string,std::string> m_Headers; TODO: later?
|
//std::map<std::string,std::string> m_Headers; TODO: later?
|
||||||
eHTTPStatus m_Status;
|
eHTTPStatus m_Status;
|
||||||
eHTTPJob m_Job;
|
eHTTPJob m_Job;
|
||||||
// job: transfer
|
// job: transfer
|
||||||
cStreamdevLiveStreamer *m_LiveStreamer;
|
cStreamdevLiveStreamer *m_LiveStreamer;
|
||||||
|
std::string m_StreamerParameter;
|
||||||
const cChannel *m_Channel;
|
const cChannel *m_Channel;
|
||||||
int m_Apid;
|
int m_Apid;
|
||||||
eStreamType m_StreamType;
|
eStreamType m_StreamType;
|
||||||
// job: listing
|
// job: listing
|
||||||
const cChannel *m_ListChannel;
|
cChannelList *m_ChannelList;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool ProcessRequest(void);
|
bool ProcessRequest(void);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* $Id: connectionVTP.c,v 1.15 2007/09/21 12:45:31 schmirl Exp $
|
* $Id: connectionVTP.c,v 1.17 2008/03/13 16:01:18 schmirl Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "server/connectionVTP.h"
|
#include "server/connectionVTP.h"
|
||||||
@ -186,7 +186,11 @@ bool cLSTEHandler::Next(bool &Last)
|
|||||||
case Event:
|
case Event:
|
||||||
if (m_Event != NULL) {
|
if (m_Event != NULL) {
|
||||||
m_State = Title;
|
m_State = Title;
|
||||||
|
#ifdef __FreeBSD__
|
||||||
|
return m_Client->Respond(-215, "E %u %d %d %X", m_Event->EventID(),
|
||||||
|
#else
|
||||||
return m_Client->Respond(-215, "E %u %ld %d %X", m_Event->EventID(),
|
return m_Client->Respond(-215, "E %u %ld %d %X", m_Event->EventID(),
|
||||||
|
#endif
|
||||||
m_Event->StartTime(), m_Event->Duration(),
|
m_Event->StartTime(), m_Event->Duration(),
|
||||||
m_Event->TableID());
|
m_Event->TableID());
|
||||||
} else {
|
} else {
|
||||||
@ -225,7 +229,11 @@ bool cLSTEHandler::Next(bool &Last)
|
|||||||
case Vps:
|
case Vps:
|
||||||
m_State = EndEvent;
|
m_State = EndEvent;
|
||||||
if (m_Event->Vps())
|
if (m_Event->Vps())
|
||||||
|
#ifdef __FreeBSD__
|
||||||
|
return m_Client->Respond(-215, "V %d", m_Event->Vps());
|
||||||
|
#else
|
||||||
return m_Client->Respond(-215, "V %ld", m_Event->Vps());
|
return m_Client->Respond(-215, "V %ld", m_Event->Vps());
|
||||||
|
#endif
|
||||||
else
|
else
|
||||||
return Next(Last);
|
return Next(Last);
|
||||||
break;
|
break;
|
||||||
@ -470,6 +478,7 @@ cConnectionVTP::cConnectionVTP(void):
|
|||||||
m_FilterStreamer(NULL),
|
m_FilterStreamer(NULL),
|
||||||
m_LastCommand(NULL),
|
m_LastCommand(NULL),
|
||||||
m_StreamType(stTSPIDS),
|
m_StreamType(stTSPIDS),
|
||||||
|
m_FiltersSupport(false),
|
||||||
m_LSTEHandler(NULL),
|
m_LSTEHandler(NULL),
|
||||||
m_LSTCHandler(NULL),
|
m_LSTCHandler(NULL),
|
||||||
m_LSTTHandler(NULL)
|
m_LSTTHandler(NULL)
|
||||||
@ -600,8 +609,10 @@ bool cConnectionVTP::CmdCAPS(char *Opts)
|
|||||||
//
|
//
|
||||||
// Deliver section filters data in separate, channel-independent data stream
|
// Deliver section filters data in separate, channel-independent data stream
|
||||||
//
|
//
|
||||||
if (strcasecmp(Opts, "FILTERS") == 0)
|
if (strcasecmp(Opts, "FILTERS") == 0) {
|
||||||
|
m_FiltersSupport = true;
|
||||||
return Respond(220, "Capability \"%s\" accepted", Opts);
|
return Respond(220, "Capability \"%s\" accepted", Opts);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return Respond(561, "Capability \"%s\" not known", Opts);
|
return Respond(561, "Capability \"%s\" not known", Opts);
|
||||||
@ -672,6 +683,7 @@ bool cConnectionVTP::CmdPORT(char *Opts)
|
|||||||
|
|
||||||
#if VDRVERSNUM >= 10300
|
#if VDRVERSNUM >= 10300
|
||||||
if (id == siLiveFilter) {
|
if (id == siLiveFilter) {
|
||||||
|
m_FiltersSupport = true;
|
||||||
if(m_FilterStreamer)
|
if(m_FilterStreamer)
|
||||||
m_FilterStreamer->Stop();
|
m_FilterStreamer->Stop();
|
||||||
delete m_FilterSocket;
|
delete m_FilterSocket;
|
||||||
@ -735,10 +747,12 @@ bool cConnectionVTP::CmdTUNE(char *Opts)
|
|||||||
m_LiveStreamer->Start(m_LiveSocket);
|
m_LiveStreamer->Start(m_LiveSocket);
|
||||||
|
|
||||||
#if VDRVERSNUM >= 10300
|
#if VDRVERSNUM >= 10300
|
||||||
|
if(m_FiltersSupport) {
|
||||||
if(!m_FilterStreamer)
|
if(!m_FilterStreamer)
|
||||||
m_FilterStreamer = new cStreamdevFilterStreamer;
|
m_FilterStreamer = new cStreamdevFilterStreamer;
|
||||||
m_FilterStreamer->SetDevice(dev);
|
m_FilterStreamer->SetDevice(dev);
|
||||||
//m_FilterStreamer->SetChannel(chan);
|
//m_FilterStreamer->SetChannel(chan);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return Respond(220, "Channel tuned");
|
return Respond(220, "Channel tuned");
|
||||||
|
@ -24,6 +24,7 @@ private:
|
|||||||
|
|
||||||
char *m_LastCommand;
|
char *m_LastCommand;
|
||||||
eStreamType m_StreamType;
|
eStreamType m_StreamType;
|
||||||
|
bool m_FiltersSupport;
|
||||||
|
|
||||||
// Members adopted for SVDRP
|
// Members adopted for SVDRP
|
||||||
cRecordings Recordings;
|
cRecordings Recordings;
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
#include "remux/extern.h"
|
#include "remux/extern.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
|
#define TSPATREPACKER
|
||||||
|
|
||||||
// --- cStreamdevLiveReceiver -------------------------------------------------
|
// --- cStreamdevLiveReceiver -------------------------------------------------
|
||||||
|
|
||||||
class cStreamdevLiveReceiver: public cReceiver {
|
class cStreamdevLiveReceiver: public cReceiver {
|
||||||
@ -232,9 +234,48 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
|
|||||||
pmtSid = assoc.getServiceId();
|
pmtSid = assoc.getServiceId();
|
||||||
if (Length < TS_SIZE-5) {
|
if (Length < TS_SIZE-5) {
|
||||||
// repack PAT to TS frame and send to client
|
// repack PAT to TS frame and send to client
|
||||||
|
#ifndef TSPATREPACKER
|
||||||
uint8_t pat_ts[TS_SIZE] = {TS_SYNC_BYTE, 0x40 /* pusi=1 */, 0 /* pid=0 */, 0x10 /* adaption=1 */, 0 /* pointer */};
|
uint8_t pat_ts[TS_SIZE] = {TS_SYNC_BYTE, 0x40 /* pusi=1 */, 0 /* pid=0 */, 0x10 /* adaption=1 */, 0 /* pointer */};
|
||||||
memcpy(pat_ts + 5, Data, Length);
|
memcpy(pat_ts + 5, Data, Length);
|
||||||
m_Streamer->Put(pat_ts, TS_SIZE);
|
m_Streamer->Put(pat_ts, TS_SIZE);
|
||||||
|
#else
|
||||||
|
int ts_id;
|
||||||
|
unsigned int crc, i, len;
|
||||||
|
uint8_t *tmp, tspat_buf[TS_SIZE];
|
||||||
|
memset(tspat_buf, 0xff, TS_SIZE);
|
||||||
|
memset(tspat_buf, 0x0, 4 + 12 + 5); // TS_HDR_LEN + PAT_TABLE_LEN + 5
|
||||||
|
ts_id = Channel->Tid(); // Get transport stream id of the channel
|
||||||
|
tspat_buf[0] = TS_SYNC_BYTE; // Transport packet header sunchronization byte (1000011 = 0x47h)
|
||||||
|
tspat_buf[1] = 0x40; // Set payload unit start indicator bit
|
||||||
|
tspat_buf[2] = 0x0; // PID
|
||||||
|
tspat_buf[3] = 0x10; // Set payload flag to indicate precence of payload data
|
||||||
|
tspat_buf[4] = 0x0; // PSI
|
||||||
|
tspat_buf[5] = 0x0; // PAT table id
|
||||||
|
tspat_buf[6] = 0xb0; // Section syntax indicator bit and reserved bits set
|
||||||
|
tspat_buf[7] = 12 + 1; // Section length (12 bit): PAT_TABLE_LEN + 1
|
||||||
|
tspat_buf[8] = (ts_id >> 8) & 0xff; // Transport stream ID (bits 8-15)
|
||||||
|
tspat_buf[9] = (ts_id & 0xff); // Transport stream ID (bits 0-7)
|
||||||
|
tspat_buf[10] = 0x01; // Version number 0, Current next indicator bit set
|
||||||
|
tspat_buf[11] = 0x0; // Section number
|
||||||
|
tspat_buf[12] = 0x0; // Last section number
|
||||||
|
tspat_buf[13] = (pmtSid >> 8) & 0xff; // Program number (bits 8-15)
|
||||||
|
tspat_buf[14] = (pmtSid & 0xff); // Program number (bits 0-7)
|
||||||
|
tspat_buf[15] = (pmtPid >> 8) & 0xff; // Network ID (bits 8-12)
|
||||||
|
tspat_buf[16] = (pmtPid & 0xff); // Network ID (bits 0-7)
|
||||||
|
crc = 0xffffffff;
|
||||||
|
len = 12; // PAT_TABLE_LEN
|
||||||
|
tmp = &tspat_buf[4 + 1]; // TS_HDR_LEN + 1
|
||||||
|
while (len--) {
|
||||||
|
crc ^= *tmp++ << 24;
|
||||||
|
for (i = 0; i < 8; i++)
|
||||||
|
crc = (crc << 1) ^ ((crc & 0x80000000) ? 0x04c11db7 : 0); // CRC32POLY
|
||||||
|
}
|
||||||
|
tspat_buf[17] = crc >> 24 & 0xff; // Checksum
|
||||||
|
tspat_buf[18] = crc >> 16 & 0xff; // Checksum
|
||||||
|
tspat_buf[19] = crc >> 8 & 0xff; // Checksum
|
||||||
|
tspat_buf[20] = crc & 0xff; // Checksum
|
||||||
|
m_Streamer->Put(tspat_buf, TS_SIZE);
|
||||||
|
#endif
|
||||||
} else
|
} else
|
||||||
isyslog("cStreamdevPatFilter: PAT size %d too large to fit in one TS", Length);
|
isyslog("cStreamdevPatFilter: PAT size %d too large to fit in one TS", Length);
|
||||||
m_Streamer->SetPids(pmtPid);
|
m_Streamer->SetPids(pmtPid);
|
||||||
@ -268,9 +309,9 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
|
|||||||
#if 0
|
#if 0
|
||||||
pids[npids++] = 0x10; // pid 0x10, tid 0x40: NIT
|
pids[npids++] = 0x10; // pid 0x10, tid 0x40: NIT
|
||||||
pids[npids++] = 0x11; // pid 0x11, tid 0x42: SDT
|
pids[npids++] = 0x11; // pid 0x11, tid 0x42: SDT
|
||||||
pids[npids++] = 0x12; // pid 0x12, tid 0x4E...0x6F: EIT
|
|
||||||
pids[npids++] = 0x14; // pid 0x14, tid 0x70: TDT
|
pids[npids++] = 0x14; // pid 0x14, tid 0x70: TDT
|
||||||
#endif
|
#endif
|
||||||
|
pids[npids++] = 0x12; // pid 0x12, tid 0x4E...0x6F: EIT
|
||||||
for (SI::Loop::Iterator it; pmt.streamLoop.getNext(stream, it); )
|
for (SI::Loop::Iterator it; pmt.streamLoop.getNext(stream, it); )
|
||||||
if (0 != (pids[npids] = GetPid(stream)) && npids < MAXRECEIVEPIDS)
|
if (0 != (pids[npids] = GetPid(stream)) && npids < MAXRECEIVEPIDS)
|
||||||
npids++;
|
npids++;
|
||||||
@ -282,9 +323,10 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
|
|||||||
|
|
||||||
// --- cStreamdevLiveStreamer -------------------------------------------------
|
// --- cStreamdevLiveStreamer -------------------------------------------------
|
||||||
|
|
||||||
cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority):
|
cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority, std::string Parameter):
|
||||||
cStreamdevStreamer("streamdev-livestreaming"),
|
cStreamdevStreamer("streamdev-livestreaming"),
|
||||||
m_Priority(Priority),
|
m_Priority(Priority),
|
||||||
|
m_Parameter(Parameter),
|
||||||
m_NumPids(0),
|
m_NumPids(0),
|
||||||
m_StreamType(stTSPIDS),
|
m_StreamType(stTSPIDS),
|
||||||
m_Channel(NULL),
|
m_Channel(NULL),
|
||||||
@ -447,7 +489,7 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str
|
|||||||
|
|
||||||
case stExtern:
|
case stExtern:
|
||||||
m_ExtRemux = new cExternRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(),
|
m_ExtRemux = new cExternRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(),
|
||||||
m_Channel->Spids());
|
m_Channel->Spids(), m_Parameter);
|
||||||
return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids());
|
return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids());
|
||||||
|
|
||||||
case stTSPIDS:
|
case stTSPIDS:
|
||||||
|
@ -19,6 +19,7 @@ class cStreamdevLiveReceiver;
|
|||||||
class cStreamdevLiveStreamer: public cStreamdevStreamer {
|
class cStreamdevLiveStreamer: public cStreamdevStreamer {
|
||||||
private:
|
private:
|
||||||
int m_Priority;
|
int m_Priority;
|
||||||
|
std::string m_Parameter;
|
||||||
int m_Pids[MAXRECEIVEPIDS + 1];
|
int m_Pids[MAXRECEIVEPIDS + 1];
|
||||||
int m_NumPids;
|
int m_NumPids;
|
||||||
eStreamType m_StreamType;
|
eStreamType m_StreamType;
|
||||||
@ -35,7 +36,7 @@ private:
|
|||||||
bool HasPid(int Pid);
|
bool HasPid(int Pid);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
cStreamdevLiveStreamer(int Priority);
|
cStreamdevLiveStreamer(int Priority, std::string Parameter = "");
|
||||||
virtual ~cStreamdevLiveStreamer();
|
virtual ~cStreamdevLiveStreamer();
|
||||||
|
|
||||||
void SetDevice(cDevice *Device) { m_Device = Device; }
|
void SetDevice(cDevice *Device) { m_Device = Device; }
|
||||||
|
420
server/menuHTTP.c
Normal file
420
server/menuHTTP.c
Normal file
@ -0,0 +1,420 @@
|
|||||||
|
#include <vdr/channels.h>
|
||||||
|
#include "server/menuHTTP.h"
|
||||||
|
|
||||||
|
//**************************** cChannelIterator **************
|
||||||
|
cChannelIterator::cChannelIterator(cChannel *First): channel(First)
|
||||||
|
{}
|
||||||
|
|
||||||
|
const cChannel* cChannelIterator::Next()
|
||||||
|
{
|
||||||
|
const cChannel *current = channel;
|
||||||
|
channel = NextChannel(channel);
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
//**************************** cListAll **************
|
||||||
|
cListAll::cListAll(): cChannelIterator(Channels.First())
|
||||||
|
{}
|
||||||
|
|
||||||
|
const cChannel* cListAll::NextChannel(const cChannel *Channel)
|
||||||
|
{
|
||||||
|
if (Channel)
|
||||||
|
Channel = Channels.Next(Channel);
|
||||||
|
return Channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
//**************************** cListChannels **************
|
||||||
|
cListChannels::cListChannels(): cChannelIterator(Channels.Get(Channels.GetNextNormal(-1)))
|
||||||
|
{}
|
||||||
|
|
||||||
|
const cChannel* cListChannels::NextChannel(const cChannel *Channel)
|
||||||
|
{
|
||||||
|
if (Channel)
|
||||||
|
Channel = Channels.Get(Channels.GetNextNormal(Channel->Index()));
|
||||||
|
return Channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ********************* cListGroups ****************
|
||||||
|
cListGroups::cListGroups(): cChannelIterator(Channels.Get(Channels.GetNextGroup(-1)))
|
||||||
|
{}
|
||||||
|
|
||||||
|
const cChannel* cListGroups::NextChannel(const cChannel *Channel)
|
||||||
|
{
|
||||||
|
if (Channel)
|
||||||
|
Channel = Channels.Get(Channels.GetNextGroup(Channel->Index()));
|
||||||
|
return Channel;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// ********************* cListGroup ****************
|
||||||
|
cListGroup::cListGroup(const cChannel *Group): cChannelIterator((Group && Group->GroupSep() && Channels.Next(Group) && !Channels.Next(Group)->GroupSep()) ? Channels.Next(Group) : NULL)
|
||||||
|
{}
|
||||||
|
|
||||||
|
const cChannel* cListGroup::NextChannel(const cChannel *Channel)
|
||||||
|
{
|
||||||
|
if (Channel)
|
||||||
|
Channel = Channels.Next(Channel);
|
||||||
|
return (Channel && !Channel->GroupSep()) ? Channel : NULL;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// ********************* cListTree ****************
|
||||||
|
cListTree::cListTree(const cChannel *SelectedGroup): cChannelIterator(Channels.Get(Channels.GetNextGroup(-1)))
|
||||||
|
{
|
||||||
|
selectedGroup = SelectedGroup;
|
||||||
|
currentGroup = Channels.Get(Channels.GetNextGroup(-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
const cChannel* cListTree::NextChannel(const cChannel *Channel)
|
||||||
|
{
|
||||||
|
if (currentGroup == selectedGroup)
|
||||||
|
{
|
||||||
|
if (Channel)
|
||||||
|
Channel = Channels.Next(Channel);
|
||||||
|
if (Channel && Channel->GroupSep())
|
||||||
|
currentGroup = Channel;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Channel)
|
||||||
|
Channel = Channels.Get(Channels.GetNextGroup(Channel->Index()));
|
||||||
|
currentGroup = Channel;
|
||||||
|
}
|
||||||
|
return Channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ******************** cChannelList ******************
|
||||||
|
cChannelList::cChannelList(cChannelIterator *Iterator) : iterator(Iterator)
|
||||||
|
{}
|
||||||
|
|
||||||
|
cChannelList::~cChannelList()
|
||||||
|
{
|
||||||
|
delete iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cChannelList::GetGroupIndex(const cChannel *Group)
|
||||||
|
{
|
||||||
|
int index = 0;
|
||||||
|
for (int curr = Channels.GetNextGroup(-1); curr >= 0; curr = Channels.GetNextGroup(curr))
|
||||||
|
{
|
||||||
|
if (Channels.Get(curr) == Group)
|
||||||
|
return index;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cChannel* cChannelList::GetGroup(int Index)
|
||||||
|
{
|
||||||
|
int group = Channels.GetNextGroup(-1);
|
||||||
|
while (Index-- && group >= 0)
|
||||||
|
group = Channels.GetNextGroup(group);
|
||||||
|
return group >= 0 ? Channels.Get(group) : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ******************** cHtmlChannelList ******************
|
||||||
|
const char* cHtmlChannelList::menu =
|
||||||
|
"[<a href=\"/\">Home</a> (<a href=\"all.html\">no script</a>)] "
|
||||||
|
"[<a href=\"tree.html\">Tree View</a>] "
|
||||||
|
"[<a href=\"groups.html\">Groups</a> (<a href=\"groups.m3u\">Playlist</a>)] "
|
||||||
|
"[<a href=\"channels.html\">Channels</a> (<a href=\"channels.m3u\">Playlist</a>)] ";
|
||||||
|
|
||||||
|
const char* cHtmlChannelList::css =
|
||||||
|
"<style type=\"text/css\">\n"
|
||||||
|
"<!--\n"
|
||||||
|
"a:link, a:visited, a:hover, a:active, a:focus { color:#333399; }\n"
|
||||||
|
"body { font:100% Verdana, Arial, Helvetica, sans-serif; background-color:#9999FF; margin:1em; }\n"
|
||||||
|
".menu { position:fixed; top:0px; left:1em; right:1em; height:3em; text-align:center; background-color:white; border:inset 2px #9999ff; }\n"
|
||||||
|
"h2 { font-size:150%; margin:0em; padding:0em 1.5em; }\n"
|
||||||
|
".contents { margin-top:5em; background-color:white; }\n"
|
||||||
|
".group { background:url(data:image/gif;base64,R0lGODdhAQAeAIQeAJub/5yc/6Cf/6Oj/6am/6qq/66u/7Gx/7S0/7i4/7u8/7+//8LD/8bG/8nK/83N/9HQ/9TU/9fX/9va/97e/+Lh/+Xl/+no/+3t//Dw//Pz//b3//v7//7+/////////ywAAAAAAQAeAAAFGCAQCANRGAeSKAvTOA8USRNVWReWaRvXhQA7) repeat-x; border:inset 2px #9999ff; }\n"
|
||||||
|
".items { border-top:dashed 1px; margin-top:0px; margin-bottom:0px; padding:0.7em 5em; }\n"
|
||||||
|
".apid { padding-left:28px; margin:0.5em; background:url(data:image/gif;base64,R0lGODlhGwASAKEBAAAAAP///////////yH5BAEKAAEALAAAAAAbABIAAAJAjI+pywj5WgPxVAmpNRcHqnGb94FPhE7m+okts7JvusSmSys2iLv6TstldjMfhhUkcXi+zjFUVFo6TiVVij0UAAA7) no-repeat; }\n"
|
||||||
|
".dpid { padding-left:28px; margin:0.5em; background:url(data:image/gif;base64,R0lGODlhGwASAKEBAAAAAP///////////yH5BAEKAAEALAAAAAAbABIAAAJFjI+py+0BopwAUoqivRvr83UaZ4RWMnVoBbLZaJbuqcCLGcv0+t5Vvgu2hLrh6pfDzVSpnlGEbAZhnIutZaVmH9yuV1EAADs=) no-repeat; }\n"
|
||||||
|
"button { width:2em; margin:0.2em 0.5em; vertical-align:top; }\n"
|
||||||
|
"-->\n"
|
||||||
|
"</style>";
|
||||||
|
|
||||||
|
const char* cHtmlChannelList::js =
|
||||||
|
"<script language=\"JavaScript\">\n"
|
||||||
|
"<!--\n"
|
||||||
|
|
||||||
|
"function eventTarget(evt) {\n"
|
||||||
|
" if (!evt) evt = window.event;\n"
|
||||||
|
" if (evt.target) return evt.target;\n"
|
||||||
|
" else if (evt.srcElement) return evt.srcElement;\n"
|
||||||
|
" else return null;\n"
|
||||||
|
"}\n"
|
||||||
|
|
||||||
|
// toggle visibility of a group
|
||||||
|
"function clickHandler(evt) {\n"
|
||||||
|
" var button = eventTarget(evt);\n"
|
||||||
|
" if (button) {\n"
|
||||||
|
" var group = document.getElementById('c' + button.id);\n"
|
||||||
|
" if (group) {\n"
|
||||||
|
" button.removeChild(button.firstChild);\n"
|
||||||
|
" if (group.style.display == 'block') {\n"
|
||||||
|
" button.appendChild(document.createTextNode(\"+\"));\n"
|
||||||
|
" group.style.display = 'none';\n"
|
||||||
|
" } else {\n"
|
||||||
|
" button.appendChild(document.createTextNode(\"-\"));\n"
|
||||||
|
" group.style.display = 'block';\n"
|
||||||
|
" }\n"
|
||||||
|
" }\n"
|
||||||
|
" }\n"
|
||||||
|
"}\n"
|
||||||
|
|
||||||
|
// insert a click button infront of each h2 and an id to the corresponding list
|
||||||
|
"function init() {\n"
|
||||||
|
" var titles = document.getElementsByTagName('h2');\n"
|
||||||
|
" for (var i = 0; i < titles.length; i++) {\n"
|
||||||
|
" var button = document.createElement('button');\n"
|
||||||
|
" button.id = 'g' + i;\n"
|
||||||
|
" button.onclick = clickHandler;\n"
|
||||||
|
" button.appendChild(document.createTextNode('+'));\n"
|
||||||
|
" titles[i].insertBefore(button, titles[i].firstChild);\n"
|
||||||
|
" var group = titles[i].nextSibling;\n"
|
||||||
|
" while (group) {\n"
|
||||||
|
" if (group.className && group.className == 'items') {\n"
|
||||||
|
" group.id = 'cg' + i;\n"
|
||||||
|
" break;\n"
|
||||||
|
" }\n"
|
||||||
|
" group = group.nextSibling;\n"
|
||||||
|
" }\n"
|
||||||
|
" }\n"
|
||||||
|
"}\n"
|
||||||
|
|
||||||
|
"window.onload = init;\n"
|
||||||
|
|
||||||
|
// hide lists before the browser renders it
|
||||||
|
"if (document.styleSheets[0].insertRule)\n"
|
||||||
|
" document.styleSheets[0].insertRule('.items { display:none }', 0);\n"
|
||||||
|
"else if (document.styleSheets[0].addRule)\n"
|
||||||
|
" document.styleSheets[0].addRule('.items', 'display:none');\n"
|
||||||
|
|
||||||
|
"//-->\n"
|
||||||
|
"</script>";
|
||||||
|
|
||||||
|
|
||||||
|
std::string cHtmlChannelList::StreamTypeMenu()
|
||||||
|
{
|
||||||
|
std::string typeMenu;
|
||||||
|
typeMenu += (streamType == stTS ? (std::string) "[TS] " :
|
||||||
|
(std::string) "[<a href=\"/TS/" + self + "\">TS</a>] ");
|
||||||
|
typeMenu += (streamType == stPS ? (std::string) "[PS] " :
|
||||||
|
(std::string) "[<a href=\"/PS/" + self + "\">PS</a>] ");
|
||||||
|
typeMenu += (streamType == stPES ? (std::string) "[PES] " :
|
||||||
|
(std::string) "[<a href=\"/PES/" + self + "\">PES</a>] ");
|
||||||
|
typeMenu += (streamType == stES ? (std::string) "[ES] " :
|
||||||
|
(std::string) "[<a href=\"/ES/" + self + "\">ES</a>] ");
|
||||||
|
typeMenu += (streamType == stExtern ? (std::string) "[Extern] " :
|
||||||
|
(std::string) "[<a href=\"/Extern/" + self + "\">Extern</a>] ");
|
||||||
|
return typeMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
cHtmlChannelList::cHtmlChannelList(cChannelIterator *Iterator, eStreamType StreamType, const char *Self, const char *GroupTarget): cChannelList(Iterator)
|
||||||
|
{
|
||||||
|
streamType = StreamType;
|
||||||
|
self = strdup(Self);
|
||||||
|
groupTarget = (GroupTarget && *GroupTarget) ? strdup(GroupTarget) : NULL;
|
||||||
|
htmlState = hsRoot;
|
||||||
|
current = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
cHtmlChannelList::~cHtmlChannelList()
|
||||||
|
{
|
||||||
|
free((void *) self);
|
||||||
|
free((void *) groupTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cHtmlChannelList::HasNext()
|
||||||
|
{
|
||||||
|
return htmlState != hsPageBottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string cHtmlChannelList::Next()
|
||||||
|
{
|
||||||
|
switch (htmlState)
|
||||||
|
{
|
||||||
|
case hsRoot:
|
||||||
|
htmlState = hsHtmlHead;
|
||||||
|
break;
|
||||||
|
case hsHtmlHead:
|
||||||
|
htmlState = hsCss;
|
||||||
|
break;
|
||||||
|
case hsCss:
|
||||||
|
htmlState = *self ? hsPageTop : hsJs;
|
||||||
|
break;
|
||||||
|
case hsJs:
|
||||||
|
htmlState = hsPageTop;
|
||||||
|
break;
|
||||||
|
case hsPageTop:
|
||||||
|
current = NextChannel();
|
||||||
|
htmlState = current ? (current->GroupSep() ? hsGroupTop : hsPlainTop) : hsPageBottom;
|
||||||
|
break;
|
||||||
|
case hsPlainTop:
|
||||||
|
htmlState = hsPlainItem;
|
||||||
|
break;
|
||||||
|
case hsPlainItem:
|
||||||
|
current = NextChannel();
|
||||||
|
htmlState = current && !current->GroupSep() ? hsPlainItem : hsPlainBottom;
|
||||||
|
break;
|
||||||
|
case hsPlainBottom:
|
||||||
|
htmlState = current ? hsGroupTop : hsPageBottom;
|
||||||
|
break;
|
||||||
|
case hsGroupTop:
|
||||||
|
current = NextChannel();
|
||||||
|
htmlState = current && !current->GroupSep() ? hsItemsTop : hsGroupBottom;
|
||||||
|
break;
|
||||||
|
case hsItemsTop:
|
||||||
|
htmlState = hsItem;
|
||||||
|
break;
|
||||||
|
case hsItem:
|
||||||
|
current = NextChannel();
|
||||||
|
htmlState = current && !current->GroupSep() ? hsItem : hsItemsBottom;
|
||||||
|
break;
|
||||||
|
case hsItemsBottom:
|
||||||
|
htmlState = hsGroupBottom;
|
||||||
|
break;
|
||||||
|
case hsGroupBottom:
|
||||||
|
htmlState = current ? hsGroupTop : hsPageBottom;
|
||||||
|
break;
|
||||||
|
case hsPageBottom:
|
||||||
|
default:
|
||||||
|
esyslog("streamdev-server cHtmlChannelList: invalid call to Next()");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (htmlState)
|
||||||
|
{
|
||||||
|
// NOTE: JavaScript requirements:
|
||||||
|
// Group title is identified by <h2> tag
|
||||||
|
// Channel list must be a sibling of <h2> with class "items"
|
||||||
|
case hsHtmlHead: return "<html><head>" + HtmlHead();
|
||||||
|
case hsCss: return css;
|
||||||
|
case hsJs: return js;
|
||||||
|
case hsPageTop: return "</head><body>" + PageTop() + "<div class=\"contents\">";
|
||||||
|
case hsGroupTop: return "<div class=\"group\"><h2>" + GroupTitle() + "</h2>";
|
||||||
|
case hsItemsTop:
|
||||||
|
case hsPlainTop: return "<ol class=\"items\">";
|
||||||
|
case hsItem:
|
||||||
|
case hsPlainItem: return ItemText();
|
||||||
|
case hsItemsBottom:
|
||||||
|
case hsPlainBottom: return "</ol>";
|
||||||
|
case hsGroupBottom: return "</div>";
|
||||||
|
case hsPageBottom: return "</div>" + PageBottom() + "</body></html>";
|
||||||
|
default: return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string cHtmlChannelList::HtmlHead()
|
||||||
|
{
|
||||||
|
return (std::string) "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string cHtmlChannelList::PageTop()
|
||||||
|
{
|
||||||
|
return (std::string) "<div class=\"menu\"><div>" + menu + "</div><div>" + StreamTypeMenu() + "</div></div>";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string cHtmlChannelList::PageBottom()
|
||||||
|
{
|
||||||
|
return (std::string) "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string cHtmlChannelList::GroupTitle()
|
||||||
|
{
|
||||||
|
if (groupTarget)
|
||||||
|
{
|
||||||
|
return (std::string) "<a href=\"" + groupTarget + "?group=" +
|
||||||
|
(const char*) itoa(cChannelList::GetGroupIndex(current)) +
|
||||||
|
"\">" + current->Name() + "</a>";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (std::string) current->Name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string cHtmlChannelList::ItemText()
|
||||||
|
{
|
||||||
|
std::string line;
|
||||||
|
line += (std::string) "<li value=\"" + (const char*) itoa(current->Number()) + "\">";
|
||||||
|
line += (std::string) "<a href=\"" + (std::string) current->GetChannelID().ToString() + "\">" +
|
||||||
|
current->Name() + "</a>";
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; current->Apid(i) != 0; ++i, ++count)
|
||||||
|
;
|
||||||
|
for (int i = 0; current->Dpid(i) != 0; ++i, ++count)
|
||||||
|
;
|
||||||
|
|
||||||
|
if (count > 1)
|
||||||
|
{
|
||||||
|
int index = 1;
|
||||||
|
for (int i = 0; current->Apid(i) != 0; ++i, ++index) {
|
||||||
|
line += (std::string) " <a href=\"" + (std::string) current->GetChannelID().ToString() +
|
||||||
|
"+" + (const char*)itoa(index) + "\" class=\"apid\">" + current->Alang(i) + "</a>";
|
||||||
|
}
|
||||||
|
for (int i = 0; current->Dpid(i) != 0; ++i, ++index) {
|
||||||
|
line += (std::string) " <a href=\"" + (std::string) current->GetChannelID().ToString() +
|
||||||
|
"+" + (const char*)itoa(index) + "\" class=\"dpid\">" + current->Dlang(i) + "</a>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
line += "</li>";
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ******************** cM3uChannelList ******************
|
||||||
|
cM3uChannelList::cM3uChannelList(cChannelIterator *Iterator, const char* Base)
|
||||||
|
: cChannelList(Iterator)
|
||||||
|
#if defined(APIVERSNUM) && APIVERSNUM >= 10503
|
||||||
|
, m_IConv(cCharSetConv::SystemCharacterTable(), "UTF-8")
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
base = strdup(Base);
|
||||||
|
m3uState = msFirst;
|
||||||
|
}
|
||||||
|
|
||||||
|
cM3uChannelList::~cM3uChannelList()
|
||||||
|
{
|
||||||
|
free(base);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cM3uChannelList::HasNext()
|
||||||
|
{
|
||||||
|
return m3uState != msLast;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string cM3uChannelList::Next()
|
||||||
|
{
|
||||||
|
if (m3uState == msFirst)
|
||||||
|
{
|
||||||
|
m3uState = msContinue;
|
||||||
|
return "#EXTM3U";
|
||||||
|
}
|
||||||
|
|
||||||
|
const cChannel *channel = NextChannel();
|
||||||
|
if (!channel)
|
||||||
|
{
|
||||||
|
m3uState = msLast;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(APIVERSNUM) && APIVERSNUM >= 10503
|
||||||
|
std::string name = (std::string) m_IConv.Convert(channel->Name());
|
||||||
|
#else
|
||||||
|
std::string name = channel->Name();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (channel->GroupSep())
|
||||||
|
{
|
||||||
|
return (std::string) "#EXTINF:0," + name + "\r\n" +
|
||||||
|
base + "group.m3u?group=" +
|
||||||
|
(const char*) itoa(cChannelList::GetGroupIndex(channel));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (std::string) "#EXTINF:0," +
|
||||||
|
(const char*) itoa(channel->Number()) + " " + name + "\r\n" +
|
||||||
|
base + (std::string) channel->GetChannelID().ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
140
server/menuHTTP.h
Normal file
140
server/menuHTTP.h
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
#ifndef VDR_STREAMDEV_SERVERS_MENUHTTP_H
|
||||||
|
#define VDR_STREAMDEV_SERVERS_MENUHTTP_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "../common.h"
|
||||||
|
|
||||||
|
class cChannel;
|
||||||
|
|
||||||
|
// ******************** cChannelIterator ******************
|
||||||
|
class cChannelIterator
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
const cChannel *channel;
|
||||||
|
protected:
|
||||||
|
virtual const cChannel* NextChannel(const cChannel *Channel) = 0;
|
||||||
|
public:
|
||||||
|
const cChannel* Next();
|
||||||
|
cChannelIterator(cChannel *First);
|
||||||
|
virtual ~cChannelIterator() {};
|
||||||
|
};
|
||||||
|
|
||||||
|
class cListAll: public cChannelIterator
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
virtual const cChannel* NextChannel(const cChannel *Channel);
|
||||||
|
public:
|
||||||
|
cListAll();
|
||||||
|
virtual ~cListAll() {};
|
||||||
|
};
|
||||||
|
|
||||||
|
class cListChannels: public cChannelIterator
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
virtual const cChannel* NextChannel(const cChannel *Channel);
|
||||||
|
public:
|
||||||
|
cListChannels();
|
||||||
|
virtual ~cListChannels() {};
|
||||||
|
};
|
||||||
|
|
||||||
|
class cListGroups: public cChannelIterator
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
virtual const cChannel* NextChannel(const cChannel *Channel);
|
||||||
|
public:
|
||||||
|
cListGroups();
|
||||||
|
virtual ~cListGroups() {};
|
||||||
|
};
|
||||||
|
|
||||||
|
class cListGroup: public cChannelIterator
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
virtual const cChannel* NextChannel(const cChannel *Channel);
|
||||||
|
public:
|
||||||
|
cListGroup(const cChannel *Group);
|
||||||
|
virtual ~cListGroup() {};
|
||||||
|
};
|
||||||
|
|
||||||
|
class cListTree: public cChannelIterator
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
const cChannel* selectedGroup;
|
||||||
|
const cChannel* currentGroup;
|
||||||
|
protected:
|
||||||
|
virtual const cChannel* NextChannel(const cChannel *Channel);
|
||||||
|
public:
|
||||||
|
cListTree(const cChannel *SelectedGroup);
|
||||||
|
virtual ~cListTree() {};
|
||||||
|
};
|
||||||
|
|
||||||
|
// ******************** cChannelList ******************
|
||||||
|
class cChannelList
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
cChannelIterator *iterator;
|
||||||
|
protected:
|
||||||
|
const cChannel* NextChannel() { return iterator->Next(); }
|
||||||
|
public:
|
||||||
|
// Helper which returns the group index
|
||||||
|
static int GetGroupIndex(const cChannel* Group);
|
||||||
|
// Helper which returns the group by its index
|
||||||
|
static const cChannel* GetGroup(int Index);
|
||||||
|
|
||||||
|
virtual std::string HttpHeader() { return "HTTP/1.0 200 OK\r\n"; };
|
||||||
|
virtual bool HasNext() = 0;
|
||||||
|
virtual std::string Next() = 0;
|
||||||
|
cChannelList(cChannelIterator *Iterator);
|
||||||
|
virtual ~cChannelList();
|
||||||
|
};
|
||||||
|
|
||||||
|
class cHtmlChannelList: public cChannelList
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static const char* menu;
|
||||||
|
static const char* css;
|
||||||
|
static const char* js;
|
||||||
|
|
||||||
|
enum eHtmlState {
|
||||||
|
hsRoot, hsHtmlHead, hsCss, hsJs, hsPageTop, hsPageBottom,
|
||||||
|
hsGroupTop, hsGroupBottom,
|
||||||
|
hsPlainTop, hsPlainItem, hsPlainBottom,
|
||||||
|
hsItemsTop, hsItem, hsItemsBottom
|
||||||
|
};
|
||||||
|
eHtmlState htmlState;
|
||||||
|
const cChannel *current;
|
||||||
|
eStreamType streamType;
|
||||||
|
const char* self;
|
||||||
|
const char* groupTarget;
|
||||||
|
|
||||||
|
std::string StreamTypeMenu();
|
||||||
|
std::string HtmlHead();
|
||||||
|
std::string PageTop();
|
||||||
|
std::string GroupTitle();
|
||||||
|
std::string ItemText();
|
||||||
|
std::string PageBottom();
|
||||||
|
public:
|
||||||
|
virtual std::string HttpHeader() { return cChannelList::HttpHeader() + "Content-type: text/html\r\n\r\n"; }
|
||||||
|
virtual bool HasNext();
|
||||||
|
virtual std::string Next();
|
||||||
|
cHtmlChannelList(cChannelIterator *Iterator, eStreamType StreamType, const char *Self, const char *GroupTarget);
|
||||||
|
virtual ~cHtmlChannelList();
|
||||||
|
};
|
||||||
|
|
||||||
|
class cM3uChannelList: public cChannelList
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
char *base;
|
||||||
|
enum eM3uState { msFirst, msContinue, msLast };
|
||||||
|
eM3uState m3uState;
|
||||||
|
#if defined(APIVERSNUM) && APIVERSNUM >= 10503
|
||||||
|
cCharSetConv m_IConv;
|
||||||
|
#endif
|
||||||
|
public:
|
||||||
|
virtual std::string HttpHeader() { return cChannelList::HttpHeader() + "Content-type: audio/x-mpegurl\r\n"; };
|
||||||
|
virtual bool HasNext();
|
||||||
|
virtual std::string Next();
|
||||||
|
cM3uChannelList(cChannelIterator *Iterator, const char* Base);
|
||||||
|
virtual ~cM3uChannelList();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -16,7 +16,7 @@ cTBSelect::~cTBSelect() {
|
|||||||
|
|
||||||
int cTBSelect::Select(uint TimeoutMs) {
|
int cTBSelect::Select(uint TimeoutMs) {
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
ssize_t res;
|
ssize_t res = 0;
|
||||||
int ms;
|
int ms;
|
||||||
|
|
||||||
tv.tv_usec = (TimeoutMs % 1000) * 1000;
|
tv.tv_usec = (TimeoutMs % 1000) * 1000;
|
||||||
|
@ -153,5 +153,5 @@ bool cTBSocket::Shutdown(int how) {
|
|||||||
|
|
||||||
bool cTBSocket::SetDSCP(void) {
|
bool cTBSocket::SetDSCP(void) {
|
||||||
int dscp = STREAMDEV_DSCP;
|
int dscp = STREAMDEV_DSCP;
|
||||||
return ::setsockopt(*this, SOL_IP, IP_TOS, &dscp, sizeof(dscp)) != -1;
|
return ::setsockopt(*this, IPPROTO_IP, IP_TOS, &dscp, sizeof(dscp)) != -1;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user