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
|
||||
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
|
||||
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)
|
||||
for providing italian language texts
|
||||
@ -13,9 +16,57 @@ Michal
|
||||
|
||||
Rolf Ahrenberg
|
||||
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
|
||||
for providing vdr-incompletesections.diff
|
||||
|
||||
Thomas Keil
|
||||
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
|
||||
---------------------------------------
|
||||
|
||||
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
|
||||
|
||||
- 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
|
||||
#
|
||||
# $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.
|
||||
# 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/componentHTTP.o server/componentVTP.o server/connection.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
|
||||
|
||||
@ -85,8 +85,10 @@ ifeq ($(shell test -f $(VDRDIR)/sections.c ; echo $$?),0)
|
||||
DEFINES += -DHAVE_AUTOPID
|
||||
endif
|
||||
|
||||
libdvbmpeg/libdvbmpegtools.a: libdvbmpeg/*.c libdvbmpeg/*.cc libdvbmpeg/*.h libdvbmpeg/*.hh
|
||||
make -C ./libdvbmpeg libdvbmpegtools.a
|
||||
### The main target:
|
||||
|
||||
.PHONY: all dist clean
|
||||
all: libvdr-$(PLUGIN)-client.so libvdr-$(PLUGIN)-server.so
|
||||
|
||||
### Implicit rules:
|
||||
|
||||
@ -113,7 +115,9 @@ endif
|
||||
|
||||
### 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)-server.so: $(SERVEROBJS) $(COMMONOBJS) libdvbmpeg/libdvbmpegtools.a
|
||||
@ -126,10 +130,10 @@ dist: clean
|
||||
@-rm -rf $(TMPDIR)/$(ARCHIVE)
|
||||
@mkdir $(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)
|
||||
@echo Distribution package created as $(PACKAGE).tgz
|
||||
|
||||
clean:
|
||||
@-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).
|
||||
|
||||
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.
|
||||
|
||||
@ -14,16 +16,12 @@ Contents:
|
||||
1. Description
|
||||
2. Installation
|
||||
2.1 VDR 1.2.X
|
||||
2.2 VDR 1.3.X
|
||||
2.2 VDR 1.3.X and above
|
||||
3. Usage
|
||||
3.1 Usage VDR-to-VDR server
|
||||
3.2 Usage HTTP server
|
||||
3.1 Usage HTTP server
|
||||
3.2 Usage VDR-to-VDR server
|
||||
3.3 Usage VDR-to-VDR client
|
||||
3.4 General Usage Notes
|
||||
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]
|
||||
4. Other useful Plugins
|
||||
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] plugins
|
||||
|
||||
2.2 VDR 1.3.X:
|
||||
--------------
|
||||
2.2 VDR 1.3.X and above:
|
||||
------------------------
|
||||
|
||||
cd vdr-1.X.X/PLUGINS/src
|
||||
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
|
||||
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
|
||||
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
|
||||
cards are in use (i.e. when something is recorded, or by multiple clients),
|
||||
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
|
||||
@ -164,8 +152,17 @@ TS Transport Stream (i.e. a dump from the device)
|
||||
PES Packetized Elemetary Stream (VDR's native recording format)
|
||||
PS Program Stream (SVCD, DVD like stream)
|
||||
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/S19.2E-0-12480-898
|
||||
@ -178,12 +175,26 @@ http://hostname:3000/TS/3
|
||||
http://hostname:3000/PES/S19.2E-0-12480-898
|
||||
|
||||
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
|
||||
format explicitly if you want to listen to radio channels. Play them pack i.e.
|
||||
with mpg123.
|
||||
Possible values are 'PES', 'TS', 'PS', 'ES' and 'EXTERN'. You need to specify
|
||||
the ES format explicitly if you want to listen to radio channels. Play them
|
||||
back i.e. with mpg123.
|
||||
|
||||
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:
|
||||
----------------------------
|
||||
|
||||
@ -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
|
||||
transponder.
|
||||
|
||||
You can choose a remote streamtype in the setup. I'd suggest TS streaming as
|
||||
it has a much shorter delay than PES streaming (compared to live-view of the
|
||||
same channel on the server), and transmits more information such as AC3 and
|
||||
teletext data.
|
||||
Only the needed PIDs are transferred, and additional PIDs can be turned on
|
||||
during an active transfer. This makes it possible to switch languages, receive
|
||||
additional channels (for recording on the client) and use plugins that use
|
||||
receivers themselves (like osdteletext).
|
||||
|
||||
When setting the parameter "MultiPID streaming" to yes (the default) (only
|
||||
applies if the streamtype is TS), only the needed PIDs are transferred, and
|
||||
additional PIDs can be turned on during an active transfer. This makes it
|
||||
possible to switch languages, receive additional channels (for recording on
|
||||
the client) and use plugins that use receivers themselves (like osdteletext).
|
||||
With "Filter Streaming" enabled, the client will receive meta information like
|
||||
EPG data and service information, just as if the client had its own DVB card.
|
||||
Link channels and even a client-side EPG scan have been reported to work.
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
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:
|
||||
4. Other useful Plugins:
|
||||
------------------------
|
||||
|
||||
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.
|
||||
I never tried, and probably I never will, so don't ask about it please ;)
|
||||
* femon (http://www.saunalahti.fi/~rahrenbe/vdr/femon/)
|
||||
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
|
||||
mode.
|
||||
4.2 Plugins for Server:
|
||||
-----------------------
|
||||
|
||||
* 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
|
||||
------------
|
||||
|
||||
Works with ivtv and analogue cards according to Andreas Kool.
|
||||
4.3 Alternatives:
|
||||
-----------------
|
||||
|
||||
* 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:
|
||||
------------------
|
||||
|
||||
- Recordings & Timers on the client side could endanger Timers & Recordings on
|
||||
the server, as they will have the same priority (by default). Set the
|
||||
default priority to i.e. 40 if you want the server to supersede the client.
|
||||
|
||||
- Sometimes, if you reload VDR too often (for example while recompiling), the
|
||||
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 :-(.
|
||||
|
||||
|
||||
* In VDR-to-VDR setup, the availability of a channel is checked with a different
|
||||
priority than the actual channel switch. The later always uses priority 0.
|
||||
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
|
||||
client side recordings or set the priority of client side timers to 0.
|
||||
|
@ -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"
|
||||
@ -212,8 +212,16 @@ void cStreamdevDevice::CloseDvrInt(void) {
|
||||
}
|
||||
|
||||
Dprintf("cStreamdevDevice::CloseDvrInt(): Closing DVR connection\n");
|
||||
#if VDRVERSNUM < 10500
|
||||
DELETENULL(m_TSBuffer);
|
||||
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) {
|
||||
|
@ -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>
|
||||
@ -140,8 +140,14 @@ bool cClientSocket::CheckConnection(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
isyslog("Streamdev: Connected to server %s:%d using capabilities TSPIDS",
|
||||
RemoteIp().c_str(), RemotePort());
|
||||
const char *Filters = "";
|
||||
#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;
|
||||
}
|
||||
|
||||
|
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>
|
||||
@ -11,7 +11,7 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
const char *VERSION = "0.3.3-20070921";
|
||||
const char *VERSION = "0.3.4";
|
||||
|
||||
const char *StreamTypes[st_Count] = {
|
||||
"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
|
||||
#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/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);
|
||||
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
if ( (fdin = open(name, O_RDONLY)) < 0){
|
||||
#else
|
||||
if ( (fdin = open(name, O_RDONLY|O_LARGEFILE)) < 0){
|
||||
#endif
|
||||
fprintf(stderr,"Can't open %s\n",name);
|
||||
exit(1);
|
||||
}
|
||||
@ -2101,8 +2105,12 @@ void split_mpg(char *name, uint64_t size)
|
||||
sprintf(new_name,"%s-%03d.%s",base_name,i,ext);
|
||||
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
|
||||
|O_LARGEFILE,
|
||||
#endif
|
||||
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|
|
||||
S_IROTH|S_IWOTH)) < 0){
|
||||
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);
|
||||
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
|
||||
|O_LARGEFILE,
|
||||
#endif
|
||||
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|
|
||||
S_IROTH|S_IWOTH)) < 0){
|
||||
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);
|
||||
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
if ( (fdin = open(name, O_RDONLY)) < 0){
|
||||
#else
|
||||
if ( (fdin = open(name, O_RDONLY|O_LARGEFILE)) < 0){
|
||||
#endif
|
||||
fprintf(stderr,"Can't open %s\n",name);
|
||||
exit(1);
|
||||
}
|
||||
@ -2182,8 +2198,12 @@ void cut_mpg(char *name, uint64_t size)
|
||||
sprintf(new_name,"%s-1.%s",base_name,ext);
|
||||
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
|
||||
|O_LARGEFILE,
|
||||
#endif
|
||||
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|
|
||||
S_IROTH|S_IWOTH)) < 0){
|
||||
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);
|
||||
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
|
||||
|O_LARGEFILE,
|
||||
#endif
|
||||
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|
|
||||
S_IROTH|S_IWOTH)) < 0){
|
||||
fprintf(stderr,"Can't open %s\n",new_name);
|
||||
|
@ -19,13 +19,13 @@ protected:
|
||||
virtual void Action(void);
|
||||
|
||||
public:
|
||||
cTSExt(cRingBufferLinear *ResultBuffer);
|
||||
cTSExt(cRingBufferLinear *ResultBuffer, std::string Parameter);
|
||||
virtual ~cTSExt();
|
||||
|
||||
void Put(const uchar *Data, int Count);
|
||||
};
|
||||
|
||||
cTSExt::cTSExt(cRingBufferLinear *ResultBuffer):
|
||||
cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, std::string Parameter):
|
||||
m_ResultBuffer(ResultBuffer),
|
||||
m_Active(false),
|
||||
m_Process(0),
|
||||
@ -67,9 +67,8 @@ cTSExt::cTSExt(cRingBufferLinear *ResultBuffer):
|
||||
for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++)
|
||||
close(i); //close all dup'ed filedescriptors
|
||||
|
||||
//printf("starting externremux.sh\n");
|
||||
execl("/bin/sh", "sh", "-c", g_ExternRemux, NULL);
|
||||
//printf("failed externremux.sh\n");
|
||||
std::string cmd = std::string(g_ExternRemux) + " " + Parameter;
|
||||
execl("/bin/sh", "sh", "-c", cmd.c_str(), NULL);
|
||||
_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_Remux(new cTSExt(m_ResultBuffer))
|
||||
m_Remux(new cTSExt(m_ResultBuffer, Parameter))
|
||||
{
|
||||
m_ResultBuffer->SetTimeouts(500, 100);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include "remux/tsremux.h"
|
||||
#include <vdr/ringbuffer.h>
|
||||
#include <string>
|
||||
|
||||
extern const char *g_ExternRemux;
|
||||
|
||||
@ -14,7 +15,7 @@ private:
|
||||
cTSExt *m_Remux;
|
||||
|
||||
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();
|
||||
|
||||
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 "server/connectionHTTP.h"
|
||||
#include "server/menuHTTP.h"
|
||||
#include "server/setup.h"
|
||||
|
||||
cConnectionHTTP::cConnectionHTTP(void):
|
||||
cServerConnection("HTTP"),
|
||||
m_Status(hsRequest),
|
||||
m_LiveStreamer(NULL),
|
||||
m_StreamerParameter(""),
|
||||
m_Channel(NULL),
|
||||
m_Apid(0),
|
||||
m_StreamType((eStreamType)StreamdevServerSetup.HTTPStreamType),
|
||||
m_ListChannel(NULL)
|
||||
m_ChannelList(NULL)
|
||||
{
|
||||
Dprintf("constructor hsRequest\n");
|
||||
}
|
||||
@ -39,6 +41,10 @@ bool cConnectionHTTP::Command(char *Cmd)
|
||||
m_Status = hsBody;
|
||||
return ProcessRequest();
|
||||
}
|
||||
if (strncasecmp(Cmd, "Host:", 5) == 0) {
|
||||
Dprintf("Host-Header\n");
|
||||
m_Host = (std::string) skipspace(Cmd + 5);
|
||||
}
|
||||
Dprintf("header\n");
|
||||
return true;
|
||||
default:
|
||||
@ -53,11 +59,9 @@ bool cConnectionHTTP::ProcessRequest(void)
|
||||
if (m_Request.substr(0, 4) == "GET " && CmdGET(m_Request.substr(4))) {
|
||||
switch (m_Job) {
|
||||
case hjListing:
|
||||
return Respond("HTTP/1.0 200 OK")
|
||||
&& Respond("Content-Type: text/html")
|
||||
&& Respond("")
|
||||
&& Respond("<html><head><title>VDR Channel Listing</title></head>")
|
||||
&& Respond("<body><ul>");
|
||||
if (m_ChannelList)
|
||||
return Respond("%s", true, m_ChannelList->HttpHeader().c_str());
|
||||
break;
|
||||
|
||||
case hjTransfer:
|
||||
if (m_Channel == NULL) {
|
||||
@ -65,7 +69,7 @@ bool cConnectionHTTP::ProcessRequest(void)
|
||||
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);
|
||||
if (device != NULL) {
|
||||
device->SwitchChannel(m_Channel, false);
|
||||
@ -106,43 +110,21 @@ void cConnectionHTTP::Flushed(void)
|
||||
|
||||
switch (m_Job) {
|
||||
case hjListing:
|
||||
if (m_ListChannel == NULL) {
|
||||
Respond("</ul></body></html>");
|
||||
if (m_ChannelList) {
|
||||
if (m_ChannelList->HasNext()) {
|
||||
if (!Respond("%s", true, m_ChannelList->Next().c_str()))
|
||||
DeferClose();
|
||||
}
|
||||
else {
|
||||
DELETENULL(m_ChannelList);
|
||||
m_Status = hsFinished;
|
||||
DeferClose();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_ListChannel->GroupSep())
|
||||
line = (std::string)"<li>--- " + m_ListChannel->Name() + "---</li>";
|
||||
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);
|
||||
// should never be reached
|
||||
esyslog("streamdev-server cConnectionHTTP::Flushed(): no channel list");
|
||||
m_Status = hsFinished;
|
||||
break;
|
||||
|
||||
case hjTransfer:
|
||||
@ -155,49 +137,131 @@ void cConnectionHTTP::Flushed(void)
|
||||
|
||||
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;
|
||||
int apid = 0;
|
||||
|
||||
ptr = skipspace(ptr);
|
||||
while (*ptr == '/')
|
||||
++ptr;
|
||||
ptr = Opts.c_str();
|
||||
|
||||
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;
|
||||
ptr += 3;
|
||||
} else if (strncasecmp(ptr, "PES/", 4) == 0) {
|
||||
} else if (strcasecmp(pType, "PES") == 0) {
|
||||
m_StreamType = stPES;
|
||||
ptr += 4;
|
||||
} else if (strncasecmp(ptr, "TS/", 3) == 0) {
|
||||
} else if (strcasecmp(pType, "TS") == 0) {
|
||||
m_StreamType = stTS;
|
||||
ptr += 3;
|
||||
} else if (strncasecmp(ptr, "ES/", 3) == 0) {
|
||||
} else if (strcasecmp(pType, "ES") == 0) {
|
||||
m_StreamType = stES;
|
||||
ptr += 3;
|
||||
} else if (strncasecmp(ptr, "Extern/", 3) == 0) {
|
||||
} else if (strcasecmp(pType, "Extern") == 0) {
|
||||
m_StreamType = stExtern;
|
||||
ptr += 7;
|
||||
}
|
||||
|
||||
while (*ptr == '/')
|
||||
++ptr;
|
||||
for (ep = ptr + strlen(ptr); ep >= ptr && !isspace(*ep); --ep)
|
||||
;
|
||||
std::string groupTarget;
|
||||
cChannelIterator *iterator = NULL;
|
||||
|
||||
std::string filespec = Opts.substr(ptr - sp, ep - ptr);
|
||||
Dprintf("substr: %s\n", filespec.c_str());
|
||||
if (filespec.compare("tree") == 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 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 (filespec == "" || filespec.substr(0, 12) == "channels.htm") {
|
||||
m_ListChannel = Channels.First();
|
||||
if (iterator) {
|
||||
if (filespec.empty() || fileext.compare(".htm") == 0 || fileext.compare(".html") == 0) {
|
||||
m_ChannelList = new cHtmlChannelList(iterator, m_StreamType, (filespec + fileext + query).c_str(), groupTarget.c_str());
|
||||
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) {
|
||||
m_Channel = chan;
|
||||
m_Apid = apid;
|
||||
Dprintf("Apid is %d\n", apid);
|
||||
m_Job = hjTransfer;
|
||||
}
|
||||
} else
|
||||
return false;
|
||||
|
||||
Dprintf("after channelfromstring\n");
|
||||
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
|
||||
@ -12,6 +12,7 @@
|
||||
|
||||
class cChannel;
|
||||
class cStreamdevLiveStreamer;
|
||||
class cChannelList;
|
||||
|
||||
class cConnectionHTTP: public cServerConnection {
|
||||
private:
|
||||
@ -28,16 +29,18 @@ private:
|
||||
};
|
||||
|
||||
std::string m_Request;
|
||||
std::string m_Host;
|
||||
//std::map<std::string,std::string> m_Headers; TODO: later?
|
||||
eHTTPStatus m_Status;
|
||||
eHTTPJob m_Job;
|
||||
// job: transfer
|
||||
cStreamdevLiveStreamer *m_LiveStreamer;
|
||||
std::string m_StreamerParameter;
|
||||
const cChannel *m_Channel;
|
||||
int m_Apid;
|
||||
eStreamType m_StreamType;
|
||||
// job: listing
|
||||
const cChannel *m_ListChannel;
|
||||
cChannelList *m_ChannelList;
|
||||
|
||||
protected:
|
||||
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"
|
||||
@ -186,7 +186,11 @@ bool cLSTEHandler::Next(bool &Last)
|
||||
case Event:
|
||||
if (m_Event != NULL) {
|
||||
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(),
|
||||
#endif
|
||||
m_Event->StartTime(), m_Event->Duration(),
|
||||
m_Event->TableID());
|
||||
} else {
|
||||
@ -225,7 +229,11 @@ bool cLSTEHandler::Next(bool &Last)
|
||||
case Vps:
|
||||
m_State = EndEvent;
|
||||
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());
|
||||
#endif
|
||||
else
|
||||
return Next(Last);
|
||||
break;
|
||||
@ -470,6 +478,7 @@ cConnectionVTP::cConnectionVTP(void):
|
||||
m_FilterStreamer(NULL),
|
||||
m_LastCommand(NULL),
|
||||
m_StreamType(stTSPIDS),
|
||||
m_FiltersSupport(false),
|
||||
m_LSTEHandler(NULL),
|
||||
m_LSTCHandler(NULL),
|
||||
m_LSTTHandler(NULL)
|
||||
@ -600,8 +609,10 @@ bool cConnectionVTP::CmdCAPS(char *Opts)
|
||||
//
|
||||
// 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);
|
||||
}
|
||||
#endif
|
||||
|
||||
return Respond(561, "Capability \"%s\" not known", Opts);
|
||||
@ -672,6 +683,7 @@ bool cConnectionVTP::CmdPORT(char *Opts)
|
||||
|
||||
#if VDRVERSNUM >= 10300
|
||||
if (id == siLiveFilter) {
|
||||
m_FiltersSupport = true;
|
||||
if(m_FilterStreamer)
|
||||
m_FilterStreamer->Stop();
|
||||
delete m_FilterSocket;
|
||||
@ -735,10 +747,12 @@ bool cConnectionVTP::CmdTUNE(char *Opts)
|
||||
m_LiveStreamer->Start(m_LiveSocket);
|
||||
|
||||
#if VDRVERSNUM >= 10300
|
||||
if(m_FiltersSupport) {
|
||||
if(!m_FilterStreamer)
|
||||
m_FilterStreamer = new cStreamdevFilterStreamer;
|
||||
m_FilterStreamer->SetDevice(dev);
|
||||
//m_FilterStreamer->SetChannel(chan);
|
||||
}
|
||||
#endif
|
||||
|
||||
return Respond(220, "Channel tuned");
|
||||
|
@ -24,6 +24,7 @@ private:
|
||||
|
||||
char *m_LastCommand;
|
||||
eStreamType m_StreamType;
|
||||
bool m_FiltersSupport;
|
||||
|
||||
// Members adopted for SVDRP
|
||||
cRecordings Recordings;
|
||||
|
@ -12,6 +12,8 @@
|
||||
#include "remux/extern.h"
|
||||
#include "common.h"
|
||||
|
||||
#define TSPATREPACKER
|
||||
|
||||
// --- cStreamdevLiveReceiver -------------------------------------------------
|
||||
|
||||
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();
|
||||
if (Length < TS_SIZE-5) {
|
||||
// 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 */};
|
||||
memcpy(pat_ts + 5, Data, Length);
|
||||
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
|
||||
isyslog("cStreamdevPatFilter: PAT size %d too large to fit in one TS", Length);
|
||||
m_Streamer->SetPids(pmtPid);
|
||||
@ -268,9 +309,9 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
|
||||
#if 0
|
||||
pids[npids++] = 0x10; // pid 0x10, tid 0x40: NIT
|
||||
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
|
||||
#endif
|
||||
pids[npids++] = 0x12; // pid 0x12, tid 0x4E...0x6F: EIT
|
||||
for (SI::Loop::Iterator it; pmt.streamLoop.getNext(stream, it); )
|
||||
if (0 != (pids[npids] = GetPid(stream)) && npids < MAXRECEIVEPIDS)
|
||||
npids++;
|
||||
@ -282,9 +323,10 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
|
||||
|
||||
// --- cStreamdevLiveStreamer -------------------------------------------------
|
||||
|
||||
cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority):
|
||||
cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority, std::string Parameter):
|
||||
cStreamdevStreamer("streamdev-livestreaming"),
|
||||
m_Priority(Priority),
|
||||
m_Parameter(Parameter),
|
||||
m_NumPids(0),
|
||||
m_StreamType(stTSPIDS),
|
||||
m_Channel(NULL),
|
||||
@ -447,7 +489,7 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str
|
||||
|
||||
case stExtern:
|
||||
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());
|
||||
|
||||
case stTSPIDS:
|
||||
|
@ -19,6 +19,7 @@ class cStreamdevLiveReceiver;
|
||||
class cStreamdevLiveStreamer: public cStreamdevStreamer {
|
||||
private:
|
||||
int m_Priority;
|
||||
std::string m_Parameter;
|
||||
int m_Pids[MAXRECEIVEPIDS + 1];
|
||||
int m_NumPids;
|
||||
eStreamType m_StreamType;
|
||||
@ -35,7 +36,7 @@ private:
|
||||
bool HasPid(int Pid);
|
||||
|
||||
public:
|
||||
cStreamdevLiveStreamer(int Priority);
|
||||
cStreamdevLiveStreamer(int Priority, std::string Parameter = "");
|
||||
virtual ~cStreamdevLiveStreamer();
|
||||
|
||||
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) {
|
||||
struct timeval tv;
|
||||
ssize_t res;
|
||||
ssize_t res = 0;
|
||||
int ms;
|
||||
|
||||
tv.tv_usec = (TimeoutMs % 1000) * 1000;
|
||||
|
@ -153,5 +153,5 @@ bool cTBSocket::Shutdown(int how) {
|
||||
|
||||
bool cTBSocket::SetDSCP(void) {
|
||||
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