Compare commits
223 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2a9b979d3 | ||
|
|
644078220b | ||
|
|
95256a52cf | ||
|
|
b84b7d858c | ||
|
|
674bb5b331 | ||
|
|
d66c635a80 | ||
|
|
fc52e920ad | ||
|
|
84c6f6b6f3 | ||
|
|
3e06c59196 | ||
|
|
b33d2631df | ||
|
|
657c8bc49c | ||
|
|
5f5fb7953e | ||
|
|
7b17f7725c | ||
|
|
1ee2049c4d | ||
|
|
99b223c55f | ||
|
|
7df7185e1a | ||
|
|
dd556ee7fd | ||
|
|
58f0348578 | ||
|
|
e83c9d92aa | ||
|
|
520adaf3da | ||
|
|
71c26e7455 | ||
|
|
e8629b5ec6 | ||
|
|
2d919997a8 | ||
|
|
703dffa0cb | ||
|
|
bdee8c1923 | ||
|
|
83262870d5 | ||
|
|
888cf0a2f8 | ||
|
|
1dc1423429 | ||
|
|
5a173b0b21 | ||
|
|
3e9e7f7de6 | ||
|
|
62fa951c61 | ||
|
|
bfbf19decc | ||
|
|
e555017565 | ||
|
|
7be0c81a81 | ||
|
|
2cdf160648 | ||
|
|
54440cb080 | ||
|
|
40704cdcbc | ||
|
|
5fcd6eca69 | ||
|
|
a4a774c6ce | ||
|
|
c18f7d47e7 | ||
|
|
1439b016b3 | ||
|
|
f194ca2074 | ||
|
|
1d4a7e06b4 | ||
|
|
458a21a62a | ||
|
|
69b654d539 | ||
|
|
5e5070edc0 | ||
|
|
dfc8339c9e | ||
|
|
a9c2adb565 | ||
|
|
8c5859ed4a | ||
|
|
e1ba17ca21 | ||
|
|
d3df5d07a1 | ||
|
|
c92de13d06 | ||
|
|
d7652d89ca | ||
|
|
b25e53c867 | ||
|
|
329129d9c1 | ||
|
|
75ee90a974 | ||
|
|
08198729ac | ||
|
|
10db11acd9 | ||
|
|
f58086a83a | ||
|
|
d3dd72072c | ||
|
|
9bbb74b7fd | ||
|
|
176df8341d | ||
|
|
138580f284 | ||
|
|
525edc1ccf | ||
|
|
9135cde712 | ||
|
|
0cf406ed3a | ||
|
|
1866716471 | ||
|
|
50d249c62e | ||
|
|
0fb7076192 | ||
|
|
f5da0ea1fc | ||
|
|
0677f48329 | ||
|
|
eaf9321c4c | ||
|
|
83e9f3250f | ||
|
|
c267b585fd | ||
|
|
be9da74958 | ||
|
|
e7bcc9349c | ||
|
|
b614fa0ec3 | ||
|
|
84db6323a6 | ||
|
|
281105f0c7 | ||
|
|
80e40d4260 | ||
|
|
5cfa16c402 | ||
|
|
af48d11b18 | ||
|
|
744dc6792c | ||
|
|
16f8c75918 | ||
|
|
fffd5aef4f | ||
|
|
6389c5fd90 | ||
|
|
6a47e20435 | ||
|
|
12b48591be | ||
|
|
ab26b4770a | ||
|
|
00b7318a7b | ||
|
|
c3ac597623 | ||
|
|
ae634538f8 | ||
|
|
783b261bcb | ||
|
|
316ac3344d | ||
|
|
8719007f5a | ||
|
|
2e8aefd2fe | ||
|
|
a1797719de | ||
|
|
5a3c535778 | ||
|
|
173d2cbb7a | ||
|
|
83b05a6292 | ||
|
|
a63f7247cb | ||
|
|
26af4459d8 | ||
|
|
3da6ae734e | ||
|
|
c1dc1453c5 | ||
|
|
a047fc7d32 | ||
|
|
229e8fbfff | ||
|
|
ba7c61fb39 | ||
|
|
6a971b9145 | ||
|
|
3440072e7e | ||
|
|
59c6558ce3 | ||
|
|
d93ca82bd1 | ||
|
|
ffb8707118 | ||
|
|
afe255aa0b | ||
|
|
ce1583a756 | ||
|
|
20c931ac6d | ||
|
|
f9224d0618 | ||
|
|
30674fb6c8 | ||
|
|
8850e63da5 | ||
|
|
6c9c6ca77f | ||
|
|
9cb72968f6 | ||
|
|
b9d2f66295 | ||
|
|
e0e14bb322 | ||
|
|
077398fdef | ||
|
|
bdaea38b86 | ||
|
|
6b633dbfa2 | ||
|
|
615f101b9d | ||
|
|
2fae067cfe | ||
|
|
475e7bbd6a | ||
|
|
4c0888d483 | ||
|
|
24a1638ddd | ||
|
|
cc74cba396 | ||
|
|
2c8377d42a | ||
|
|
673fbae2e2 | ||
|
|
f73665a0bd | ||
|
|
f6299b4584 | ||
|
|
09e0a0176b | ||
|
|
19e8cf2a28 | ||
|
|
17bb6390f9 | ||
|
|
58a4f3dbe5 | ||
|
|
1a933d6cc9 | ||
|
|
c06970123c | ||
|
|
2cb0ba6cf1 | ||
|
|
ee7929bd39 | ||
|
|
dc41eecffd | ||
|
|
30930d5c74 | ||
|
|
3a2936a140 | ||
|
|
06d5418e42 | ||
|
|
0783799826 | ||
|
|
316a1f884f | ||
|
|
9fbc166848 | ||
|
|
abf8a62fe9 | ||
|
|
a35675490d | ||
|
|
0c07109974 | ||
|
|
6ea5efe939 | ||
|
|
0a860a1e3e | ||
|
|
c7d19619db | ||
|
|
c0a7f1610a | ||
|
|
edc3ad1c8e | ||
|
|
ebacf759ab | ||
|
|
1a1141e4a1 | ||
|
|
7517b874d8 | ||
|
|
6f984b87e3 | ||
|
|
8a128f7c57 | ||
|
|
2f027a6ffc | ||
|
|
dd7cc838ad | ||
|
|
93c9aa9af0 | ||
|
|
c882a991cc | ||
|
|
b44c9ac3f5 | ||
|
|
b3c72deaed | ||
|
|
d794d83854 | ||
|
|
b5bd22e7bd | ||
|
|
e99ea00348 | ||
|
|
73e30fd5ca | ||
|
|
ac40edfd24 | ||
|
|
6da7cf90d9 | ||
|
|
1cf41fb16f | ||
|
|
6fb88f8d99 | ||
|
|
0e9e22c23d | ||
|
|
1474cdf799 | ||
|
|
4ab256caea | ||
|
|
4f3a6aae88 | ||
|
|
d5fa0a49f8 | ||
|
|
8469a4d894 | ||
|
|
41763e7710 | ||
|
|
c8cf217c01 | ||
|
|
5644d3c03f | ||
|
|
fe77c5dccb | ||
|
|
dd09bfbb45 | ||
|
|
460d5f0689 | ||
|
|
052a94db5a | ||
|
|
7b8e396f77 | ||
|
|
64bf3e5ecf | ||
|
|
44a71ffe8a | ||
|
|
008e7c8510 | ||
|
|
64ff2c08be | ||
|
|
3d16ba0840 | ||
|
|
78410ea576 | ||
|
|
c26b89f9c2 | ||
|
|
37f2adf02b | ||
|
|
479d5c4b48 | ||
|
|
0e0b4b587d | ||
|
|
9af6ceb007 | ||
|
|
41cf7a5848 | ||
|
|
30aa3b0610 | ||
|
|
ca043780a7 | ||
|
|
cd8d7fbd6c | ||
|
|
459e41e810 | ||
|
|
7184adef83 | ||
|
|
ac2e992305 | ||
|
|
c364a3396d | ||
|
|
4a5af4f489 | ||
|
|
86c82c1381 | ||
|
|
992444cb67 | ||
|
|
c5b5a4cbeb | ||
|
|
42474b8431 | ||
|
|
924cf6e4d0 | ||
|
|
90bae5b011 | ||
|
|
9ecbab8882 | ||
|
|
685f43c6ec | ||
|
|
b2bea54b34 | ||
|
|
ecbde41090 | ||
|
|
dc796dfe74 | ||
|
|
34a62eb2ef |
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
|
||||
# Eclipse project files
|
||||
.project
|
||||
.cproject
|
||||
.settings
|
||||
|
||||
# Generated dependency file
|
||||
.dependencies
|
||||
|
||||
# translations
|
||||
*.mo
|
||||
*.pot
|
||||
189
CONTRIBUTORS
189
CONTRIBUTORS
@@ -1,6 +1,10 @@
|
||||
Special thanks go to the following persons (if you think your name is missing
|
||||
here, please send an email to vdrdev@schmirler.de):
|
||||
|
||||
Klaus Schmidinger
|
||||
for VDR as a whole
|
||||
for permission to use VDR 1.6.0 cRemux code for PES remuxing
|
||||
|
||||
Sascha Volkenandt, the original author,
|
||||
for this great plugin
|
||||
|
||||
@@ -8,14 +12,14 @@ The Metzler Brothers
|
||||
as a lot of code has been taken from their libdvbmpeg package
|
||||
|
||||
Angelus (DOm)
|
||||
for providing italian language texts
|
||||
for providing Italian language texts
|
||||
for reporting problems with the Elchi-Patch
|
||||
|
||||
Michal
|
||||
for sending a patch to select the HTTP streamtype via remote
|
||||
|
||||
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
|
||||
@@ -25,6 +29,19 @@ Rolf Ahrenberg
|
||||
for a TS PAT repacker based on Petri Laine's VDR TS recording patch
|
||||
for making it possible to pass parameters to externremux.sh
|
||||
for removing pre VDR 1.4 legacy code
|
||||
for adding gettext support
|
||||
for fixing output format of some debug messages
|
||||
for replacing private members by cThread::Running()/Active()
|
||||
for improving externremux script termination
|
||||
for fixing PAT repacker version field
|
||||
for improving LIMIKUUTIO and PARENTALRATING patch detection
|
||||
for suggesting to include the charset in HTTP replies
|
||||
for requesting replacement of asprintf calls
|
||||
for suggesting to change the URL path from EXTERN to EXT
|
||||
for suggesting increased thread priorities for cStreamdevWriter/Streamer
|
||||
for adding "Hide mainmenu entry" option
|
||||
for polishing po file headers
|
||||
for adding the special meaning "show current channel" to channel 0
|
||||
|
||||
Rantanen Teemu
|
||||
for providing vdr-incompletesections.diff
|
||||
@@ -43,6 +60,8 @@ Udo Richter
|
||||
for fixing streamdev-server shutdown
|
||||
for speeding up cPluginStreamdevServer::Active()
|
||||
for adapting to VDR 1.5.0 API
|
||||
for adapting to VDR 1.7.1
|
||||
for proper tsplay-0.2 patch detection
|
||||
|
||||
greenman
|
||||
for reporting that the log could get flooded on connection failures.
|
||||
@@ -68,9 +87,175 @@ alexw
|
||||
|
||||
Olli Lammi
|
||||
for fixing a busy wait when client isn't accepting data fast enough
|
||||
for suggesting signaling instead of sleeping when writing to buffers
|
||||
|
||||
Joerg Pulz
|
||||
for his FreeBSD compatibility patch
|
||||
|
||||
tobi
|
||||
for pointing to unused files in the libdvbmpeg directory
|
||||
|
||||
Diego Pierotto
|
||||
for providing Italian language texts
|
||||
|
||||
micky979
|
||||
for providing French language texts
|
||||
|
||||
Tiroler
|
||||
for reporting a problem when switching between encrypted channels
|
||||
|
||||
Pixelpeter
|
||||
for an initial fix to the "switching between ecncrypted channels" problem
|
||||
|
||||
Anssi Hannula
|
||||
for the vdr-1.6.0-intcamdevices patch
|
||||
for fixing insecure format strings in LSTX handlers
|
||||
|
||||
wirbel
|
||||
for pointing out that section filtering is optional for VDR devices
|
||||
for reporting a problem with Makefile defines in VDR 1.7.4+
|
||||
|
||||
Jori Hamalainen
|
||||
for extensive testing while making stream compatible to Network Media Tank
|
||||
for adding Network Media Tank browser support to HTML pages
|
||||
|
||||
Oliver Wagner
|
||||
for pointing out a problem with the encrypted channel switching fix
|
||||
for suggesting use of SO_KEEPALIVE socket option to detect dead sockets
|
||||
for making cStatus::ChannelChange re-tune only if CA IDs changed
|
||||
|
||||
Joachim König-Baltes
|
||||
for fixing Min/MaxPriority parsing
|
||||
|
||||
Artem Makhutov
|
||||
for suggesting and heavy testing IGMP based multicast streaming
|
||||
|
||||
Alwin Esch
|
||||
for adding XBMC support by extending VTP capabilities
|
||||
for adding VDR 1.7.11 parental rating support for VTP LSTE command
|
||||
for adding the DELT FORCE option to delete running timers
|
||||
|
||||
BBlack
|
||||
for reporting that updating recordings list on CmdPLAY is a bad idea
|
||||
|
||||
Milan Hrala
|
||||
for providing Slovak language texts
|
||||
|
||||
Valdemaras Pipiras
|
||||
for providing Lithuanian language texts
|
||||
|
||||
sk8ter
|
||||
for fixing failures when switching between two encrypted channels
|
||||
|
||||
lhanisch
|
||||
for fixing a memory leak in cStreamdevPatFilter::GetPid
|
||||
|
||||
Eric Valette
|
||||
for adding support for EnhancedAC3
|
||||
|
||||
carel
|
||||
for reporting "plugin doesn't honor APIVERSION" error in new Makefile
|
||||
for helping to find a way to cleanly shutdown externremux with mencoder
|
||||
for reporting that GetClippedNumProvidedSystems is no longer up-to-date
|
||||
|
||||
wolfi.m
|
||||
for reporting a typo in externremux quality parameter value
|
||||
|
||||
Norman Thiel
|
||||
for reporting a wrong URL path in m3u playlists
|
||||
|
||||
vel_tins
|
||||
for reporting that externremux x264 uses value of ABR for VBR
|
||||
for various suggestions to improve externremux.sh
|
||||
|
||||
Matthias Prill
|
||||
for reporting a compiler error with older libstdc++ versions
|
||||
|
||||
Timothy D. Lenz
|
||||
for reporting missing support for invisible channel groups in HTTP menu
|
||||
|
||||
Rainer Blickle
|
||||
for reporting that channel switches may interrupt live TV on the server
|
||||
|
||||
Gavin Hamill
|
||||
for reporting that ES/PS/PES no longer works
|
||||
|
||||
Michal Novotny
|
||||
for reporting that switching away live TV fails when "always suspended"
|
||||
|
||||
wtor
|
||||
for reporting that a client may interrupt replaying on FF cards
|
||||
for helping to debug channel switch issues on FF cards
|
||||
|
||||
Javier Bradineras
|
||||
for providing Spanish language texts
|
||||
|
||||
Pekko Tiitto
|
||||
for providing a git mirror of streamdev's lost CVS repository
|
||||
for suggesting to use mencoder params -alang and -msglevel in externremux
|
||||
|
||||
Lubo¨ Dole¸el
|
||||
for suggesting higher buffer sizes to fix some ringbuffer overflows
|
||||
|
||||
Ville Skyttä
|
||||
for updating the outdated COPYING file and FSF address
|
||||
for restricting VTP command RENR to liemikuutio patch < 1.32
|
||||
for fixing memory and filedescriptor leaks in libdvbmpeg
|
||||
for code cleanup and optimization
|
||||
for correcting typos
|
||||
|
||||
Methodus
|
||||
for suggesting to use HTTP code 503 for unavailable channels
|
||||
|
||||
Uwe
|
||||
for reporting a compiler error in client/device.c with VDR < 1.7.22
|
||||
|
||||
Chris Tallon
|
||||
for his kind permission to use VOMP's recplayer for replaying recordings
|
||||
|
||||
macmenot
|
||||
for adapting Makefiles to VDR 1.7.36+
|
||||
|
||||
thomasjfox
|
||||
for fixing cSuspendCtl preventing idle shutdown
|
||||
|
||||
hivdr
|
||||
for adding the pos= parameter for replaying recordings from a certain position
|
||||
for suggesting to add the HTTP "Server" header
|
||||
|
||||
hummel99
|
||||
for reporting and helping to debug channel switch issues with priority > 0
|
||||
for reporting a race condition when switching the server's LiveTV device
|
||||
|
||||
Henrik Niehaus
|
||||
for fixing replay of large TS files on 32-bit systems
|
||||
|
||||
Guy Martin
|
||||
for adding SVDRP commands to list and disconnect clients
|
||||
|
||||
Martin1234
|
||||
for suggesting a service call, returning the number of clients
|
||||
for implementing GetCurrentlyTunedTransponder() on client
|
||||
|
||||
Toerless Eckert
|
||||
for converting suspend.dat into proper PES format
|
||||
for investigating and fixing problems caused by filter streaming
|
||||
for fixing TimedWrite() so it doesn't fail after a slow but successful write
|
||||
for suggesting to double the size of client's filter buffer
|
||||
|
||||
Tomasz Maciej Nowak
|
||||
for providing Polish language texts
|
||||
|
||||
Christopher Reimer
|
||||
for providing an initial compatibility patch for VDR 2.3.1
|
||||
|
||||
Matthias Senzel
|
||||
for refining the compatibility patch for VDR 2.3.1
|
||||
|
||||
David Binderman
|
||||
for fixing an lseek error check in libdvbmpeg
|
||||
|
||||
Jasmin J
|
||||
for fixing some warnings in libdvbmpeg
|
||||
for adding .gitignore
|
||||
for fixing compilation for VDR 2.3.7
|
||||
|
||||
39
COPYING
39
COPYING
@@ -1,12 +1,12 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
@@ -15,7 +15,7 @@ software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
@@ -55,8 +55,8 @@ patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
@@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions:
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
@@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
@@ -225,7 +225,7 @@ impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
@@ -255,7 +255,7 @@ make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
@@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
@@ -303,10 +303,9 @@ the "copyright" line and a pointer to where the full notice is found.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
@@ -336,5 +335,5 @@ necessary. Here is a sample; alter the names:
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
|
||||
338
HISTORY
338
HISTORY
@@ -1,6 +1,344 @@
|
||||
VDR Plugin 'streamdev' Revision History
|
||||
---------------------------------------
|
||||
|
||||
- fixed compilation for VDR 2.3.7 (thanks to Jasmin J)
|
||||
- added .gitignore (thanks to Jasmin J)
|
||||
- fixed some warnings in libdvbmpeg (thanks to Jasmin J)
|
||||
- fixed lseek error check in libdvbmpeg (thanks to David Binderman)
|
||||
- server compatibility with VDR 2.3.1 (thanks to Christopher Reimer and
|
||||
Matthias Senzel)
|
||||
- client compatibility with VDR 2.3.1
|
||||
- use cReceiver::SetPriority(...) in VDR 2.1.4+
|
||||
- doubled size of client's filter buffer (suggested by Toerless Eckert)
|
||||
- make sure TimedWrite(...) doesn't return failure after a slow but successful
|
||||
write operation (thanks to Toerless Eckert)
|
||||
- fixed problems related to VTP filter streaming like ringbuffer overflows,
|
||||
stuttering or aborting video stream (thanks to Toerless Eckert)
|
||||
- added Polish translation (thanks to Tomasz Maciej Nowak)
|
||||
- converted suspend.dat into proper PES format (thanks to Toerless Eckert)
|
||||
- implemented GetCurrentlyTunedTransponder() on client (thanks to Martin1234)
|
||||
- added service call returning the number of clients (suggested by Martin1234)
|
||||
- added SVDRP commands to list and disconnect clients (thanks to Guy Martin)
|
||||
- fixed recplayer issues with large TS files (>4GB)
|
||||
- Don't abort externremux when internal read buffer is empty
|
||||
- Implemented remuxing of recordings
|
||||
- Make ChannelChange retune only if CA IDs changed (thanks to Oliver Wagner)
|
||||
- Implemented VDR 2.1.4 cStatus::ChannelChange(...)
|
||||
- Call detach only if receiver is attached
|
||||
- Try changing to other device when receiver got detached
|
||||
- In TSPIDS mode, create and attach receiver with empty pid list to occupy dev
|
||||
- Restructured server classes
|
||||
- New option for server side live TV buffer to prevent buffer underruns
|
||||
|
||||
2013-11-28: Version 0.6.1
|
||||
|
||||
- Updated Slovak translation (thanks to Milan Hrala)
|
||||
- Updated Finnish translation (thanks to Rolf Ahrenberg)
|
||||
- Disabled PS remuxer which is said to produce anything but PS
|
||||
- The patches intcamdevices and ignore_missing_cam are no longer required
|
||||
on VDR >= 1.7.30. The localchannelprovide patch became obsolete with VDR
|
||||
1.7.21.
|
||||
- Added option to suspend live TV when the server starts
|
||||
- Set device occupied when streamdev switches away LiveTV on the server, to
|
||||
reduce the risk that the VDR main loop immediately switches back, resulting
|
||||
in a black screen on the client (reported by hummel99)
|
||||
- Fixed channel switch issues with priority > 0 (reported by hummel99)
|
||||
- Removed noisy debug messages
|
||||
- Fixed HTTP menu destruction
|
||||
- API change of VDR 2.1.2
|
||||
- Fixed priority handling, messed up when adding multi-device support
|
||||
- Added HTTP "Server" header (suggested by hivdr)
|
||||
- Ignore dummy file extensions (.ts, .vob, .vdr) when parsing HTTP URIs
|
||||
- Select start position for replaying a recording by parameter pos=. Supported
|
||||
values are resume, mark.#, time.#, frame.# or a plain # representing a
|
||||
percentage if < 100 or a byte position otherwise (thanks to hivdr)
|
||||
- Start cSuspendCtl hidden or it will prevent idle shutdown (thanks to
|
||||
thomasjfox)
|
||||
- Fixed recordings menu inode numbers: ino_t is a long long on some systems
|
||||
- Updated Slovak translation (thanks to Milan Hrala)
|
||||
- Adapted Makefiles to VDR 1.7.36+ (thanks to macmenot). Old makefiles have
|
||||
been renamed to Makefile-1.7.33.
|
||||
- API changes of VDR 1.7.38 (thanks to mal@vdr-developer)
|
||||
- Added simple recordings menu in HTTP server
|
||||
- Restructured menuHTTP classes
|
||||
- Added RSS format for HTTP menus
|
||||
- Recordings can now also be selected by struct stat "st_dev:st_ino.rec"
|
||||
- Implemented multi-device support for streamdev client (suggested by johns)
|
||||
- Basic support for HTTP streaming of recordings
|
||||
- Close writer when streamer is finished
|
||||
- Don't abort VTP connection if filter stream is broken
|
||||
- Restructured cStreamdevStreamer: Moved inbound buffer into actual subclass.
|
||||
- In cStreamdevStreamer dropped Activate(bool) and moved its code into Start().
|
||||
- Moved cStreamdevFilterStreamer to livefilter.[hc]
|
||||
- Return HTTP/1.1 compliant response headers plus some always useful headers
|
||||
- Return HTTP URL parameters ending with ".dlna.org" as response headers
|
||||
- Store HTTP URL parameters in a map
|
||||
- Support HTTP HEAD requests with external remuxer
|
||||
- Fixed always using priority 0 for HTTP HEAD requests
|
||||
- Start writer right after creating it
|
||||
- Corrected typos (thanks to Ville Skyttä)
|
||||
- Fixed compiler error in client/device.c with VDR < 1.7.22 (reported by
|
||||
Uwe@vdrportal)
|
||||
- Updated Italian translation (thanks to Diego Pierotto)
|
||||
- Added DeviceName() and DeviceType() to client device. The server IP and the
|
||||
number of the device used on the server are returned respectively.
|
||||
|
||||
2012-05-29: Version 0.6.0
|
||||
|
||||
- Reimplemented some client device methods
|
||||
- Proper fix for "client sends ABRT after TUNE". Obsoletes many hacks in client
|
||||
- Added CLOCK_MONOTONIC timestamp and thread id to Dprintf
|
||||
- Silenced warning (thanks to Rolf Ahrenberg)
|
||||
- Updated Finnish translation (thanks to Rolf Ahrenberg)
|
||||
- Replaced server-side suspend modes with priority based precedence handling
|
||||
- Client-side priority handling for VDR >= 1.7.25 and servers running VTP > 1.0
|
||||
- Introduced VTP protocol version numbering for easier compatibility handling
|
||||
between different client and server versions. The server includes the protocol
|
||||
version in its greeting string, the client reports its version with the new
|
||||
command "VERS".
|
||||
- Dropped compatibility of streamdev-server with VDR < 1.7.25
|
||||
|
||||
2012-05-12: Version 0.5.2
|
||||
|
||||
- Use fileno() to retrieve the fd from a FILE structure (submitted by an
|
||||
anonymous user)
|
||||
- New special meaning "show current channel" when channel 0 is requested.
|
||||
Applies to HTTP streaming only (thanks to Rolf Ahrenberg)
|
||||
- Fixed ProvidesChannel() on client always returning true since the new timeout
|
||||
option has been added.
|
||||
- Updated Finnish translation (thanks to Rolf Ahrenberg)
|
||||
- With VDR 1.7.25 priorities down to -99 will be used. Please update
|
||||
"Minimum Priority" in streamdev-client setup.
|
||||
- Use the new streamdev-client setup option "Live TV Priority" to control
|
||||
precedence among multiple clients. The VDR option "Primary Limit" which
|
||||
has previouly been used for this purpose has been dropped in VDR 1.7.25.
|
||||
- Timout for network operations now configurable in streamdev-client setup
|
||||
- Added timeout to Connect()
|
||||
- Report the server-side HTTP status "503 Service unavailable" instead of
|
||||
the client-side error "409 Conflict" when a channel is unavailable
|
||||
(suggested by Methodus)
|
||||
- Update of po headers and Finnish translation (thanks to Rolf Ahrenberg)
|
||||
- support for non-cycle-free setups (e.g. where two VDRs mutually share
|
||||
their DVB cards through streamdev-client/-server). Must be enabled in
|
||||
streamdev-server setup. Obsoletes recursion patches.
|
||||
- API change of VDR 1.7.22
|
||||
- VDR 1.7.22 obsoletes cap_net_raw patch. Added cap_net_raw patch for VDR
|
||||
1.7.5 - 1.7.21.
|
||||
- Update and UTF-8 conversion of Finnish po files (thanks to Rolf Ahrenberg)
|
||||
- Added "Hide mainmenu entry" option on server (thanks to Rolf Ahrenberg)
|
||||
- Added server menu with list of clients. Connections can be terminated
|
||||
with the "red" key. The former main menu action of suspending live TV
|
||||
moved to the "blue" key.
|
||||
- code cleanup and optimization (thanks to Ville Skyttä)
|
||||
- properly shutdown IGMP timeout handler thread when the plugin is stopped.
|
||||
Fixes occasional segfaults on VDR exit.
|
||||
- fixed memory leak in libdvbmpeg read_pes (thanks to Ville Skyttä)
|
||||
- dropped several unused functions in libdvbmpeg
|
||||
- restricted VTP command RENR to liemikuutio patch < 1.32. Build fails with
|
||||
newer versions of this patch (thanks to Ville Skyttä)
|
||||
- updated outdated COPYING file and FSF address (thanks to Ville Skyttä)
|
||||
- include SDT and TDT in TS streams
|
||||
- the icy-name HTTP header sent with radio streams makes VLC pick the wrong
|
||||
demuxer. Send icy-name only for ES audio streams.
|
||||
- fixed regression of "live TV must be switched in VDR main thread" change:
|
||||
deadlock in IGMP streaming server when switching live TV.
|
||||
- streamdev-client returns true in its AvoidRecording() method introduced
|
||||
with VDR 1.7.19. Note however that the impact of NumProvidedSystems is
|
||||
higher.
|
||||
- updated device selection to code of VDR 1.7.19
|
||||
- adaption to VDR 1.7.12 cReceiver API change
|
||||
- increased WRITERBUFSIZE. Has been reported to fix some ringbuffer
|
||||
overflows (thanks to Lubo¨ Dole¸el)
|
||||
- check availability of channel if VTP command TUNE is called without
|
||||
prior PROV call (e.g. client side EPG scan)
|
||||
- added support for VDR 1.7.19 SignalStrength/SignalQuality
|
||||
- analog video channels use the same transponder and pid for different
|
||||
channels, so streamdev-client must always issue TUNE command
|
||||
- server must close the VTP connection also if filter stream is broken
|
||||
- fixed missing #ifdefs for new NumProvidedSystems setup option
|
||||
- new externremux.sh mencoder config options: audio pid by language code
|
||||
(-alang) and verbosity (-msglevel) (thanks to Pekko Tiitto)
|
||||
- writer must not spend too much time waiting in select() without checking
|
||||
if the thread has been cancelled
|
||||
- added Spanish translation (thanks to Javier Bradineras)
|
||||
- live TV must be switched in VDR main thread
|
||||
- dropped compatibility with VDR < 1.5.16
|
||||
- return value of streamdev-clients cDevice::NumProvidedSystems() now
|
||||
configurable in plugin setup
|
||||
|
||||
2011-02-11: Version 0.5.1
|
||||
|
||||
- updated copy of GetClippedNumProvidedSystems to the version used since
|
||||
VDR 1.7.15 (reported by carel@vdrportal)
|
||||
- fixed the code deciding if a device is in use for live TV or not. It did
|
||||
not work as expected for FF cards (reported by wtor@vdrportal)
|
||||
- increased client side timeout for TUNE command
|
||||
- more dsyslog messages to help troubleshouting channel switch issues
|
||||
- improved the channel switch code trying to move live TV to different card
|
||||
- make sure that a client doesn't interrupt replaying on server's FF card
|
||||
(reported by wtor@vdrportal)
|
||||
- switching away live TV failed even when "always suspended" (reported by
|
||||
Michal Novotny)
|
||||
- fixed regression: no receiver created for ES/PS/PES (reported by Gavin
|
||||
Hamill)
|
||||
- VTP no longer uses a static priority value for its server-side receivers.
|
||||
The server stores channel and priority requested with the PROV command and
|
||||
re-uses these values in a subsequent TUNE for the same channel. The new
|
||||
PRIO command is used to update the receiver's priority if necessary.
|
||||
- added parameter HEIGHT to externremux.sh
|
||||
- fixed syslog messages reporting local instead of remote IP and port
|
||||
- fixed regression of the GetDevice(...) change. Filter streaming to clients
|
||||
with a recent VDR version no longer worked.
|
||||
- log an error if externremux.sh is missing or not executable
|
||||
- since VDR 1.5.0 cDevice::GetDevice(...) is no longer a query only method.
|
||||
It detaches all receivers of the device it returns. So it is no longer
|
||||
suitable for testing the availability of a device. Added a copy of VDR's
|
||||
cDevice::GetDevice(...) without the detach receivers part as a workaround
|
||||
until a better solution is available
|
||||
- added dsyslog messages to help troubleshouting channel switch issues
|
||||
- VTP command SUSP didn't attach the player to the primary device
|
||||
- fixed incompatibilities with older make versions
|
||||
- replacing a connections receiver is now an atomic operation. Solves
|
||||
stuttering audio/video due to lost TS packets when adding/removing PIDs
|
||||
- disabled attribute warn_unused_result in libdvbmpeg
|
||||
- slightly increased thread priorities of cStreamdevWriter/Streamer
|
||||
(suggested by Rolf Ahrenberg)
|
||||
- fixed missing support for invisible channel groups (groups without name)
|
||||
in HTTP menu (reported by Timothy D. Lenz)
|
||||
- don't quote actual program call in externremux.sh, so you can run the
|
||||
program through e.g. nice or taskset just by extending the variable
|
||||
which holds the program name
|
||||
- in externremux.sh each mencoder audio and video codec has a dedicated
|
||||
variable for a default option string now. Still you can override each
|
||||
default option with an URL parameter
|
||||
- externremux.sh mencoder now uses scale parameter with negative height
|
||||
instead of -xy for scaling (suggested by vel_tins@vdrportal)
|
||||
- added FPS (frames per second) parameter to externremux.sh (suggested by
|
||||
vel_tins@vdrportal)
|
||||
- don't use std::map.at(). It's not available in older libstdc++ version
|
||||
(reported by Matthias Prill)
|
||||
- fixed extremux x264 using value of ABR for VBR (thanks to vel_tins@vdrportal)
|
||||
|
||||
2010-07-20: Version 0.5.0b
|
||||
|
||||
- fixed wrong URL path in m3u playlists (reported by Norman Thiel)
|
||||
|
||||
2010-07-20: Version 0.5.0a
|
||||
|
||||
- set externremux.sh executable in distribution archive
|
||||
- externremux quality value should be wlan54, not wlan45 (reported by
|
||||
wolfi.m@vdrportal)
|
||||
|
||||
2010-07-19: Version 0.5.0
|
||||
|
||||
- using SIGINT in externremux to kill mencoder works better than SIGTERM;
|
||||
especially x264 still needs a SIGKILL sometimes
|
||||
- added --remove-destination to cp commands installing plugins
|
||||
- fixed "plugin doesn't honor APIVERSION" (reported by carel@vdrportal)
|
||||
- updated Italian translation (thanks to Diego Pierotto)
|
||||
- config option "client may suspend" hidden if not applicable
|
||||
- updated and enhanced README
|
||||
- separated language resources of client and server
|
||||
- restructured build process
|
||||
- added support for HTTP method HEAD
|
||||
- rewrite of externremux.sh, including support for various URL parameters,
|
||||
logging and improved shutdown
|
||||
- start externremux script in a separate process group
|
||||
- changed HTTP URL path for externremux from EXTERN to EXT (suggested by
|
||||
Rolf Ahrenberg)
|
||||
- HTTP headers now have to be emitted by externremux script
|
||||
- pass channel related information and URL parameters to externremux script
|
||||
through environment
|
||||
- implement CGI like interface for externremux script
|
||||
- dropped "Synchronize EPG" feature. Please use epgsync-plugin instead
|
||||
(available from http://vdr.schmirler.de)
|
||||
- proper tsplay-0.2 patch detection. tsplay-0.1 is no longer recognized
|
||||
(thanks to Udo Richter)
|
||||
- added compatibility with VDR 1.6 tsplay-0.1 patch
|
||||
- added support for EnhancedAC3 (thanks to Eric Valette)
|
||||
- fixed a memory leak in cStreamdevPatFilter::GetPid (thanks to lhanisch)
|
||||
- length -1 is the correct value for streams in M3U playlists
|
||||
- switching between two encrypted channels on the same transponder didn't
|
||||
always work (thanks to sk8ter@vdrportal)
|
||||
- added DELT FORCE option to delete running timers (thanks to Alwin Esch)
|
||||
- added VDR 1.7.11 parental rating support for VTP LSTE command (thanks to
|
||||
Alwin Esch)
|
||||
- added Lithuanian translation (thanks to Valdemaras Pipiras)
|
||||
- fixed missing virtual destructor for cTSRemux
|
||||
- added defines for large file support to Makefile as required by VDR 1.7.4+
|
||||
(reported by wirbel@vdrportal)
|
||||
- added Slovak translation (thanks to Milan Hrala)
|
||||
- fixed regression from fix for switching between encrypted channels. It was
|
||||
no longer possible to receive multiple (FTA) streams from the same
|
||||
transponder
|
||||
- silenced warnings concerning asprintf (requested by Rolf Ahrenberg)
|
||||
- don't update recordings list on CmdPLAY (reported by BBlack)
|
||||
- cleaned up common.h / common.c
|
||||
- dropped cStreamdevMenuSetupPage
|
||||
- report charset in HTTP replies (suggested by Rolf Ahrenberg)
|
||||
- use SO_KEEPALIVE option on all sockets do detect dead sockets (thanks to
|
||||
owagner)
|
||||
- enable PatFilter for externremux, so VLC can be used as remuxer or client
|
||||
- fixed insecure format strings in LSTX handlers (thanks to Anssi Hannula)
|
||||
- updated Finish translation (thanks to Rolf Ahrenberg)
|
||||
- removed redefinitions in includes - caused problems in older compilers
|
||||
- fixed ts2ps.h defines
|
||||
- fixed missing virtual for cTS2PESRemux destructor
|
||||
- silenced format mismatch warning on 64bit OS
|
||||
- added XBMC support by extending VTP capabilities (thanks to Alwin Esch)
|
||||
- now there's a common baseclass for all remuxers, make use of it
|
||||
- added cDevice::NumProvidedSystems() which was introduced in VDR 1.7.0
|
||||
- added namespace to remuxers
|
||||
- increased WRITERBUFSIZE - buffer was too small for high bandwidth content
|
||||
- removed cStreamdevStreamer::m_Running
|
||||
- eliminated potential busy waits in remuxers
|
||||
- updated cTSRemux static helpers to code of their VDR 1.6.0 counterparts
|
||||
- re-enabled PES vor VDR 1.7.3+. Streamdev now uses a copy of VDR 1.6.0's
|
||||
cRemux for TS to PES remuxing.
|
||||
- make sure that only complete TS packets are written to ringbuffers
|
||||
- use signaling instead of sleeps when writing to ringbuffers
|
||||
- optimized cStreamdevPatFilter PAT packet initialization
|
||||
- fixed cStreamdevPatFilter not processing PATs with length > TS_SIZE - 5
|
||||
- use a small ringbuffer for cStreamdevPatFilter instead of writing to
|
||||
cStreamdevStreamers SendBuffer as two threads mustn't write to the same
|
||||
ringbuffer
|
||||
- added missing call to StopSectionHandler which could cause crashes when
|
||||
shutting down VDR
|
||||
- added IGMP based multicast streaming
|
||||
- ignore trailing blank lines in HTTP requests
|
||||
- fixed parsing Min/MaxPriority from config (thanks to Joachim König-Baltes)
|
||||
- updated Finnish translation (thanks to Rolf Ahrenberg)
|
||||
- added Min/MaxPriority parameters. Can be used to keep client VDR from
|
||||
using streamdev e.g. when recording
|
||||
- disabled PES for VDR 1.7.3+
|
||||
- added Network Media Tank browser support to HTML pages (thanks to Jori
|
||||
Hamalainen)
|
||||
- minor fixes of PAT repacker
|
||||
- repack and send every PAT packet we receive
|
||||
- fixed null pointer in server.c when cConnection::Accept() failes
|
||||
- consider Pids from channels.conf when HTTP TS streaming. Section filtering
|
||||
is an optional feature for VDR devices, so we must not rely on the PMT
|
||||
alone (pointed out by wirbel@vdrportal)
|
||||
- improved externremux script termination (thanks to Rolf Ahrenberg)
|
||||
- use cThread::Running()/Active() instead of private members (thanks to
|
||||
Rolf Ahrenberg)
|
||||
- fixed output format of some debug messages (thanks to Rolf Ahrenberg)
|
||||
- added HTTP authentication
|
||||
- compatibility for VDR 1.7.1 (thanks to Udo Richter)
|
||||
- added vdr-1.6.0-intcamdevices.patch (thanks to Anssi Hannula)
|
||||
- fixed problem when switching from one encrypted channel to an other
|
||||
(reported by Tiroler@vdrportal, initial bugfix by pixelpeter@vdrportal,
|
||||
another fix by owagner@vdrportal)
|
||||
- added preprocessor directive for ancient gcc
|
||||
- added Russian translation (thanks to Oleg Roitburd)
|
||||
- fixed assignment of externremux.sh's default location (reported by plautze)
|
||||
- added French translation (thanks to micky979)
|
||||
- added Italian translation (thanks to Diego Pierotto)
|
||||
- added gettext support (thanks to Rolf Ahrenberg)
|
||||
- added vdr-1.6.0-ignore_missing_cam patch
|
||||
- dropped obsolete respect_ca patch
|
||||
- removed legacy code for < VDR 1.5.9 (thanks to Rolf Ahrenberg)
|
||||
|
||||
2008-04-07: Branched v0_4
|
||||
|
||||
- changed location of streamdevhosts.conf to VDRCONFDIR/plugins/streamdev
|
||||
|
||||
127
Makefile
127
Makefile
@@ -1,36 +1,46 @@
|
||||
#
|
||||
# Makefile for a Video Disk Recorder plugin
|
||||
#
|
||||
# $Id: Makefile,v 1.15 2008/04/07 14:50:32 schmirl Exp $
|
||||
# $Id: $
|
||||
|
||||
# The official name of this plugin.
|
||||
# This name will be used in the '-P...' option of VDR to load the plugin.
|
||||
# By default the main source file also carries this name.
|
||||
#
|
||||
|
||||
PLUGIN = streamdev
|
||||
|
||||
### The version number of this plugin (taken from the main source file):
|
||||
|
||||
VERSION = $(shell grep 'const char \*VERSION *=' common.c | awk '{ print $$5 }' | sed -e 's/[";]//g')
|
||||
|
||||
### The C++ compiler and options:
|
||||
|
||||
CXX ?= g++
|
||||
CXXFLAGS ?= -fPIC -g -O2 -Wall -Woverloaded-virtual -Wno-parentheses
|
||||
|
||||
### The directory environment:
|
||||
|
||||
VDRDIR = ../../..
|
||||
LIBDIR = ../../lib
|
||||
TMPDIR = /tmp
|
||||
# Use package data if installed...otherwise assume we're under the VDR source directory:
|
||||
PKGCFG = $(if $(VDRDIR),$(shell pkg-config --variable=$(1) $(VDRDIR)/vdr.pc),$(shell pkg-config --variable=$(1) vdr || pkg-config --variable=$(1) ../../../vdr.pc))
|
||||
LIBDIR = $(call PKGCFG,libdir)
|
||||
LOCDIR = $(call PKGCFG,locdir)
|
||||
PLGCFG = $(call PKGCFG,plgcfg)
|
||||
#
|
||||
TMPDIR ?= /tmp
|
||||
|
||||
### The compiler options:
|
||||
|
||||
export CFLAGS = $(call PKGCFG,cflags)
|
||||
export CXXFLAGS = $(call PKGCFG,cxxflags)
|
||||
|
||||
### The version number of VDR's plugin API:
|
||||
|
||||
APIVERSION = $(call PKGCFG,apiversion)
|
||||
|
||||
### Allow user defined options to overwrite defaults:
|
||||
|
||||
-include $(VDRDIR)/Make.config
|
||||
-include $(PLGCFG)
|
||||
|
||||
### The version number of VDR (taken from VDR's "config.h"):
|
||||
|
||||
APIVERSION = $(shell grep 'define APIVERSION ' $(VDRDIR)/config.h | awk '{ print $$3 }' | sed -e 's/"//g')
|
||||
### export all vars for sub-makes, using absolute paths
|
||||
LIBDIR := $(shell cd $(LIBDIR) >/dev/null 2>&1 && pwd)
|
||||
LOCDIR := $(shell cd $(LOCDIR) >/dev/null 2>&1 && pwd)
|
||||
export
|
||||
unexport PLUGIN
|
||||
|
||||
### The name of the distribution archive:
|
||||
|
||||
@@ -39,83 +49,56 @@ PACKAGE = vdr-$(ARCHIVE)
|
||||
|
||||
### Includes and Defines (add further entries here):
|
||||
|
||||
INCLUDES += -I$(VDRDIR)/include -I.
|
||||
INCLUDES += -I$(VDRDIR)/include -I..
|
||||
export INCLUDES
|
||||
|
||||
DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
|
||||
|
||||
### The object files (add further files here):
|
||||
|
||||
COMMONOBJS = common.o i18n.o \
|
||||
\
|
||||
tools/source.o tools/select.o tools/socket.o tools/tools.o
|
||||
|
||||
CLIENTOBJS = $(PLUGIN)-client.o \
|
||||
\
|
||||
client/socket.o client/device.o client/setup.o \
|
||||
client/filter.o
|
||||
|
||||
|
||||
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/menuHTTP.o \
|
||||
\
|
||||
remux/tsremux.o remux/ts2ps.o remux/ts2es.o remux/extern.o
|
||||
DEFINES += -D_GNU_SOURCE
|
||||
|
||||
ifdef DEBUG
|
||||
DEFINES += -DDEBUG
|
||||
DEFINES += -DDEBUG
|
||||
endif
|
||||
ifdef STREAMDEV_DEBUG
|
||||
DEFINES += -DDEBUG
|
||||
endif
|
||||
|
||||
### The main target:
|
||||
|
||||
.PHONY: all dist clean
|
||||
all: libvdr-$(PLUGIN)-client.so libvdr-$(PLUGIN)-server.so
|
||||
|
||||
### Implicit rules:
|
||||
|
||||
%.o: %.c
|
||||
$(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $<
|
||||
|
||||
# Dependencies:
|
||||
|
||||
MAKEDEP = $(CXX) -MM -MG
|
||||
DEPFILE = .dependencies
|
||||
ifdef GCC3
|
||||
$(DEPFILE): Makefile
|
||||
@rm -f $@
|
||||
@for i in $(CLIENTOBJS:%.o=%.c) $(SERVEROBJS:%.o=%.c) $(COMMONOBJS:%.o=%.c) ; do \
|
||||
$(MAKEDEP) $(DEFINES) $(INCLUDES) -MT "`dirname $$i`/`basename $$i .c`.o" $$i >>$@ ; \
|
||||
done
|
||||
else
|
||||
$(DEPFILE): Makefile
|
||||
@$(MAKEDEP) $(DEFINES) $(INCLUDES) $(CLIENTOBJS:%.o=%.c) $(SERVEROBJS:%.o=%.c) \
|
||||
$(COMMONOBJS:%.o=%.c) > $@
|
||||
endif
|
||||
|
||||
-include $(DEPFILE)
|
||||
.PHONY: all client server install install-client install-server dist clean
|
||||
all: client server
|
||||
|
||||
### Targets:
|
||||
|
||||
libdvbmpeg/libdvbmpegtools.a: libdvbmpeg/*.c libdvbmpeg/*.h
|
||||
$(MAKE) -C ./libdvbmpeg libdvbmpegtools.a
|
||||
client:
|
||||
$(MAKE) -C ./tools
|
||||
$(MAKE) -C ./client
|
||||
|
||||
libvdr-$(PLUGIN)-client.so: $(CLIENTOBJS) $(COMMONOBJS) libdvbmpeg/libdvbmpegtools.a
|
||||
libvdr-$(PLUGIN)-server.so: $(SERVEROBJS) $(COMMONOBJS) libdvbmpeg/libdvbmpegtools.a
|
||||
server:
|
||||
$(MAKE) -C ./tools
|
||||
$(MAKE) -C ./libdvbmpeg
|
||||
$(MAKE) -C ./remux
|
||||
$(MAKE) -C ./server
|
||||
|
||||
%.so:
|
||||
$(CXX) $(CXXFLAGS) -shared $^ -o $@
|
||||
@cp $@ $(LIBDIR)/$@.$(APIVERSION)
|
||||
install-client: client
|
||||
$(MAKE) -C ./client install
|
||||
# installs to $(LIBDIR)/libvdr-streamdev-client.so.$(APIVERSION)
|
||||
|
||||
install-server: server
|
||||
$(MAKE) -C ./server install
|
||||
# installs to $(LIBDIR)/libvdr-streamdev-server.so.$(APIVERSION)
|
||||
|
||||
install: install-client install-server
|
||||
|
||||
dist: clean
|
||||
@-rm -rf $(TMPDIR)/$(ARCHIVE)
|
||||
@mkdir $(TMPDIR)/$(ARCHIVE)
|
||||
@cp -a * $(TMPDIR)/$(ARCHIVE)
|
||||
@tar czf $(PACKAGE).tgz --exclude CVS -C $(TMPDIR) $(ARCHIVE)
|
||||
@tar czf $(PACKAGE).tgz -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 ./tools clean
|
||||
$(MAKE) -C ./libdvbmpeg clean
|
||||
$(MAKE) -C ./remux clean
|
||||
$(MAKE) -C ./client clean
|
||||
$(MAKE) -C ./server clean
|
||||
|
||||
108
Makefile-1.7.33
Normal file
108
Makefile-1.7.33
Normal file
@@ -0,0 +1,108 @@
|
||||
#
|
||||
# Makefile for a Video Disk Recorder plugin
|
||||
#
|
||||
# $Id: Makefile,v 1.23 2010/08/02 10:36:59 schmirl Exp $
|
||||
|
||||
# The main source file name.
|
||||
#
|
||||
PLUGIN = streamdev
|
||||
|
||||
### The C/C++ compiler and options:
|
||||
|
||||
CC ?= gcc
|
||||
CFLAGS ?= -g -O2 -Wall
|
||||
|
||||
CXX ?= g++
|
||||
CXXFLAGS ?= -g -O2 -Wall -Woverloaded-virtual -Wno-parentheses
|
||||
|
||||
### The version number of this plugin (taken from the main source file):
|
||||
|
||||
VERSION = $(shell grep 'const char \*VERSION *=' common.c | awk '{ print $$5 }' | sed -e 's/[";]//g')
|
||||
|
||||
### The directory environment:
|
||||
|
||||
VDRDIR = ../../..
|
||||
LIBDIR = ../../lib
|
||||
TMPDIR = /tmp
|
||||
|
||||
### The version number of VDR (taken from VDR's "config.h"):
|
||||
|
||||
APIVERSION = $(shell grep 'define APIVERSION ' $(VDRDIR)/config.h | awk '{ print $$3 }' | sed -e 's/"//g')
|
||||
APIVERSNUM = $(shell grep 'define APIVERSNUM ' $(VDRDIR)/config.h | awk '{ print $$3 }' | sed -e 's/"//g')
|
||||
TSPLAYVERSNUM = $(shell grep 'define TSPLAY_PATCH_VERSION ' $(VDRDIR)/device.h | awk '{ print $$3 }')
|
||||
|
||||
### Allow user defined options to overwrite defaults:
|
||||
|
||||
ifeq ($(shell test $(APIVERSNUM) -ge 10713; echo $$?),0)
|
||||
include $(VDRDIR)/Make.global
|
||||
else
|
||||
ifeq ($(shell test $(APIVERSNUM) -ge 10704 -o -n "$(TSPLAYVERSNUM)" ; echo $$?),0)
|
||||
DEFINES += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE
|
||||
CFLAGS += -fPIC
|
||||
CXXFLAGS += -fPIC
|
||||
else
|
||||
CFLAGS += -fPIC
|
||||
CXXFLAGS += -fPIC
|
||||
endif
|
||||
endif
|
||||
|
||||
-include $(VDRDIR)/Make.config
|
||||
|
||||
### export all vars for sub-makes, using absolute paths
|
||||
|
||||
VDRDIR := $(shell cd $(VDRDIR) >/dev/null 2>&1 && pwd)
|
||||
LIBDIR := $(shell cd $(LIBDIR) >/dev/null 2>&1 && pwd)
|
||||
export
|
||||
unexport PLUGIN
|
||||
|
||||
### The name of the distribution archive:
|
||||
|
||||
ARCHIVE = $(PLUGIN)-$(VERSION)
|
||||
PACKAGE = vdr-$(ARCHIVE)
|
||||
|
||||
### Includes and Defines (add further entries here):
|
||||
|
||||
INCLUDES += -I$(VDRDIR)/include -I..
|
||||
|
||||
DEFINES += -D_GNU_SOURCE
|
||||
|
||||
ifdef DEBUG
|
||||
DEFINES += -DDEBUG
|
||||
endif
|
||||
ifdef STREAMDEV_DEBUG
|
||||
DEFINES += -DDEBUG
|
||||
endif
|
||||
|
||||
### The main target:
|
||||
|
||||
.PHONY: all client server dist clean
|
||||
all: client server
|
||||
|
||||
### Targets:
|
||||
|
||||
client:
|
||||
$(MAKE) -C ./tools
|
||||
$(MAKE) -C ./client
|
||||
# installs to $(LIBDIR)/libvdr-streamdev-client.so.$(APIVERSION)
|
||||
|
||||
server:
|
||||
$(MAKE) -C ./tools
|
||||
$(MAKE) -C ./libdvbmpeg
|
||||
$(MAKE) -C ./remux
|
||||
$(MAKE) -C ./server
|
||||
# installs to $(LIBDIR)/libvdr-streamdev-server.so.$(APIVERSION)
|
||||
|
||||
dist: clean
|
||||
@-rm -rf $(TMPDIR)/$(ARCHIVE)
|
||||
@mkdir $(TMPDIR)/$(ARCHIVE)
|
||||
@cp -a * $(TMPDIR)/$(ARCHIVE)
|
||||
@tar czf $(PACKAGE).tgz --exclude CVS -C $(TMPDIR) $(ARCHIVE)
|
||||
@-rm -rf $(TMPDIR)/$(ARCHIVE)
|
||||
@echo Distribution package created as $(PACKAGE).tgz
|
||||
|
||||
clean:
|
||||
$(MAKE) -C ./tools clean
|
||||
$(MAKE) -C ./libdvbmpeg clean
|
||||
$(MAKE) -C ./remux clean
|
||||
$(MAKE) -C ./client clean
|
||||
$(MAKE) -C ./server clean
|
||||
495
README
495
README
@@ -15,18 +15,20 @@ Contents:
|
||||
|
||||
1. Description
|
||||
2. Installation
|
||||
2.1 VDR 1.3.X and older
|
||||
2.2 VDR 1.4.X and above
|
||||
2.3 Updating from streamdev 0.3.x
|
||||
2.1 Compatibility
|
||||
2.2 Compiling
|
||||
2.3 Updating
|
||||
3. Usage
|
||||
3.1 Usage HTTP server
|
||||
3.2 Usage VDR-to-VDR server
|
||||
3.3 Usage VDR-to-VDR client
|
||||
3.2 Usage IGMP multicast server
|
||||
3.3 Usage VDR-to-VDR server
|
||||
3.4 Usage VDR-to-VDR client
|
||||
4. Other useful Plugins
|
||||
4.1 Plugins for VDR-to-VDR clients
|
||||
4.2 Plugins for Server
|
||||
4.3 Alternatives
|
||||
5. Known Problems
|
||||
5. externremux.sh
|
||||
6. Known Problems
|
||||
|
||||
|
||||
1. Description:
|
||||
@@ -61,7 +63,7 @@ the PROTOCOL file.
|
||||
2. Installation:
|
||||
----------------
|
||||
|
||||
Let's say streamdev's version is 0.4.0 and vdr's version is 1.X.X. If you
|
||||
Let's say streamdev's version is 0.5.0 and vdr's version is 1.X.X. If you
|
||||
use anything else please exchange the version numbers appropriately (this
|
||||
way I don't have to update this section all the times;) ).
|
||||
|
||||
@@ -78,56 +80,115 @@ If you want to drive additional Input-Devices (with different sources) on the
|
||||
client, you can merge the channels.conf files. VDR will detect if the local
|
||||
device or the network device can receive the channels.
|
||||
|
||||
Last, but not least you have to copy the streamdev folder into the
|
||||
"plugins/streamdev" subfolder of VDR's config-directory (which is equal to your
|
||||
video-directory if not specified otherwise). For example, if you didn't specify
|
||||
a separate config-directory, and specified your video directory as "/video0",
|
||||
the directory has to be copied to /video0/plugins/streamdev.
|
||||
Last, but not least you have to copy the streamdev-server folder into the
|
||||
"plugins/streamdev-server" subfolder of VDR's config-directory (which is equal
|
||||
to your video-directory if not specified otherwise). For example, if you didn't
|
||||
specify a separate config-directory, and set your video directory to "/video0",
|
||||
the directory has to be copied to /video0/plugins/streamdev-server.
|
||||
|
||||
The directory contains a file named streamdevhosts.conf which you must adjust
|
||||
to your needs. The syntax is the same as for svdrphosts.conf, so please consult
|
||||
VDR's documentation on how to fill that file, if you can't do it on-the-fly.
|
||||
|
||||
There's also a sample externremux.sh script in this directory. It is used by
|
||||
streamdev's external remux feature. The sample script uses mencoder. Please
|
||||
check the script for further information. You can specify a different script
|
||||
location with the -r parameter. The VDR commandline would then include a
|
||||
streamdev's external remux feature. The sample script uses mencoder by default.
|
||||
Please check the script for further information. You can specify a different
|
||||
script location with the -r parameter. The VDR commandline would then include a
|
||||
"-P 'streamdev-server -r /usr/local/bin/remux.sh'". Note the additional quotes,
|
||||
as otherwise -r will be passed to VDR and not to streamdev.
|
||||
|
||||
|
||||
2.1 VDR 1.3.X and older:
|
||||
------------------------
|
||||
2.1 Compatibility:
|
||||
------------------
|
||||
|
||||
This version is not compatible to VDR releases older than 1.4.0. You will
|
||||
probably need one of the streamdev-0.3.x releases.
|
||||
This version is not compatible to VDR releases older than 1.7.25. Use one of
|
||||
the streamdev-0.5.x releases for older versions.
|
||||
|
||||
2.2 VDR 1.4.X and above:
|
||||
------------------------
|
||||
2.2 Compiling:
|
||||
--------------
|
||||
|
||||
cd vdr-1.X.X/PLUGINS/src
|
||||
tar xvfz vdr-streamdev-0.4.0.tgz
|
||||
ln -s streamdev-0.4.0 streamdev
|
||||
cp -r streamdev/streamdev VDRCONFDIR/plugins/
|
||||
cd ../..
|
||||
make [options, if necessary] vdr
|
||||
make [options, if necessary] plugins
|
||||
The Makefiles are for VDR 1.7.36 and above. For VDR 1.7.33 and below, please
|
||||
replace the Makefiles in the main directory and in the client/ and server/
|
||||
subdirectories with the corresponding Makefile-1.7.33 files. With VDR 1.7.34 and
|
||||
1.7.35 YMMV ;)
|
||||
|
||||
2.3 Updating from streamdev 0.3.x
|
||||
----------------------------------
|
||||
cd vdr-1.X.X/PLUGINS/src
|
||||
tar xvfz vdr-streamdev-0.5.0.tgz
|
||||
ln -s streamdev-0.5.0 streamdev
|
||||
cp -r streamdev/streamdev-server VDRCONFDIR/plugins/
|
||||
cd ../..
|
||||
make [options, if necessary] vdr
|
||||
make [options, if necessary] plugins
|
||||
|
||||
Starting with streamdev 0.4.0, all additional files are kept in a directory
|
||||
called "streamdev" inside VDR's plugin config directory. This affects in
|
||||
particular the file "streamdevhosts.conf". You will have to move it to its
|
||||
To build only the plugin, change into the streamdev source folder and issue
|
||||
make
|
||||
|
||||
To build only streamdev-server or only streamdev-client, use
|
||||
make server
|
||||
make client
|
||||
|
||||
2.3 Updating:
|
||||
--------------
|
||||
|
||||
If you are updating streamdev from an earlier release, you might have to
|
||||
perform some additional steps. Check which version you've been running before,
|
||||
then read below for the necessary changes.
|
||||
|
||||
* Priorities:
|
||||
-------------
|
||||
(Affected: 0.5.x and older)
|
||||
|
||||
The server-side setting "Suspend behaviour" has been dropped in 0.6.0 in favour
|
||||
of priority based precedence. A priority of 0 and above means that clients
|
||||
have precedence. A negative priority gives precedence to local live TV on the
|
||||
server. So if "Suspend behaviour" was previously set to "Client may suspend" or
|
||||
"Never suspended", you will have to configure a negative priority. If the
|
||||
"Suspend behaviour" was set to "Always suspended", the default values should do.
|
||||
|
||||
Configure the desired priorities for HTTP and IGMP Multicast streaming in the
|
||||
settings of streamdev-server. If you haven't updated all your streamdev-clients
|
||||
to at least 0.5.2, configure "Legacy Client Priority", too.
|
||||
|
||||
In streamdev-client, you should set "Minimum Priority" to -99. Adjust "Live TV
|
||||
Priority" if necessary.
|
||||
|
||||
* Location of files:
|
||||
--------------------
|
||||
(Affected: 0.3.x, 0.4.x, 0.4.0pre, 0.5.0pre)
|
||||
|
||||
Starting with streamdev 0.5.0, all additional files are kept in a directory
|
||||
called "streamdev-server" inside VDR's plugin config directory. It is the new
|
||||
default location of externremux.sh and the new place where streamdev-server
|
||||
expects the file "streamdevhosts.conf". You will have to move this file to its
|
||||
new location:
|
||||
|
||||
mv VDRCONFDIR/plugins/streamdevhosts.conf VDRCONFDIR/plugins/streamdev/
|
||||
streamdev 0.3.x:
|
||||
mv VDRCONFDIR/plugins/streamdevhosts.conf VDRCONFDIR/plugins/streamdev-server/
|
||||
|
||||
(Directory VDRCONFDIR/plugins/streamdev already exists, as you copied the
|
||||
whole folder from the sources directory as suggested above, right?)
|
||||
streamdev 0.4.x, 0.4.0pre and 0.5.0pre:
|
||||
mv VDRCONFDIR/plugins/streamdev VDRCONFDIR/plugins/streamdev-server/
|
||||
|
||||
The new default location for externremux.sh is also in this directory.
|
||||
Now check the contents of streamdevhosts.conf. Does it contain a "0.0.0.0/0"
|
||||
entry? If your VDR machine is connected to the Internet, this line gives
|
||||
*anyone* full access to streamdev, unless you took some other measures to
|
||||
prevent this (e.g. firewall). You might want to remove this line and enable
|
||||
HTTP authentication instead.
|
||||
|
||||
* Handling of externremux script:
|
||||
---------------------------------
|
||||
(Affected: 0.3.x, 0.4.0pre, 0.5.0pre)
|
||||
|
||||
Streamdev server's externremux script became responsible for emitting all HTTP
|
||||
headers. A quick and dirty extension to your current script would be:
|
||||
|
||||
echo -ne 'Content-type: video/mpeg\r\n'
|
||||
echo -ne '\r\n'
|
||||
|
||||
However I encourage you to try the new externremux.sh script shipped with the
|
||||
streamdev source distribution.
|
||||
|
||||
To emphasize the required change in externremux, the URL path for passing the
|
||||
stream through externremux has changed from EXTERN to EXT.
|
||||
|
||||
3. Usage:
|
||||
---------
|
||||
@@ -136,25 +197,41 @@ Start the server core itself by specifying -Pstreamdev-server on your VDR
|
||||
commandline. To use the client core, specify -Pstreamdev-client. Both parts
|
||||
can run in one VDR instance, if necessary.
|
||||
|
||||
The parameter "Suspend behaviour" allows you to specify how the server should
|
||||
react in case the client requests a channel that would require switching the
|
||||
primary device (i.e. disrupt live-tv). If set to "Offer suspend mode", you will
|
||||
have a new entry in the main menu. Activating that will put the server into
|
||||
"Suspend Mode" (a picture is displayed on TV). Then, a client may switch the
|
||||
primary card to wherever it likes to. While watching TV (Suspend deactivated),
|
||||
the client may not switch the transponder on the primary device. If you set
|
||||
the behaviour to "Always suspended" (the default), there will be normal live-tv
|
||||
on the server, but whenever a client decides to switch the transponder, the
|
||||
server will lose it's live-tv. Set to "Never suspended", the server always
|
||||
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).
|
||||
Precedence between multiple clients and between client and server is controlled
|
||||
with priorities. For HTTP and IGMP Multicast, the priority is configured in
|
||||
streamdev-server's setup menu. A negative priority gives precedence to local
|
||||
live TV on the server. Zero and positive values give precedence to the client.
|
||||
|
||||
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.
|
||||
The priority for VDR clients watching live TV is configured in the plugin setup
|
||||
of streamdev-client. For other client tasks (e.g. recording a client side timer)
|
||||
the same priority as on the client is used. With the parameter "Legacy client
|
||||
Priority" in streamdev-server's setup menu you can configure the priority for
|
||||
clients which cannot be configured to use negative priorities. It is used
|
||||
when an old client is detected an it requests priority "0".
|
||||
|
||||
On the server, the main menu entry "Streamdev Connections" gives you a list
|
||||
of currently connected clients. Use the "red" key to terminate a connection.
|
||||
Note that depending on connection type and client, the client might re-connect
|
||||
sooner or later.
|
||||
|
||||
The "blue" key in the server's main menu will suspend live TV on server. An
|
||||
image is displayed instead. This would allow a low priority client to switch
|
||||
to a different transponder. Enable "Client may suspend" in the server setup
|
||||
to allow VDR clients to suspend live TV remotely.
|
||||
|
||||
In the server's setup there's also an option to suspend live TV when starting
|
||||
the server. The "auto" option will suspend live TV if there's no device with
|
||||
an MPEG decoder available which is typically the case on a headless server.
|
||||
|
||||
NOTE: Precedence is mainly an issue on One-Card-Systems, since with multiple
|
||||
cards there is no need to switch transponders on the primary interface, if one
|
||||
of the other cards is idle (i.e. if it is not blocked by a recording). If all
|
||||
cards are in use (i.e. when something is recorded, or by multiple clients), this
|
||||
applies to Multiple-Card-Systems as well.
|
||||
|
||||
If your client suffers from buffer underruns while watching live TV, you can
|
||||
configure buffering on the server side. Enter a reasonable value (e.g. 300ms)
|
||||
as "Live TV buffer delay (ms)" in the server setup.
|
||||
|
||||
3.1 Usage HTTP server:
|
||||
----------------------
|
||||
@@ -166,15 +243,14 @@ listen to with the parameter "HTTP Server Port". The parameter "HTTP Streamtype"
|
||||
allows you to specify a default stream type, which is used if no specific type
|
||||
has been requested in the URL (see below). The supported stream types are:
|
||||
|
||||
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)
|
||||
TS Transport Stream (i.e. a dump from the device)
|
||||
PES Packetized Elemetary Stream (VDR's native recording format)
|
||||
ES Elementary Stream (only Video, if available, otherwise only Audio)
|
||||
EXT Pass stream through external script (e.g. for converting with mencoder)
|
||||
|
||||
Assuming that you leave the default port (3000), point your web browser to
|
||||
|
||||
http://hostname:3000/
|
||||
http://hostname:3000/
|
||||
|
||||
You will be presented a menu with links to various channel lists, including M3U
|
||||
playlist formats.
|
||||
@@ -182,29 +258,103 @@ 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
|
||||
http://hostname:3000/3
|
||||
http://hostname:3000/S19.2E-0-12480-898
|
||||
|
||||
The first one will deliver a channel by number on the server, the second one
|
||||
will request the channel by unique channel id. In addition, you can specify
|
||||
the desired stream type as a path to the channel.
|
||||
will request the channel by unique channel id. Use the special channel number 0
|
||||
to see the server's current live TV channel.
|
||||
|
||||
http://hostname:3000/TS/3
|
||||
http://hostname:3000/PES/S19.2E-0-12480-898
|
||||
In addition, you can specify the desired stream type as a path to the channel.
|
||||
|
||||
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', '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.
|
||||
Possible values are 'PES', 'TS', 'ES' and 'EXT'. 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
|
||||
mpg123 http://hostname:3000/ES/200
|
||||
|
||||
With 'EXTERN' you can also add a parameter which is passed as argument to the
|
||||
externremux script.
|
||||
With 'EXT' you can also add parameters which are passed as arguments to the
|
||||
externremux script (e.g. http://hostname:3000/EXT;param1=value1;param2=value2/3)
|
||||
Check your externremux.sh script for the parameters it understands. For details
|
||||
on how to modify or write your own externremux.sh, please see the chapter upon
|
||||
externremux.sh further down.
|
||||
|
||||
http://hostname:3000/EXTERN;some_parameter/3
|
||||
If you want to access streamdev's HTTP server from the Internet, do *not* grant
|
||||
access for anyone by allowing any IP in "streamdevhosts.conf". Instead, pass the
|
||||
"-a" commandline option to streamdev-server. It takes a username and a password
|
||||
as argument. Clients with an IP not accepted by "streamdevhosts.conf" will then
|
||||
have to login. The VDR commandline will have to look like this:
|
||||
|
||||
3.2 Usage VDR-to-VDR server:
|
||||
vdr ... -P 'streamdev-server -a vdr:secret' ...
|
||||
|
||||
Note the single quotes, as otherwise "-a" will be passed to VDR and not to
|
||||
streamdev-server. The login ("vdr" in the example above) doesn't have to exist
|
||||
as a system account.
|
||||
|
||||
3.2 Usage IGMP multicast server:
|
||||
--------------------------------
|
||||
|
||||
IGMP based multicast streaming is often used by settop boxes to receive IP TV.
|
||||
Streamdev's multicast server allows you to feed live TV from VDR to such a
|
||||
settop box. VLC is known to work well if you look for a software client.
|
||||
|
||||
The advantage of multicasting is that the actual stream is sent out only once,
|
||||
regardless of how many clients want to receive it. The downside is, that you
|
||||
cannot simply multicast across network boundaries. You need multicast routers.
|
||||
For multicast streaming over the public Internet you would even need to register
|
||||
for your own IP range. So don't even think of multicasting via Internet with
|
||||
streamdev! Streamdev will send the stream only to one local ethernet segment and
|
||||
all clients must be connected to this same segment. There must not be a router
|
||||
in between. Also note that the client must not run on the streamdev-server
|
||||
machine.
|
||||
|
||||
Each channel is offered on a different multicast IP. Channel 1 is available from
|
||||
multicast IP 239.255.0.1, channel 2 from 239.255.0.2 and so on. The upper limit
|
||||
is 239.255.254.255 which corresponds to channel 65279 (239.255.255.0/24 is
|
||||
reserved according to RFC-2365).
|
||||
|
||||
Before you can use streamdev's multicast server, you might need to patch VDR.
|
||||
Binding an IGMP socket is a privileged operation, so you must start VDR as root.
|
||||
|
||||
The multicast server is disabled by default. Enter the streamdev-server setup
|
||||
menu to enable it and - IMPORTANT - bind the multicast server to the IP of your
|
||||
VDR server's LAN ethernet card. The multicast server will refuse to start with
|
||||
the default bind address "0.0.0.0".
|
||||
|
||||
Now edit your streamdevhosts.conf. To allow streaming of all channels, it must
|
||||
contain "239.255.0.0/16". Note that you cannot limit connections by client IP
|
||||
here. You can however restrict which channels are allowed to be multicasted.
|
||||
Enter individual multicast IPs instead of "239.255.0.0/16".
|
||||
|
||||
By default, the linux kernel will refuse to join more than 20 multicast groups.
|
||||
You might want to increase this up to "number_of_channels + 1". Note that it's
|
||||
"number_of_channels", not "maximum_channel_number".
|
||||
|
||||
#First 100 channels:
|
||||
bash# sysctl -w net.ipv4.igmp_max_memberships=101
|
||||
|
||||
#All channels:
|
||||
bash# COUNT=$(grep -c '^[^:]' PATH_TO_YOUR/channels.conf)
|
||||
bash# sysctl -w net.ipv4.igmp_max_memberships=$((COUNT + 1))
|
||||
|
||||
You need to run the sysctl command *before* VDR is started. The setting is lost
|
||||
after the next reboot. Check the documentation of your Linux distro on how to
|
||||
make the setting persist (i.e. have your distro change the value for you as
|
||||
part of the boot procedure). Most likely /etc/sysctl.conf is your friend.
|
||||
|
||||
A multicast server never knows how many clients are actually receiving a stream.
|
||||
If a client signals that it leaves a multicast group, the server has to query
|
||||
for other listeners before it can stop the stream. This may delay zapping from
|
||||
one transponder to an other. The client will probably requests the new channel
|
||||
before the previous stream has been stopped. If there's no free DVB card, VDR
|
||||
won't be able to fulfill the request until a DVB card becomes available and the
|
||||
client resends the request.
|
||||
|
||||
3.3 Usage VDR-to-VDR server:
|
||||
----------------------------
|
||||
|
||||
You can activate the VDR-to-VDR server part in the PlugIn's Setup Menu. It is
|
||||
@@ -213,7 +363,14 @@ 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:
|
||||
If both, streamdev-client and streamdev-server are installed, the additional
|
||||
option "Loop prevention" will show up in the streamdev-server setup. If enabled,
|
||||
streamdev-client won't be considered when streamdev-server is looking for a
|
||||
device which is able to receive some channel. This is required if two or more
|
||||
VDRs mutually share their DVB devices through streamdev. Otherwise you would
|
||||
end up in a loop.
|
||||
|
||||
3.4 Usage VDR-to-VDR client:
|
||||
----------------------------
|
||||
|
||||
Streamdev-client adds a "Suspend Server" item to VDR's mainmenu. With the
|
||||
@@ -221,9 +378,9 @@ setup parameter "Hide Mainmenu Entry" you can hide this menu item if you don't
|
||||
need it. "Suspend Server" is only useful if the server runs in "Offer suspend
|
||||
mode" with "Client may suspend" enabled.
|
||||
|
||||
The parameter "Remote IP" uses an IP-Adress-Editor, where you can just enter
|
||||
The parameter "Remote IP" uses an IP-Address-Editor, where you can just enter
|
||||
the IP number with the number keys on your remote. After three digits (or if
|
||||
the next digit would result in an invalid IP adress, or if the first digit is
|
||||
the next digit would result in an invalid IP address, or if the first digit is
|
||||
0), the current position jumps to the next one. You can change positions with
|
||||
the left and right buttons, and you can cycle the current position using up
|
||||
and down. To confirm the entered address, press OK. So, if you want to enter
|
||||
@@ -232,42 +389,91 @@ type "127001<OK>" on your remote. If you want to enter "192.168.1.12", type
|
||||
"1921681<Right>12<OK>".
|
||||
|
||||
The parameters "Remote IP" and "Remote Port" in the client's setup specify the
|
||||
address of the remote VDR-to-VDR server to connect to. Activate the client by
|
||||
setting "Start Client" to yes. It is disabled by default, because it wouldn't
|
||||
make much sense to start the client without specifying a server anyway. The
|
||||
client is activated after you push the OK button, so there's no need to restart
|
||||
VDR. Deactivation on-the-fly is not possible, so in order to deactivate the
|
||||
client, you will have to restart VDR. However requests to switch channels will
|
||||
be refused by streamdev-client once it has been deactivated. All other settings
|
||||
can be changed without restarting VDR.
|
||||
|
||||
The client will try to connect to the server (in case it isn't yet) whenever
|
||||
a remote channel is requested. Just activate the client and switch to a
|
||||
channel that's not available by local devices. If anything goes wrong with the
|
||||
connection between the two, you will see it in the logfile instantly. If you
|
||||
now switch the client to a channel which isn't covered by it's own local
|
||||
devices, it will ask the server for it. If the server can (currently) receive
|
||||
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.
|
||||
address of the remote VDR-to-VDR server to connect to. The client is disabled
|
||||
by default, because it wouldn't make much sense to start the client without
|
||||
specifying a server anyway. Activate the client by setting "Simultaneously used
|
||||
Devices" to at least 1. Streamdev-client will allocate as many VDR devices as
|
||||
you configure here. Each of these devices opens one connection to the server
|
||||
and becomes associated with one of the server's devices (typically a DVB card)
|
||||
on demand.
|
||||
|
||||
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).
|
||||
additional channels on the same transponder and use plugins that use receivers
|
||||
themselves (like osdteletext).
|
||||
|
||||
So for viewing live TV a single device is sufficient. But if the client needs
|
||||
to receive channels from different transponders simultaneously (e.g. for PiP or
|
||||
client side recordings) a second device becomes necessary.
|
||||
|
||||
To allocate additional devices, just increase the number and push the OK button.
|
||||
There's no need to restart VDR. Deleting VDR devices on-the-fly is not possible.
|
||||
However requests to switch channels will be refused by redundant devices.
|
||||
|
||||
The default timeout of 2 seconds for network operations should be sufficient in
|
||||
most cases. Increase "Timeout" if you get frequent timeout errors in the log.
|
||||
|
||||
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. 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).
|
||||
If you have TV programs with dynamically changing PIDs (such as some german
|
||||
regional programs like NDR), then you need to enable "Filter Streaming" to
|
||||
correctly receive them. You also need to set in VDRs DVB setup the option
|
||||
"Update channels" to at least "PIDs only" (or "names and PIDs") for this
|
||||
to work.
|
||||
|
||||
"Filter streaming" uses internally a socketpair(2) to copy meta data to
|
||||
VDR. This socketpair may require larger than default buffering. If
|
||||
you see a mesage like the following in syslog,
|
||||
|
||||
cStreamdevFilter::PutSection(Pid:18 Tid: 64): Dropped 2995 bytes, max queue: 328640
|
||||
|
||||
then you should increase the streamdev client "FilterSockBufSize" value. A
|
||||
good value is 3072000. You will need to first configure your linux to
|
||||
permit such a large buffer size:
|
||||
|
||||
sysctl net.core.wmem_max=3072000
|
||||
|
||||
The precedence among multiple client VDRs receiving live TV from the same
|
||||
server is controlled with "Live TV Priority".
|
||||
|
||||
With "Maximum Priority" and "Minimum Priority" you can keep VDR from considering
|
||||
streamdev in certain cases. If for instance you have a streamdev client with its
|
||||
own DVB card, VDR might use streamdev for recording. If this is not what you
|
||||
want, you could set the maximum priority to 0. As recordings usually have a much
|
||||
higher priority (default 50), streamdev is now no longer used for recordings.
|
||||
The two parameters define the inclusive range of priorities for which streamdev
|
||||
will accept to tune. Setting the minimum priority to a higher value than the
|
||||
maximum, you will get two ranges: "up to maximum" and "minimum and above".
|
||||
|
||||
You can also configure the "Broadcast Systems / Cost" of the streamdev-client
|
||||
device. On a pure streamdev-client only system it doesn't matter what you
|
||||
configure here. But if your client is equipped with a DVB card, you should read
|
||||
on. VDR always prefers the cheapest device in terms of supported broadcast
|
||||
systems and modulations. A DVB-S2 card supports two broadcast systems (DVB-S and
|
||||
DVB-S2). The supported modulations are counted as well (QPSK, QAM32/64/128/256,
|
||||
VSB8/16, TURBO_FEC). So for a DVB-S2 card which does QPSK you'll get a total
|
||||
cost of three. A DVB-C card (one broadcast system) which can do QAM32, QAM64,
|
||||
QAM128, QAM256 would give you a total of five. Check your log for "frontend ...
|
||||
provides ... with ..." messages to find out the cost of your DVB cards. Then
|
||||
pick a suitable value for streamdev-client. With equal costs, VDR will usually
|
||||
prefer the DVB card and take streamdev for recordings. If streamdev's costs are
|
||||
higher, live TV will use your DVB card until a recordings kicks in. Then the
|
||||
recording will take the DVB card and live TV will be shifted to streamdev
|
||||
(you'll notice a short interruption of live TV).
|
||||
|
||||
To receive channels from multiple servers, create additional instances of the
|
||||
streamdev-client plugin. Simply copy (don't link!) the binary to a different
|
||||
name (e.g. streamdev-client2):
|
||||
|
||||
cd VDRPLUGINLIBDIR
|
||||
cp libvdr-streamdev-client.so.1.X.X libvdr-streamdev-client2.so.1.X.X
|
||||
|
||||
Now add -Pstreamdev-client2 to the VDR commandline. In the VDR plugin setup
|
||||
a second streamdev-client entry should show up. Both instances have to be
|
||||
configured individually.
|
||||
|
||||
|
||||
4. Other useful Plugins:
|
||||
------------------------
|
||||
@@ -311,11 +517,76 @@ 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. externremux.sh:
|
||||
------------------
|
||||
|
||||
* 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.
|
||||
When selecting streamtype "EXT", the TS stream from VDR is passed through an
|
||||
external program for further processing. By default a script installed at
|
||||
VDRCONFDIR/plugins/streamdev/externremux.sh is expected, however you may
|
||||
specify a different location as parameter -r to the streamdev-server plugin
|
||||
(see chapter upon Installation above).
|
||||
|
||||
The TS stream is passed to the script on stdin, the resulting stream is expected
|
||||
on stdout. The following parameters are passed to the script in the environment:
|
||||
|
||||
* Information on the channel:
|
||||
REMUX_CHANNEL_ID VDR channel ID
|
||||
REMUX_CHANNEL_NAME Channel name
|
||||
REMUX_VTYPE Video type (2 for MPEG-2)
|
||||
REMUX_VPID Video PID (undefined if audio only)
|
||||
REMUX_PPID PCR PID (undefined if equal to VPID)
|
||||
REMUX_TPID Teletext PID (undefined if not available)
|
||||
REMUX_APID Space separated list of audio pids
|
||||
REMUX_ALANG Space separated list of audio languages
|
||||
REMUX_DPID Space separated list of dolby pids
|
||||
REMUX_DLANG Space separated list of dolby languages
|
||||
REMUX_SPID Space separated list of subtitle pids
|
||||
REMUX_SLANG Space separated list of subtitle languages
|
||||
REMUX_PARAM_* All (user supplied) parameters (e.g. REMUX_PARAM_x)
|
||||
|
||||
* Information on the connection (CGI like)
|
||||
REMOTE_ADDR Client IP
|
||||
SERVER_NAME Local IP
|
||||
SERVER_PORT Local port
|
||||
SERVER_PROTOCOL Streamdev protocol (HTTP, VTP, IGMP)
|
||||
SERVER_SOFTWARE Streamdev version
|
||||
All HTTP headers converted to uppercase, '-' replaced by '_' (e.g. USER_AGENT)
|
||||
|
||||
The script should perform the following steps (pseudocode):
|
||||
|
||||
if (SERVER_PROTOCOL == HTTP)
|
||||
write headers (including Content-Type) to STDOUT
|
||||
write empty line to STDOUT
|
||||
if (REQUEST_METHOD == HEAD)
|
||||
exit
|
||||
endif
|
||||
endif
|
||||
while (read STDIN)
|
||||
remux to STDOUT
|
||||
wend
|
||||
|
||||
onSIGINT/SIGKILL: cleanup and exit
|
||||
|
||||
|
||||
6. Known Problems:
|
||||
------------------
|
||||
|
||||
* In VDR before 1.7.30 viewing encrypted channels is an issue as Streamdev
|
||||
doesn't provide a (dummy) CAM. So out of the box, VDR won't ever try to receive
|
||||
encrypted channels from streamdev. Pick one of the following solutions to work
|
||||
around the problem:
|
||||
|
||||
1. Force VDR to use streamdev. Open the channels menu on the client (or edit its
|
||||
channels.conf if you know how to do this) and set the CA field of all channels
|
||||
that only the server can decrypt to streamdev's device index. Usually streamdev
|
||||
will get number 9 or 10. Streamdev logs the actual device number when starting
|
||||
up. So please consider the logs for the correct value. Remember to fill in
|
||||
hexadecimal values if you are using an editor to modify your channels.conf
|
||||
(number 10 becomes an "a", number 11 a "b", ...).
|
||||
|
||||
2. Apply either patch "patches/vdr-1.6.0-1.7.29-intcamdevices.patch" or patch
|
||||
"patches/vdr-1.6.0-1.7.29-ignore_missing_cam.diff" to your client VDR.
|
||||
Intcamdevices is the clean solution, but it modifies the VDR API. So you will
|
||||
need to recompile all of your plugins. The ignore_missing_cam patch is trivial,
|
||||
no need to recompile other plugins. However it is not suitable for clients with
|
||||
a DVB card of their own.
|
||||
|
||||
83
client/Makefile
Normal file
83
client/Makefile
Normal file
@@ -0,0 +1,83 @@
|
||||
#
|
||||
# Makefile for a Video Disk Recorder plugin
|
||||
#
|
||||
# $Id: $
|
||||
|
||||
# The official name of this plugin.
|
||||
# This name will be used in the '-P...' option of VDR to load the plugin.
|
||||
# By default the main source file also carries this name.
|
||||
|
||||
PLUGIN = streamdev-client
|
||||
|
||||
### The name of the shared object file:
|
||||
|
||||
SOFILE = libvdr-$(PLUGIN).so
|
||||
|
||||
### Includes and Defines (add further entries here):
|
||||
|
||||
DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
|
||||
|
||||
### The object files (add further files here):
|
||||
|
||||
COMMONOBJS = ../common.o
|
||||
|
||||
CLIENTOBJS = $(PLUGIN).o \
|
||||
device.o filter.o setup.o socket.o
|
||||
|
||||
### The main target:
|
||||
|
||||
all: $(SOFILE) i18n
|
||||
|
||||
### Implicit rules:
|
||||
|
||||
%.o: %.c
|
||||
$(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $<
|
||||
|
||||
### Dependencies:
|
||||
|
||||
MAKEDEP = $(CXX) -MM -MG
|
||||
DEPFILE = .dependencies
|
||||
$(DEPFILE): Makefile
|
||||
@$(MAKEDEP) $(CXXFLAGS) $(DEFINES) $(INCLUDES) $(CLIENTOBJS:%.o=%.c) $(COMMONOBJS:%.o=%.c) > $@
|
||||
|
||||
-include $(DEPFILE)
|
||||
|
||||
### Internationalization (I18N):
|
||||
|
||||
PODIR = po
|
||||
I18Npo = $(wildcard $(PODIR)/*.po)
|
||||
I18Nmo = $(addsuffix .mo, $(foreach file, $(I18Npo), $(basename $(file))))
|
||||
I18Nmsgs = $(addprefix $(DESTDIR)$(LOCDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
|
||||
I18Npot = $(PODIR)/$(PLUGIN).pot
|
||||
|
||||
%.mo: %.po
|
||||
msgfmt -c -o $@ $<
|
||||
|
||||
$(I18Npot): $(CLIENTOBJS:%.o=%.c)
|
||||
xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name=vdr-$(PLUGIN) --package-version=$(VERSION) --msgid-bugs-address='<vdrdev@schmirler.de>' -o $@ `ls $^`
|
||||
|
||||
%.po: $(I18Npot)
|
||||
msgmerge -U --no-wrap --no-location --backup=none -q -N $@ $<
|
||||
@touch $@
|
||||
|
||||
$(I18Nmsgs): $(DESTDIR)$(LOCDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
|
||||
install -D -m644 $< $@
|
||||
|
||||
.PHONY: i18n
|
||||
i18n: $(I18Nmo) $(I18Npot)
|
||||
|
||||
install-i18n: $(I18Nmsgs)
|
||||
|
||||
### Targets:
|
||||
|
||||
$(SOFILE): $(CLIENTOBJS) $(COMMONOBJS) ../tools/sockettools.a
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $^ -o $@
|
||||
|
||||
install-lib: $(SOFILE)
|
||||
install -D $^ $(DESTDIR)$(LIBDIR)/$^.$(APIVERSION)
|
||||
|
||||
install: install-lib install-i18n
|
||||
|
||||
clean:
|
||||
@-rm -f $(PODIR)/*.mo $(PODIR)/*.pot
|
||||
@-rm -f $(COMMONOBJS) $(CLIENTOBJS) $(DEPFILE) *.so *.tgz core* *~
|
||||
84
client/Makefile-1.7.33
Normal file
84
client/Makefile-1.7.33
Normal file
@@ -0,0 +1,84 @@
|
||||
#
|
||||
# Makefile for a Video Disk Recorder plugin
|
||||
#
|
||||
# $Id: Makefile,v 1.2 2010/07/19 13:49:25 schmirl Exp $
|
||||
|
||||
# The official name of this plugin.
|
||||
# This name will be used in the '-P...' option of VDR to load the plugin.
|
||||
# By default the main source file also carries this name.
|
||||
#
|
||||
PLUGIN = streamdev-client
|
||||
|
||||
### Includes and Defines (add further entries here):
|
||||
|
||||
DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
|
||||
|
||||
### The object files (add further files here):
|
||||
|
||||
COMMONOBJS = ../common.o
|
||||
|
||||
CLIENTOBJS = $(PLUGIN).o \
|
||||
device.o filter.o setup.o socket.o
|
||||
|
||||
### The main target:
|
||||
|
||||
.PHONY: all i18n dist clean
|
||||
all: libvdr-$(PLUGIN).so i18n
|
||||
|
||||
### Implicit rules:
|
||||
|
||||
%.o: %.c
|
||||
$(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $<
|
||||
|
||||
### Dependencies:
|
||||
|
||||
MAKEDEP = $(CXX) -MM -MG
|
||||
DEPFILE = .dependencies
|
||||
|
||||
$(DEPFILE): Makefile
|
||||
@$(MAKEDEP) $(DEFINES) $(INCLUDES) $(CLIENTOBJS:%.o=%.c) $(COMMONOBJS:%.o=%.c) > $@
|
||||
|
||||
-include $(DEPFILE)
|
||||
|
||||
### Internationalization (I18N):
|
||||
|
||||
PODIR = po
|
||||
LOCALEDIR = $(VDRDIR)/locale
|
||||
I18Npo = $(wildcard $(PODIR)/*.po)
|
||||
I18Nmsgs = $(addprefix $(LOCALEDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
|
||||
I18Npot = $(PODIR)/$(PLUGIN).pot
|
||||
|
||||
%.mo: %.po
|
||||
msgfmt -c -o $@ $<
|
||||
|
||||
$(I18Npot): $(CLIENTOBJS:%.o=%.c)
|
||||
xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --msgid-bugs-address='<http://www.vdr-developer.org/mantisbt/>' -o $@ $^
|
||||
|
||||
%.po: $(I18Npot)
|
||||
msgmerge -U --no-wrap --no-location --backup=none -q $@ $<
|
||||
@touch $@
|
||||
|
||||
$(I18Nmsgs): $(LOCALEDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
|
||||
@mkdir -p $(dir $@)
|
||||
cp $< $@
|
||||
|
||||
i18n: $(I18Nmsgs)
|
||||
|
||||
### Targets:
|
||||
|
||||
libvdr-$(PLUGIN).so: $(CLIENTOBJS) $(COMMONOBJS) ../tools/sockettools.a
|
||||
|
||||
%.so:
|
||||
$(CXX) $(CXXFLAGS) -shared $^ -o $@
|
||||
@cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION)
|
||||
|
||||
dist: clean
|
||||
@-rm -rf $(TMPDIR)/$(ARCHIVE)
|
||||
@mkdir $(TMPDIR)/$(ARCHIVE)
|
||||
@cp -a * $(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) $(DEPFILE) $(PODIR)/*.mo $(PODIR)/*.pot *.so *.tgz core* *~
|
||||
322
client/device.c
322
client/device.c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: device.c,v 1.18 2008/04/07 14:50:32 schmirl Exp $
|
||||
* $Id: device.c,v 1.27 2010/08/18 10:26:55 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include "client/device.h"
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "tools/select.h"
|
||||
|
||||
#include <vdr/config.h>
|
||||
#include <vdr/channels.h>
|
||||
#include <vdr/ringbuffer.h>
|
||||
#include <vdr/eit.h>
|
||||
@@ -18,42 +19,53 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
#ifndef LIVEPRIORITY
|
||||
#define LIVEPRIORITY 0
|
||||
#endif
|
||||
|
||||
#ifndef TRANSFERPRIORITY
|
||||
#define TRANSFERPRIORITY -1
|
||||
#endif
|
||||
|
||||
#define VIDEOBUFSIZE MEGABYTE(3)
|
||||
|
||||
cStreamdevDevice *cStreamdevDevice::m_Device = NULL;
|
||||
const cChannel *cStreamdevDevice::m_DenyChannel = NULL;
|
||||
|
||||
cStreamdevDevice::cStreamdevDevice(void) {
|
||||
m_Channel = NULL;
|
||||
m_TSBuffer = NULL;
|
||||
m_Disabled = false;
|
||||
m_ClientSocket = new cClientSocket();
|
||||
m_Channel = NULL;
|
||||
m_TSBuffer = NULL;
|
||||
|
||||
m_Filters = new cStreamdevFilters;
|
||||
m_Filters = new cStreamdevFilters(m_ClientSocket);
|
||||
StartSectionHandler();
|
||||
isyslog("streamdev-client: got device number %d", CardIndex() + 1);
|
||||
|
||||
m_Device = this;
|
||||
m_Pids = 0;
|
||||
m_DvrClosed = true;
|
||||
|
||||
if (StreamdevClientSetup.SyncEPG)
|
||||
ClientSocket.SynchronizeEPG();
|
||||
}
|
||||
|
||||
cStreamdevDevice::~cStreamdevDevice() {
|
||||
Dprintf("Device gets destructed\n");
|
||||
|
||||
Lock();
|
||||
m_Device = NULL;
|
||||
m_Filters->SetConnection(-1);
|
||||
ClientSocket.Quit();
|
||||
ClientSocket.Reset();
|
||||
m_ClientSocket->Quit();
|
||||
m_ClientSocket->Reset();
|
||||
Unlock();
|
||||
|
||||
Cancel(3);
|
||||
|
||||
StopSectionHandler();
|
||||
DELETENULL(m_Filters);
|
||||
DELETENULL(m_TSBuffer);
|
||||
delete m_ClientSocket;
|
||||
}
|
||||
|
||||
#if APIVERSNUM >= 10700
|
||||
int cStreamdevDevice::NumProvidedSystems(void) const
|
||||
{ return StreamdevClientSetup.NumProvidedSystems; }
|
||||
#endif
|
||||
|
||||
bool cStreamdevDevice::ProvidesSource(int Source) const {
|
||||
Dprintf("ProvidesSource, Source=%d\n", Source);
|
||||
return true;
|
||||
@@ -65,32 +77,78 @@ bool cStreamdevDevice::ProvidesTransponder(const cChannel *Channel) const
|
||||
return true;
|
||||
}
|
||||
|
||||
#if APIVERSNUM >= 10722
|
||||
bool cStreamdevDevice::IsTunedToTransponder(const cChannel *Channel) const
|
||||
#else
|
||||
bool cStreamdevDevice::IsTunedToTransponder(const cChannel *Channel)
|
||||
#endif
|
||||
{
|
||||
bool res = false;
|
||||
if (ClientSocket.DataSocket(siLive) != NULL
|
||||
&& TRANSPONDER(Channel, m_Channel))
|
||||
res = true;
|
||||
return res;
|
||||
return m_ClientSocket->DataSocket(siLive) != NULL &&
|
||||
m_Channel != NULL &&
|
||||
Channel->Transponder() == m_Channel->Transponder();
|
||||
}
|
||||
|
||||
const cChannel *cStreamdevDevice::GetCurrentlyTunedTransponder(void) const {
|
||||
if (m_ClientSocket->DataSocket(siLive) != NULL)
|
||||
return m_Channel;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool cStreamdevDevice::ProvidesChannel(const cChannel *Channel, int Priority,
|
||||
bool *NeedsDetachReceivers) const {
|
||||
bool res = false;
|
||||
bool prio = Priority < 0 || Priority > this->Priority();
|
||||
bool ndr = false;
|
||||
|
||||
if (!StreamdevClientSetup.StartClient)
|
||||
if (m_Disabled || Channel == m_DenyChannel)
|
||||
return false;
|
||||
|
||||
Dprintf("ProvidesChannel, Channel=%s, Prio=%d\n", Channel->Name(), Priority);
|
||||
Dprintf("ProvidesChannel, Channel=%s, Priority=%d, SocketPrio=%d\n", Channel->Name(), Priority, m_ClientSocket->Priority());
|
||||
|
||||
if (ClientSocket.DataSocket(siLive) != NULL
|
||||
&& TRANSPONDER(Channel, m_Channel))
|
||||
res = true;
|
||||
else {
|
||||
res = prio && ClientSocket.ProvidesChannel(Channel, Priority);
|
||||
ndr = true;
|
||||
if (StreamdevClientSetup.MinPriority <= StreamdevClientSetup.MaxPriority)
|
||||
{
|
||||
if (Priority < StreamdevClientSetup.MinPriority ||
|
||||
Priority > StreamdevClientSetup.MaxPriority)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Priority < StreamdevClientSetup.MinPriority &&
|
||||
Priority > StreamdevClientSetup.MaxPriority)
|
||||
return false;
|
||||
}
|
||||
|
||||
int newPrio = Priority;
|
||||
if (Priority == LIVEPRIORITY) {
|
||||
if (m_ClientSocket->ServerVersion() >= 100 || StreamdevClientSetup.LivePriority >= 0)
|
||||
newPrio = StreamdevClientSetup.LivePriority;
|
||||
}
|
||||
|
||||
#if APIVERSNUM >= 10725
|
||||
bool prio = Priority == IDLEPRIORITY || newPrio >= m_ClientSocket->Priority();
|
||||
#else
|
||||
bool prio = Priority < 0 || newPrio > m_ClientSocket->Priority();
|
||||
#endif
|
||||
bool res = prio;
|
||||
bool ndr = false;
|
||||
|
||||
#if APIVERSNUM >= 10722
|
||||
if (IsTunedToTransponder(Channel)) {
|
||||
#else
|
||||
if (const_cast<cStreamdevDevice*>(this)->IsTunedToTransponder(Channel)) {
|
||||
#endif
|
||||
if (Channel->Ca() < CA_ENCRYPTED_MIN ||
|
||||
(Channel->Vpid() && HasPid(Channel->Vpid())) ||
|
||||
(Channel->Apid(0) && HasPid(Channel->Apid(0))))
|
||||
res = true;
|
||||
else
|
||||
ndr = true;
|
||||
}
|
||||
else if (prio) {
|
||||
if (Priority == LIVEPRIORITY && m_ClientSocket->ServerVersion() >= 100)
|
||||
UpdatePriority(true);
|
||||
|
||||
res = m_ClientSocket->ProvidesChannel(Channel, newPrio);
|
||||
ndr = Receiving();
|
||||
|
||||
if (m_ClientSocket->ServerVersion() >= 100)
|
||||
UpdatePriority(false);
|
||||
}
|
||||
|
||||
if (NeedsDetachReceivers)
|
||||
@@ -101,137 +159,93 @@ bool cStreamdevDevice::ProvidesChannel(const cChannel *Channel, int Priority,
|
||||
|
||||
bool cStreamdevDevice::SetChannelDevice(const cChannel *Channel,
|
||||
bool LiveView) {
|
||||
bool res;
|
||||
Dprintf("SetChannelDevice Channel: %s, LiveView: %s\n", Channel->Name(),
|
||||
LiveView ? "true" : "false");
|
||||
LOCK_THREAD;
|
||||
|
||||
if (LiveView)
|
||||
return false;
|
||||
|
||||
if (ClientSocket.DataSocket(siLive) != NULL
|
||||
&& TRANSPONDER(Channel, m_Channel))
|
||||
return true;
|
||||
|
||||
DetachAllReceivers();
|
||||
m_Channel = Channel;
|
||||
bool r = ClientSocket.SetChannelDevice(m_Channel);
|
||||
Dprintf("setchanneldevice r=%d\n", r);
|
||||
return r;
|
||||
if (Receiving() && IsTunedToTransponder(Channel) && (
|
||||
Channel->Ca() < CA_ENCRYPTED_MIN ||
|
||||
(Channel->Vpid() && HasPid(Channel->Vpid())) ||
|
||||
(Channel->Apid(0) && HasPid(Channel->Apid(0))))) {
|
||||
res = true;
|
||||
}
|
||||
else {
|
||||
DetachAllReceivers();
|
||||
m_Channel = Channel;
|
||||
// Old servers delete cStreamdevLiveStreamer in ABRT.
|
||||
// Delete it now or it will happen after we tuned to new channel
|
||||
if (m_ClientSocket->ServerVersion() < 100)
|
||||
CloseDvr();
|
||||
res = m_ClientSocket->SetChannelDevice(m_Channel);
|
||||
}
|
||||
Dprintf("setchanneldevice res=%d\n", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool cStreamdevDevice::SetPid(cPidHandle *Handle, int Type, bool On) {
|
||||
Dprintf("SetPid, Pid=%d, Type=%d, On=%d, used=%d\n", Handle->pid, Type, On,
|
||||
Handle->used);
|
||||
Dprintf("SetPid, Pid=%d, Type=%d, On=%d, used=%d\n", Handle->pid, Type, On, Handle->used);
|
||||
LOCK_THREAD;
|
||||
|
||||
if (On && !m_TSBuffer) {
|
||||
Dprintf("SetPid: no data connection -> OpenDvr()");
|
||||
OpenDvrInt();
|
||||
}
|
||||
|
||||
bool res = true;
|
||||
if (Handle->pid && (On || !Handle->used)) {
|
||||
res = ClientSocket.SetPid(Handle->pid, On);
|
||||
res = m_ClientSocket->SetPid(Handle->pid, On);
|
||||
|
||||
m_Pids += (!res) ? 0 : On ? 1 : -1;
|
||||
if (m_Pids < 0)
|
||||
m_Pids = 0;
|
||||
|
||||
if(m_Pids < 1 && m_DvrClosed) {
|
||||
Dprintf("SetPid: 0 pids left -> CloseDvr()");
|
||||
CloseDvrInt();
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool cStreamdevDevice::OpenDvrInt(void) {
|
||||
Dprintf("OpenDvrInt\n");
|
||||
LOCK_THREAD;
|
||||
|
||||
CloseDvrInt();
|
||||
if (m_TSBuffer) {
|
||||
Dprintf("cStreamdevDevice::OpenDvrInt(): DVR connection already open\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
Dprintf("cStreamdevDevice::OpenDvrInt(): Connecting ...\n");
|
||||
if (ClientSocket.CreateDataConnection(siLive)) {
|
||||
m_TSBuffer = new cTSBuffer(*ClientSocket.DataSocket(siLive), MEGABYTE(2), CardIndex() + 1);
|
||||
return true;
|
||||
}
|
||||
esyslog("cStreamdevDevice::OpenDvrInt(): DVR connection FAILED");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool cStreamdevDevice::OpenDvr(void) {
|
||||
Dprintf("OpenDvr\n");
|
||||
LOCK_THREAD;
|
||||
|
||||
m_DvrClosed = false;
|
||||
return OpenDvrInt();
|
||||
}
|
||||
|
||||
void cStreamdevDevice::CloseDvrInt(void) {
|
||||
Dprintf("CloseDvrInt\n");
|
||||
LOCK_THREAD;
|
||||
|
||||
if (ClientSocket.CheckConnection()) {
|
||||
if (!m_DvrClosed) {
|
||||
Dprintf("cStreamdevDevice::CloseDvrInt(): m_DvrClosed=false -> not closing yet\n");
|
||||
return;
|
||||
}
|
||||
if (m_Pids > 0) {
|
||||
Dprintf("cStreamdevDevice::CloseDvrInt(): %d active pids -> not closing yet\n", m_Pids);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Dprintf("cStreamdevDevice::CloseDvrInt(): Control connection gone !\n");
|
||||
CloseDvr();
|
||||
if (m_ClientSocket->CreateDataConnection(siLive)) {
|
||||
m_TSBuffer = new cTSBuffer(*m_ClientSocket->DataSocket(siLive), MEGABYTE(2), CardIndex() + 1);
|
||||
}
|
||||
|
||||
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
|
||||
else {
|
||||
esyslog("cStreamdevDevice::OpenDvr(): DVR connection FAILED");
|
||||
}
|
||||
return m_TSBuffer != NULL;
|
||||
}
|
||||
|
||||
|
||||
void cStreamdevDevice::CloseDvr(void) {
|
||||
Dprintf("CloseDvr\n");
|
||||
LOCK_THREAD;
|
||||
|
||||
m_DvrClosed = true;
|
||||
CloseDvrInt();
|
||||
m_ClientSocket->CloseDvr();
|
||||
DELETENULL(m_TSBuffer);
|
||||
}
|
||||
|
||||
bool cStreamdevDevice::GetTSPacket(uchar *&Data) {
|
||||
if (m_TSBuffer && m_Device) {
|
||||
if (m_TSBuffer) {
|
||||
Data = m_TSBuffer->Get();
|
||||
#if 1 // TODO: this should be fixed in vdr cTSBuffer
|
||||
// simple disconnect detection
|
||||
static int m_TSFails = 0;
|
||||
if (!Data) {
|
||||
LOCK_THREAD;
|
||||
if(!ClientSocket.DataSocket(siLive)) {
|
||||
if(!m_ClientSocket->DataSocket(siLive)) {
|
||||
return false; // triggers CloseDvr() + OpenDvr() in cDevice
|
||||
}
|
||||
cPoller Poller(*ClientSocket.DataSocket(siLive));
|
||||
cPoller Poller(*m_ClientSocket->DataSocket(siLive));
|
||||
errno = 0;
|
||||
if (Poller.Poll() && !errno) {
|
||||
char tmp[1];
|
||||
if (recv(*ClientSocket.DataSocket(siLive), tmp, 1, MSG_PEEK) == 0 && !errno) {
|
||||
if (recv(*m_ClientSocket->DataSocket(siLive), tmp, 1, MSG_PEEK) == 0 && !errno) {
|
||||
esyslog("cStreamDevice::GetTSPacket: GetChecked: NOTHING (%d)", m_TSFails);
|
||||
m_TSFails++;
|
||||
if (m_TSFails > 10) {
|
||||
isyslog("cStreamdevDevice::GetTSPacket(): disconnected");
|
||||
m_Pids = 0;
|
||||
CloseDvrInt();
|
||||
CloseDvr();
|
||||
m_TSFails = 0;
|
||||
return false;
|
||||
}
|
||||
@@ -253,39 +267,89 @@ int cStreamdevDevice::OpenFilter(u_short Pid, u_char Tid, u_char Mask) {
|
||||
return -1;
|
||||
|
||||
|
||||
if (!ClientSocket.DataSocket(siLiveFilter)) {
|
||||
if (ClientSocket.CreateDataConnection(siLiveFilter)) {
|
||||
m_Filters->SetConnection(*ClientSocket.DataSocket(siLiveFilter));
|
||||
if (!m_ClientSocket->DataSocket(siLiveFilter)) {
|
||||
if (m_ClientSocket->CreateDataConnection(siLiveFilter)) {
|
||||
m_Filters->SetConnection(*m_ClientSocket->DataSocket(siLiveFilter));
|
||||
} else {
|
||||
isyslog("cStreamdevDevice::OpenFilter: connect failed: %m");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (ClientSocket.SetFilter(Pid, Tid, Mask, true))
|
||||
if (m_ClientSocket->SetFilter(Pid, Tid, Mask, true))
|
||||
return m_Filters->OpenFilter(Pid, Tid, Mask);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool cStreamdevDevice::Init(void) {
|
||||
if (m_Device == NULL && StreamdevClientSetup.StartClient)
|
||||
new cStreamdevDevice;
|
||||
void cStreamdevDevice::CloseFilter(int Handle) {
|
||||
|
||||
if(m_Filters)
|
||||
m_Filters->CloseFilter(Handle);
|
||||
else
|
||||
esyslog("cStreamdevDevice::CloseFilter called while m_Filters is null");
|
||||
}
|
||||
|
||||
bool cStreamdevDevice::ReInit(bool Disable) {
|
||||
LOCK_THREAD;
|
||||
m_Disabled = Disable;
|
||||
m_Filters->SetConnection(-1);
|
||||
m_Pids = 0;
|
||||
m_ClientSocket->Quit();
|
||||
m_ClientSocket->Reset();
|
||||
//DELETENULL(m_TSBuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cStreamdevDevice::ReInit(void) {
|
||||
if(m_Device) {
|
||||
m_Device->Lock();
|
||||
m_Device->m_Filters->SetConnection(-1);
|
||||
m_Device->m_Pids = 0;
|
||||
void cStreamdevDevice::UpdatePriority(bool SwitchingChannels) const {
|
||||
if (!m_Disabled) {
|
||||
//LOCK_THREAD;
|
||||
const_cast<cStreamdevDevice*>(this)->Lock();
|
||||
if (m_ClientSocket->SupportsPrio() && m_ClientSocket->DataSocket(siLive)) {
|
||||
int Priority = this->Priority();
|
||||
// override TRANSFERPRIORITY (-1) with live TV priority from setup
|
||||
if (Priority == TRANSFERPRIORITY && this == cDevice::ActualDevice()) {
|
||||
Priority = StreamdevClientSetup.LivePriority;
|
||||
// temporarily lower priority
|
||||
if (SwitchingChannels)
|
||||
Priority--;
|
||||
if (Priority < 0 && m_ClientSocket->ServerVersion() < 100)
|
||||
Priority = 0;
|
||||
}
|
||||
m_ClientSocket->SetPriority(Priority);
|
||||
}
|
||||
const_cast<cStreamdevDevice*>(this)->Unlock();
|
||||
}
|
||||
ClientSocket.Quit();
|
||||
ClientSocket.Reset();
|
||||
if (m_Device != NULL) {
|
||||
//DELETENULL(m_Device->m_TSBuffer);
|
||||
m_Device->Unlock();
|
||||
}
|
||||
return StreamdevClientSetup.StartClient ? Init() : true;
|
||||
}
|
||||
|
||||
cString cStreamdevDevice::DeviceName(void) const {
|
||||
return StreamdevClientSetup.RemoteIp;
|
||||
}
|
||||
|
||||
cString cStreamdevDevice::DeviceType(void) const {
|
||||
static int dev = -1;
|
||||
static cString devType("STRDev");
|
||||
int d = -1;
|
||||
if (m_ClientSocket->DataSocket(siLive) != NULL)
|
||||
m_ClientSocket->GetSignal(NULL, NULL, &d);
|
||||
if (d != dev) {
|
||||
dev = d;
|
||||
devType = d < 0 ? "STRDev" : *cString::sprintf("STRD%2d", d);
|
||||
}
|
||||
return devType;
|
||||
}
|
||||
|
||||
int cStreamdevDevice::SignalStrength(void) const {
|
||||
int strength = -1;
|
||||
if (m_ClientSocket->DataSocket(siLive) != NULL)
|
||||
m_ClientSocket->GetSignal(&strength, NULL, NULL);
|
||||
return strength;
|
||||
}
|
||||
|
||||
int cStreamdevDevice::SignalQuality(void) const {
|
||||
int quality = -1;
|
||||
if (m_ClientSocket->DataSocket(siLive) != NULL)
|
||||
m_ClientSocket->GetSignal(NULL, &quality, NULL);
|
||||
return quality;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: device.h,v 1.7 2008/04/07 14:40:39 schmirl Exp $
|
||||
* $Id: device.h,v 1.10 2010/08/18 10:26:55 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_DEVICE_H
|
||||
@@ -15,23 +15,24 @@ class cTBString;
|
||||
#define CMD_LOCK_OBJ(x) cMutexLock CmdLock((cMutex*)&(x)->m_Mutex)
|
||||
|
||||
class cStreamdevDevice: public cDevice {
|
||||
friend class cRemoteRecordings;
|
||||
|
||||
private:
|
||||
bool m_Disabled;
|
||||
cClientSocket *m_ClientSocket;
|
||||
const cChannel *m_Channel;
|
||||
cTSBuffer *m_TSBuffer;
|
||||
cStreamdevFilters *m_Filters;
|
||||
int m_Pids;
|
||||
bool m_DvrClosed;
|
||||
|
||||
static cStreamdevDevice *m_Device;
|
||||
|
||||
bool OpenDvrInt(void);
|
||||
void CloseDvrInt(void);
|
||||
static const cChannel *m_DenyChannel;
|
||||
|
||||
protected:
|
||||
virtual bool SetChannelDevice(const cChannel *Channel, bool LiveView);
|
||||
#if APIVERSNUM >= 10738
|
||||
virtual bool HasLock(int TimeoutMs) const
|
||||
#else
|
||||
virtual bool HasLock(int TimeoutMs)
|
||||
#endif
|
||||
{
|
||||
//printf("HasLock is %d\n", (ClientSocket.DataSocket(siLive) != NULL));
|
||||
//return ClientSocket.DataSocket(siLive) != NULL;
|
||||
@@ -44,21 +45,38 @@ protected:
|
||||
virtual bool GetTSPacket(uchar *&Data);
|
||||
|
||||
virtual int OpenFilter(u_short Pid, u_char Tid, u_char Mask);
|
||||
virtual void CloseFilter(int Handle);
|
||||
|
||||
public:
|
||||
cStreamdevDevice(void);
|
||||
virtual ~cStreamdevDevice();
|
||||
|
||||
virtual bool HasInternalCam(void) { return true; }
|
||||
virtual bool ProvidesSource(int Source) const;
|
||||
virtual bool ProvidesTransponder(const cChannel *Channel) const;
|
||||
virtual bool ProvidesChannel(const cChannel *Channel, int Priority = -1,
|
||||
bool *NeedsDetachReceivers = NULL) const;
|
||||
#if APIVERSNUM >= 10700
|
||||
virtual int NumProvidedSystems(void) const;
|
||||
#endif
|
||||
#if APIVERSNUM >= 10719
|
||||
virtual bool AvoidRecording(void) const { return true; }
|
||||
#endif
|
||||
#if APIVERSNUM >= 10722
|
||||
virtual bool IsTunedToTransponder(const cChannel *Channel) const;
|
||||
#else
|
||||
virtual bool IsTunedToTransponder(const cChannel *Channel);
|
||||
#endif
|
||||
virtual const cChannel *GetCurrentlyTunedTransponder(void) const;
|
||||
virtual cString DeviceName(void) const;
|
||||
virtual cString DeviceType(void) const;
|
||||
virtual int SignalStrength(void) const;
|
||||
virtual int SignalQuality(void) const;
|
||||
|
||||
static bool Init(void);
|
||||
static bool ReInit(void);
|
||||
|
||||
static cStreamdevDevice *GetDevice(void) { return m_Device; }
|
||||
bool ReInit(bool Disable);
|
||||
void UpdatePriority(bool SwitchingChannels = false) const;
|
||||
bool SuspendServer() { return m_ClientSocket->SuspendServer(); }
|
||||
static void DenyChannel(const cChannel *Channel) { m_DenyChannel = Channel; }
|
||||
};
|
||||
|
||||
#endif // VDR_STREAMDEV_DEVICE_H
|
||||
|
||||
173
client/filter.c
173
client/filter.c
@@ -1,24 +1,33 @@
|
||||
/*
|
||||
* $Id: filter.c,v 1.12 2008/04/07 14:27:28 schmirl Exp $
|
||||
* $Id: filter.c,v 1.14 2009/02/13 13:02:39 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include "client/filter.h"
|
||||
#include "client/socket.h"
|
||||
#include "tools/select.h"
|
||||
#include "common.h"
|
||||
#include <sys/ioctl.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <vdr/device.h>
|
||||
|
||||
#define PID_MASK_HI 0x1F
|
||||
// --- cStreamdevFilter ------------------------------------------------------
|
||||
|
||||
static int FilterSockBufSize_warn = 0;
|
||||
|
||||
class cStreamdevFilter: public cListObject {
|
||||
private:
|
||||
uchar m_Buffer[4096];
|
||||
uchar m_Buffer[8192];
|
||||
int m_Used;
|
||||
int m_Pipe[2];
|
||||
u_short m_Pid;
|
||||
u_char m_Tid;
|
||||
u_char m_Mask;
|
||||
#ifdef TIOCOUTQ
|
||||
unsigned long m_maxq;
|
||||
unsigned long m_flushed;
|
||||
#endif
|
||||
|
||||
public:
|
||||
cStreamdevFilter(u_short Pid, u_char Tid, u_char Mask);
|
||||
@@ -28,7 +37,6 @@ public:
|
||||
bool PutSection(const uchar *Data, int Length, bool Pusi);
|
||||
int ReadPipe(void) const { return m_Pipe[0]; }
|
||||
|
||||
bool IsClosed(void);
|
||||
void Reset(void);
|
||||
|
||||
u_short Pid(void) const { return m_Pid; }
|
||||
@@ -46,6 +54,10 @@ cStreamdevFilter::cStreamdevFilter(u_short Pid, u_char Tid, u_char Mask) {
|
||||
m_Tid = Tid;
|
||||
m_Mask = Mask;
|
||||
m_Pipe[0] = m_Pipe[1] = -1;
|
||||
#ifdef TIOCOUTQ
|
||||
m_flushed = 0;
|
||||
m_maxq = 0;
|
||||
#endif
|
||||
|
||||
#ifdef SOCK_SEQPACKET
|
||||
// SOCK_SEQPACKET (since kernel 2.6.4)
|
||||
@@ -57,7 +69,46 @@ cStreamdevFilter::cStreamdevFilter(u_short Pid, u_char Tid, u_char Mask) {
|
||||
esyslog("streamdev-client: couldn't open section filter socket: %m");
|
||||
}
|
||||
|
||||
else if(fcntl(m_Pipe[0], F_SETFL, O_NONBLOCK) != 0 ||
|
||||
// Set buffer for socketpair. During certain situations, such as startup, channel/transponder
|
||||
// change, VDR may lag in reading data. Instead of discarding it, we can buffer it.
|
||||
// Buffer size required may be up to 4MByte.
|
||||
|
||||
if(StreamdevClientSetup.FilterSockBufSize) {
|
||||
int sbs = StreamdevClientSetup.FilterSockBufSize;
|
||||
int sbs2;
|
||||
unsigned int sbss = sizeof(sbs);
|
||||
int r;
|
||||
|
||||
r = setsockopt(m_Pipe[1], SOL_SOCKET, SO_SNDBUF, (char *)&sbs, sbss);
|
||||
|
||||
if(r < 0) {
|
||||
isyslog("streamdev-client: setsockopt(SO_SNDBUF, %d) = %s", sbs, strerror(errno));
|
||||
}
|
||||
sbs2 = 0;
|
||||
r = getsockopt(m_Pipe[1], SOL_SOCKET, SO_SNDBUF, (char *)&sbs2, &sbss);
|
||||
if(r < 0 || !sbss || !sbs2) {
|
||||
isyslog("streamdev-client: getsockopt(SO_SNDBUF, &%d, &%d) = %s", sbs2, sbss, strerror(errno));
|
||||
} else {
|
||||
// Linux actually returns double the requested size
|
||||
// if everything works fine. And it actually buffers up to that double amount
|
||||
// as can be seen from observing TIOCOUTQ (kernel 3.7/2014).
|
||||
|
||||
if(sbs2 > sbs)
|
||||
sbs2 /= 2;
|
||||
if(sbs2 < sbs) {
|
||||
if(FilterSockBufSize_warn != sbs2) {
|
||||
isyslog("streamdev-client: ******************************************************");
|
||||
isyslog("streamdev-client: getsockopt(SO_SNDBUF) = %d < %d (configured).", sbs2, sbs);
|
||||
isyslog("streamdev-client: Consider increasing system buffer size:");
|
||||
isyslog("streamdev-client: 'sysctl net.core.wmem_max=%d'", sbs);
|
||||
isyslog("streamdev-client: ******************************************************");
|
||||
FilterSockBufSize_warn = sbs2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(fcntl(m_Pipe[0], F_SETFL, O_NONBLOCK) != 0 ||
|
||||
fcntl(m_Pipe[1], F_SETFL, O_NONBLOCK) != 0) {
|
||||
esyslog("streamdev-client: couldn't set section filter socket to non-blocking mode: %m");
|
||||
}
|
||||
@@ -66,11 +117,12 @@ cStreamdevFilter::cStreamdevFilter(u_short Pid, u_char Tid, u_char Mask) {
|
||||
cStreamdevFilter::~cStreamdevFilter() {
|
||||
Dprintf("~cStreamdevFilter %p\n", this);
|
||||
|
||||
// ownership of handle m_Pipe[0] has been transferred to VDR section handler
|
||||
//if (m_Pipe[0] >= 0)
|
||||
// close(m_Pipe[0]);
|
||||
if (m_Pipe[1] >= 0)
|
||||
if (m_Pipe[0] >= 0) {
|
||||
close(m_Pipe[0]);
|
||||
}
|
||||
if (m_Pipe[1] >= 0) {
|
||||
close(m_Pipe[1]);
|
||||
}
|
||||
}
|
||||
|
||||
bool cStreamdevFilter::PutSection(const uchar *Data, int Length, bool Pusi) {
|
||||
@@ -93,13 +145,42 @@ bool cStreamdevFilter::PutSection(const uchar *Data, int Length, bool Pusi) {
|
||||
int length = (((m_Buffer[1] & 0x0F) << 8) | m_Buffer[2]) + 3;
|
||||
if (m_Used == length) {
|
||||
m_Used = 0;
|
||||
if (write(m_Pipe[1], m_Buffer, length) < 0) {
|
||||
if(errno == EAGAIN || errno == EWOULDBLOCK)
|
||||
dsyslog("cStreamdevFilter::PutSection socket overflow, "
|
||||
"Pid %4d Tid %3d", m_Pid, m_Tid);
|
||||
#ifdef TIOCOUTQ
|
||||
// If we can determine the queue size of the socket,
|
||||
// we flush rather then let the socket drop random packets.
|
||||
// This ensures that we have more contiguous set of packets
|
||||
// on the receiver side.
|
||||
if(m_flushed) {
|
||||
unsigned long queue = 0;
|
||||
ioctl(m_Pipe[1], TIOCOUTQ, &queue);
|
||||
if(queue > m_maxq)
|
||||
m_maxq = queue;
|
||||
if(queue * 2 < m_maxq) {
|
||||
dsyslog("cStreamdevFilter::PutSection(Pid:%d Tid: %d): "
|
||||
"Flushed %ld bytes, max queue: %ld",
|
||||
m_Pid, m_Tid, m_flushed, m_maxq);
|
||||
m_flushed = m_maxq = 0;
|
||||
|
||||
else
|
||||
} else {
|
||||
m_flushed += length;
|
||||
}
|
||||
}
|
||||
if(!m_flushed)
|
||||
#endif
|
||||
if(write(m_Pipe[1], m_Buffer, length) < 0) {
|
||||
if(errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||
dsyslog("cStreamdevFilter::PutSection(Pid:%d Tid: %d): error: %s",
|
||||
m_Pid, m_Tid, strerror(errno));
|
||||
return false;
|
||||
} else {
|
||||
#ifdef TIOCOUTQ
|
||||
m_flushed += length;
|
||||
#else
|
||||
dsyslog("cStreamdevFilter::PutSection(Pid:%d Tid: %d): "
|
||||
"Dropping packet %ld bytes (queue overflow)",
|
||||
m_Pid, m_Tid, length);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,29 +203,11 @@ void cStreamdevFilter::Reset(void) {
|
||||
m_Used = 0;
|
||||
}
|
||||
|
||||
bool cStreamdevFilter::IsClosed(void) {
|
||||
char m_Buffer[3] = {0,0,0}; /* tid 0, 0 bytes */
|
||||
|
||||
// Test if pipe/socket has been closed by writing empty section
|
||||
if (write(m_Pipe[1], m_Buffer, 3) < 0 &&
|
||||
errno != EAGAIN &&
|
||||
errno != EWOULDBLOCK) {
|
||||
|
||||
if (errno != ECONNREFUSED &&
|
||||
errno != ECONNRESET &&
|
||||
errno != EPIPE)
|
||||
esyslog("cStreamdevFilter::IsClosed failed: %m");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- cStreamdevFilters -----------------------------------------------------
|
||||
|
||||
cStreamdevFilters::cStreamdevFilters(void):
|
||||
cStreamdevFilters::cStreamdevFilters(cClientSocket *ClientSocket):
|
||||
cThread("streamdev-client: sections assembler") {
|
||||
m_ClientSocket = ClientSocket;
|
||||
m_TSBuffer = NULL;
|
||||
}
|
||||
|
||||
@@ -153,43 +216,27 @@ cStreamdevFilters::~cStreamdevFilters() {
|
||||
}
|
||||
|
||||
int cStreamdevFilters::OpenFilter(u_short Pid, u_char Tid, u_char Mask) {
|
||||
CarbageCollect();
|
||||
|
||||
cStreamdevFilter *f = new cStreamdevFilter(Pid, Tid, Mask);
|
||||
int fh = f->ReadPipe();
|
||||
|
||||
Lock();
|
||||
LOCK_THREAD;
|
||||
Add(f);
|
||||
Unlock();
|
||||
|
||||
return fh;
|
||||
}
|
||||
|
||||
void cStreamdevFilters::CarbageCollect(void) {
|
||||
void cStreamdevFilters::CloseFilter(int Handle) {
|
||||
LOCK_THREAD;
|
||||
for (cStreamdevFilter *fi = First(); fi;) {
|
||||
if (fi->IsClosed()) {
|
||||
if (errno == ECONNREFUSED ||
|
||||
errno == ECONNRESET ||
|
||||
errno == EPIPE) {
|
||||
ClientSocket.SetFilter(fi->Pid(), fi->Tid(), fi->Mask(), false);
|
||||
Dprintf("cStreamdevFilters::CarbageCollector: filter closed: Pid %4d, Tid %3d, Mask %2x (%d filters left)",
|
||||
(int)fi->Pid(), (int)fi->Tid(), fi->Mask(), Count()-1);
|
||||
|
||||
cStreamdevFilter *next = Prev(fi);
|
||||
Del(fi);
|
||||
fi = next ? Next(next) : First();
|
||||
} else {
|
||||
esyslog("cStreamdevFilters::CarbageCollector() error: "
|
||||
"Pid %4d, Tid %3d, Mask %2x (%d filters left) failed",
|
||||
(int)fi->Pid(), (int)fi->Tid(), fi->Mask(), Count()-1);
|
||||
LOG_ERROR;
|
||||
fi = Next(fi);
|
||||
}
|
||||
} else {
|
||||
fi = Next(fi);
|
||||
for (cStreamdevFilter *fi = First(); fi; fi = Next(fi)) {
|
||||
if(fi->ReadPipe() == Handle) {
|
||||
// isyslog("cStreamdevFilters::CloseFilter(%d): Pid %4d, Tid %3d, Mask %2x (%d filters left)\n",
|
||||
// Handle, (int)fi->Pid(), (int)fi->Tid(), fi->Mask(), Count()-1);
|
||||
Del(fi);
|
||||
return;
|
||||
}
|
||||
}
|
||||
esyslog("cStreamdevFilters::CloseFilter(%d): failed (%d filters left)\n", Handle, Count()-1);
|
||||
}
|
||||
|
||||
bool cStreamdevFilters::ReActivateFilters(void)
|
||||
@@ -197,9 +244,8 @@ bool cStreamdevFilters::ReActivateFilters(void)
|
||||
LOCK_THREAD;
|
||||
|
||||
bool res = true;
|
||||
CarbageCollect();
|
||||
for (cStreamdevFilter *fi = First(); fi; fi = Next(fi)) {
|
||||
res = ClientSocket.SetFilter(fi->Pid(), fi->Tid(), fi->Mask(), true) && res;
|
||||
res = m_ClientSocket->SetFilter(fi->Pid(), fi->Tid(), fi->Mask(), true) && res;
|
||||
Dprintf("ReActivateFilters(%d, %d, %d) -> %s", fi->Pid(), fi->Tid(), fi->Mask(), res ? "Ok" :"FAIL");
|
||||
}
|
||||
return res;
|
||||
@@ -226,6 +272,7 @@ void cStreamdevFilters::Action(void) {
|
||||
u_short pid = (((u_short)block[1] & PID_MASK_HI) << 8) | block[2];
|
||||
u_char tid = block[3];
|
||||
bool Pusi = block[1] & 0x40;
|
||||
// proprietary extension
|
||||
int len = block[4];
|
||||
#if 0
|
||||
if (block[1] == 0xff &&
|
||||
@@ -249,7 +296,7 @@ void cStreamdevFilters::Action(void) {
|
||||
Dprintf("FATAL ERROR: %m\n");
|
||||
esyslog("streamdev-client: couldn't send section packet: %m");
|
||||
}
|
||||
ClientSocket.SetFilter(f->Pid(), f->Tid(), f->Mask(), false);
|
||||
m_ClientSocket->SetFilter(f->Pid(), f->Tid(), f->Mask(), false);
|
||||
Del(f);
|
||||
// Filter was closed.
|
||||
// - need to check remaining filters for another match
|
||||
@@ -259,7 +306,7 @@ void cStreamdevFilters::Action(void) {
|
||||
} else {
|
||||
#if 1 // TODO: this should be fixed in vdr cTSBuffer
|
||||
// Check disconnection
|
||||
int fd = *ClientSocket.DataSocket(siLiveFilter);
|
||||
int fd = *m_ClientSocket->DataSocket(siLiveFilter);
|
||||
if(fd < 0)
|
||||
break;
|
||||
cPoller Poller(fd);
|
||||
@@ -271,7 +318,7 @@ void cStreamdevFilters::Action(void) {
|
||||
++fails;
|
||||
if (fails >= 10) {
|
||||
esyslog("cStreamdevFilters::Action(): stream disconnected ?");
|
||||
ClientSocket.CloseDataConnection(siLiveFilter);
|
||||
m_ClientSocket->CloseDataConnection(siLiveFilter);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -11,23 +11,24 @@
|
||||
|
||||
class cTSBuffer;
|
||||
class cStreamdevFilter;
|
||||
class cClientSocket;
|
||||
|
||||
class cStreamdevFilters: public cList<cStreamdevFilter>, public cThread {
|
||||
private:
|
||||
cClientSocket *m_ClientSocket;
|
||||
cTSBuffer *m_TSBuffer;
|
||||
|
||||
protected:
|
||||
virtual void Action(void);
|
||||
void CarbageCollect(void);
|
||||
|
||||
bool ReActivateFilters(void);
|
||||
|
||||
public:
|
||||
cStreamdevFilters(void);
|
||||
cStreamdevFilters(cClientSocket *ClientSocket);
|
||||
virtual ~cStreamdevFilters();
|
||||
|
||||
void SetConnection(int Handle);
|
||||
int OpenFilter(u_short Pid, u_char Tid, u_char Mask);
|
||||
void CloseFilter(int Handle);
|
||||
};
|
||||
|
||||
#endif // VDR_STREAMDEV_FILTER_H
|
||||
|
||||
62
client/po/de_DE.po
Normal file
62
client/po/de_DE.po
Normal file
@@ -0,0 +1,62 @@
|
||||
# VDR streamdev plugin language source file.
|
||||
# Copyright (C) 2008 streamdev development team. See http://streamdev.vdr-developer.org
|
||||
# This file is distributed under the same license as the VDR streamdev package.
|
||||
# Frank Schmirler <vdrdev@schmirler.de>, 2008
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: streamdev 0.5.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdrdev@schmirler.de>\n"
|
||||
"POT-Creation-Date: 2014-10-20 22:57+0200\n"
|
||||
"PO-Revision-Date: 2008-03-30 02:11+0200\n"
|
||||
"Last-Translator: Frank Schmirler <vdrdev@schmirler.de>\n"
|
||||
"Language-Team: German <vdr@linuxtv.org>\n"
|
||||
"Language: de\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=ISO-8859-15\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "Hide Mainmenu Entry"
|
||||
msgstr "Hauptmenüeintrag verstecken"
|
||||
|
||||
msgid "Simultaneously used Devices"
|
||||
msgstr "Gleichzeitig genutzte DVB-Karten"
|
||||
|
||||
msgid "Remote IP"
|
||||
msgstr "IP der Gegenseite"
|
||||
|
||||
msgid "Remote Port"
|
||||
msgstr "Port der Gegenseite"
|
||||
|
||||
msgid "Timeout (s)"
|
||||
msgstr "Timeout (s)"
|
||||
|
||||
msgid "Filter Streaming"
|
||||
msgstr "Filter-Daten streamen"
|
||||
|
||||
msgid "Filter SockBufSize"
|
||||
msgstr "Filter Socket Puffergröße"
|
||||
|
||||
msgid "Live TV Priority"
|
||||
msgstr "Live TV Priorität"
|
||||
|
||||
msgid "Minimum Priority"
|
||||
msgstr "Minimale Priorität"
|
||||
|
||||
msgid "Maximum Priority"
|
||||
msgstr "Maximale Priorität"
|
||||
|
||||
msgid "Broadcast Systems / Cost"
|
||||
msgstr "Empfangssysteme / Kosten"
|
||||
|
||||
msgid "VTP Streaming Client"
|
||||
msgstr "VTP Streaming Client"
|
||||
|
||||
msgid "Suspend Server"
|
||||
msgstr "Server pausieren"
|
||||
|
||||
msgid "Server is suspended"
|
||||
msgstr "Server ist pausiert"
|
||||
|
||||
msgid "Couldn't suspend Server!"
|
||||
msgstr "Konnte Server nicht pausieren!"
|
||||
62
client/po/es_ES.po
Normal file
62
client/po/es_ES.po
Normal file
@@ -0,0 +1,62 @@
|
||||
# VDR streamdev plugin language source file.
|
||||
# Copyright (C) 2008 streamdev development team. See http://streamdev.vdr-developer.org
|
||||
# This file is distributed under the same license as the VDR streamdev package.
|
||||
# Javier Bradineras <jbradi@hotmail.com>, 2011
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: streamdev 0.5.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdrdev@schmirler.de>\n"
|
||||
"POT-Creation-Date: 2015-01-16 22:32+0100\n"
|
||||
"PO-Revision-Date: 2010-06-19 03:58+0100\n"
|
||||
"Last-Translator: Javier Bradineras <jbradi@hotmail.com>\n"
|
||||
"Language-Team: Spanish <vdr@linuxtv.org>\n"
|
||||
"Language: es\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=ISO-8859-15\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "Hide Mainmenu Entry"
|
||||
msgstr "Ocultar entrada en menú principal"
|
||||
|
||||
msgid "Simultaneously used Devices"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remote IP"
|
||||
msgstr "Indicar IP del Servidor"
|
||||
|
||||
msgid "Remote Port"
|
||||
msgstr "Indicar puerto remoto del Servidor"
|
||||
|
||||
msgid "Timeout (s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Filter Streaming"
|
||||
msgstr "Filtrar transmisión"
|
||||
|
||||
msgid "Filter SockBufSize"
|
||||
msgstr ""
|
||||
|
||||
msgid "Live TV Priority"
|
||||
msgstr ""
|
||||
|
||||
msgid "Minimum Priority"
|
||||
msgstr "Prioridad mínima"
|
||||
|
||||
msgid "Maximum Priority"
|
||||
msgstr "Prioridad máxima"
|
||||
|
||||
msgid "Broadcast Systems / Cost"
|
||||
msgstr ""
|
||||
|
||||
msgid "VTP Streaming Client"
|
||||
msgstr "Cliente trasmisión VTP"
|
||||
|
||||
msgid "Suspend Server"
|
||||
msgstr "Suspender servidor"
|
||||
|
||||
msgid "Server is suspended"
|
||||
msgstr "Servidor en suspensión"
|
||||
|
||||
msgid "Couldn't suspend Server!"
|
||||
msgstr "Imposible suspender el servidor!"
|
||||
62
client/po/fi_FI.po
Normal file
62
client/po/fi_FI.po
Normal file
@@ -0,0 +1,62 @@
|
||||
# VDR streamdev plugin language source file.
|
||||
# Copyright (C) 2008 streamdev development team. See http://streamdev.vdr-developer.org
|
||||
# This file is distributed under the same license as the VDR streamdev package.
|
||||
# Rolf Ahrenberg, 2008-
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: streamdev 0.5.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdrdev@schmirler.de>\n"
|
||||
"POT-Creation-Date: 2015-01-16 22:32+0100\n"
|
||||
"PO-Revision-Date: 2008-03-30 02:11+0200\n"
|
||||
"Last-Translator: Rolf Ahrenberg\n"
|
||||
"Language-Team: Finnish <vdr@linuxtv.org>\n"
|
||||
"Language: fi\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "Hide Mainmenu Entry"
|
||||
msgstr "Piilota valinta päävalikosta"
|
||||
|
||||
msgid "Simultaneously used Devices"
|
||||
msgstr "Yhtäaikaiset laitteet"
|
||||
|
||||
msgid "Remote IP"
|
||||
msgstr "Etäkoneen IP-osoite"
|
||||
|
||||
msgid "Remote Port"
|
||||
msgstr "Etäkoneen portti"
|
||||
|
||||
msgid "Timeout (s)"
|
||||
msgstr "Yhteyden aikakatkaisu (s)"
|
||||
|
||||
msgid "Filter Streaming"
|
||||
msgstr "Suodatetun tiedon suoratoisto"
|
||||
|
||||
msgid "Filter SockBufSize"
|
||||
msgstr ""
|
||||
|
||||
msgid "Live TV Priority"
|
||||
msgstr "Live-katselun prioriteetti"
|
||||
|
||||
msgid "Minimum Priority"
|
||||
msgstr "Pienin prioriteetti"
|
||||
|
||||
msgid "Maximum Priority"
|
||||
msgstr "Suurin prioriteetti"
|
||||
|
||||
msgid "Broadcast Systems / Cost"
|
||||
msgstr "Lähetysjärjestelmien suhdeluku"
|
||||
|
||||
msgid "VTP Streaming Client"
|
||||
msgstr "VTP-suoratoistoasiakas"
|
||||
|
||||
msgid "Suspend Server"
|
||||
msgstr "Pysäytä palvelin"
|
||||
|
||||
msgid "Server is suspended"
|
||||
msgstr "Palvelin on pysäytetty"
|
||||
|
||||
msgid "Couldn't suspend Server!"
|
||||
msgstr "Palvelinta ei onnistuttu pysäyttämään!"
|
||||
62
client/po/fr_FR.po
Normal file
62
client/po/fr_FR.po
Normal file
@@ -0,0 +1,62 @@
|
||||
# VDR streamdev plugin language source file.
|
||||
# Copyright (C) 2008 streamdev development team. See http://streamdev.vdr-developer.org
|
||||
# This file is distributed under the same license as the VDR streamdev package.
|
||||
# Frank Schmirler <vdrdev@schmirler.de>, 2008
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: streamdev 0.5.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdrdev@schmirler.de>\n"
|
||||
"POT-Creation-Date: 2015-01-16 22:32+0100\n"
|
||||
"PO-Revision-Date: 2008-03-30 02:11+0200\n"
|
||||
"Last-Translator: micky979 <micky979@free.fr>\n"
|
||||
"Language-Team: French <vdr@linuxtv.org>\n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=ISO-8859-15\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "Hide Mainmenu Entry"
|
||||
msgstr "Masquer dans le menu principal"
|
||||
|
||||
msgid "Simultaneously used Devices"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remote IP"
|
||||
msgstr "Adresse IP du serveur"
|
||||
|
||||
msgid "Remote Port"
|
||||
msgstr "Port du serveur"
|
||||
|
||||
msgid "Timeout (s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Filter Streaming"
|
||||
msgstr "Filtre streaming"
|
||||
|
||||
msgid "Filter SockBufSize"
|
||||
msgstr ""
|
||||
|
||||
msgid "Live TV Priority"
|
||||
msgstr ""
|
||||
|
||||
msgid "Minimum Priority"
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum Priority"
|
||||
msgstr ""
|
||||
|
||||
msgid "Broadcast Systems / Cost"
|
||||
msgstr ""
|
||||
|
||||
msgid "VTP Streaming Client"
|
||||
msgstr "Client de streaming VTP"
|
||||
|
||||
msgid "Suspend Server"
|
||||
msgstr "Suspendre le serveur"
|
||||
|
||||
msgid "Server is suspended"
|
||||
msgstr "Le serveur est suspendu"
|
||||
|
||||
msgid "Couldn't suspend Server!"
|
||||
msgstr "Impossible de suspendre le serveur!"
|
||||
64
client/po/it_IT.po
Normal file
64
client/po/it_IT.po
Normal file
@@ -0,0 +1,64 @@
|
||||
# VDR streamdev plugin language source file.
|
||||
# Copyright (C) 2008 streamdev development team. See http://streamdev.vdr-developer.org
|
||||
# This file is distributed under the same license as the VDR streamdev package.
|
||||
# Alberto Carraro <bertocar@tin.it>, 2001
|
||||
# Antonio Ospite <ospite@studenti.unina.it>, 2003
|
||||
# Sean Carlos <seanc@libero.it>, 2005
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: streamdev 0.5.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdrdev@schmirler.de>\n"
|
||||
"POT-Creation-Date: 2015-01-16 22:32+0100\n"
|
||||
"PO-Revision-Date: 2012-06-10 20:34+0100\n"
|
||||
"Last-Translator: Diego Pierotto <vdr-italian@tiscali.it>\n"
|
||||
"Language-Team: Italian <vdr@linuxtv.org>\n"
|
||||
"Language: it\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "Hide Mainmenu Entry"
|
||||
msgstr "Nascondi voce menu principale"
|
||||
|
||||
msgid "Simultaneously used Devices"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remote IP"
|
||||
msgstr "Indirizzo IP del Server"
|
||||
|
||||
msgid "Remote Port"
|
||||
msgstr "Porta Server Remoto"
|
||||
|
||||
msgid "Timeout (s)"
|
||||
msgstr "Scadenza (s)"
|
||||
|
||||
msgid "Filter Streaming"
|
||||
msgstr "Filtra trasmissione"
|
||||
|
||||
msgid "Filter SockBufSize"
|
||||
msgstr ""
|
||||
|
||||
msgid "Live TV Priority"
|
||||
msgstr "Priorità TV dal vivo"
|
||||
|
||||
msgid "Minimum Priority"
|
||||
msgstr "Priorità minima"
|
||||
|
||||
msgid "Maximum Priority"
|
||||
msgstr "Priorità massima"
|
||||
|
||||
msgid "Broadcast Systems / Cost"
|
||||
msgstr "Costo / sistemi trasmissione"
|
||||
|
||||
msgid "VTP Streaming Client"
|
||||
msgstr "Client trasmissione VTP"
|
||||
|
||||
msgid "Suspend Server"
|
||||
msgstr "Sospendi Server"
|
||||
|
||||
msgid "Server is suspended"
|
||||
msgstr "Server sospeso"
|
||||
|
||||
msgid "Couldn't suspend Server!"
|
||||
msgstr "Impossibile sospendere il server!"
|
||||
62
client/po/lt_LT.po
Normal file
62
client/po/lt_LT.po
Normal file
@@ -0,0 +1,62 @@
|
||||
# VDR streamdev plugin language source file.
|
||||
# Copyright (C) 2008 streamdev development team. See http://streamdev.vdr-developer.org
|
||||
# This file is distributed under the same license as the VDR streamdev package.
|
||||
# Frank Schmirler <vdrdev@schmirler.de>, 2008
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: streamdev 0.5.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdrdev@schmirler.de>\n"
|
||||
"POT-Creation-Date: 2015-01-16 22:32+0100\n"
|
||||
"PO-Revision-Date: 2009-11-26 21:57+0200\n"
|
||||
"Last-Translator: Valdemaras Pipiras <varas@ambernet.lt>\n"
|
||||
"Language-Team: Lithuanian <vdr@linuxtv.org>\n"
|
||||
"Language: lt\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "Hide Mainmenu Entry"
|
||||
msgstr "Paslėpti pagrindinio meniu įrašą"
|
||||
|
||||
msgid "Simultaneously used Devices"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remote IP"
|
||||
msgstr "Nuotolinis IP adresas"
|
||||
|
||||
msgid "Remote Port"
|
||||
msgstr "Nuotolinis portas"
|
||||
|
||||
msgid "Timeout (s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Filter Streaming"
|
||||
msgstr "Filtruoti transliavimą"
|
||||
|
||||
msgid "Filter SockBufSize"
|
||||
msgstr ""
|
||||
|
||||
msgid "Live TV Priority"
|
||||
msgstr ""
|
||||
|
||||
msgid "Minimum Priority"
|
||||
msgstr "Minimalus prioritetas"
|
||||
|
||||
msgid "Maximum Priority"
|
||||
msgstr "Maksimalus prioritetas"
|
||||
|
||||
msgid "Broadcast Systems / Cost"
|
||||
msgstr ""
|
||||
|
||||
msgid "VTP Streaming Client"
|
||||
msgstr "VTP transliavimo standartas"
|
||||
|
||||
msgid "Suspend Server"
|
||||
msgstr "Sustabdyti serverį"
|
||||
|
||||
msgid "Server is suspended"
|
||||
msgstr "Serveris sustabdytas"
|
||||
|
||||
msgid "Couldn't suspend Server!"
|
||||
msgstr "Negali sustabdyti serverio!"
|
||||
63
client/po/pl_PL.po
Normal file
63
client/po/pl_PL.po
Normal file
@@ -0,0 +1,63 @@
|
||||
# VDR streamdev plugin language source file.
|
||||
# Copyright (C) 2008 streamdev development team. See
|
||||
# This file is distributed under the same license as the VDR streamdev package.
|
||||
# Tomasz Maciej Nowak, 2014
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: vdr-streamdev-client 0.6.1-git\n"
|
||||
"Report-Msgid-Bugs-To: <vdrdev@schmirler.de>\n"
|
||||
"POT-Creation-Date: 2015-01-16 22:32+0100\n"
|
||||
"PO-Revision-Date: 2014-11-24 18:07+0100\n"
|
||||
"Last-Translator: Tomasz Maciej Nowak <tomek_n@o2.pl>\n"
|
||||
"Language-Team: Polish <vdr@linuxtv.org>\n"
|
||||
"Language: pl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=iso-8859-2\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 1.6.10\n"
|
||||
|
||||
msgid "Hide Mainmenu Entry"
|
||||
msgstr "Ukryj pozycjê w g³ównym menu"
|
||||
|
||||
msgid "Simultaneously used Devices"
|
||||
msgstr "Jednocze¶nie u¿ywane urz±dzenia"
|
||||
|
||||
msgid "Remote IP"
|
||||
msgstr "Adres serwera"
|
||||
|
||||
msgid "Remote Port"
|
||||
msgstr "Port serwera"
|
||||
|
||||
msgid "Timeout (s)"
|
||||
msgstr "Czas oczekiwania (s)"
|
||||
|
||||
msgid "Filter Streaming"
|
||||
msgstr "Filtruj strumieñ"
|
||||
|
||||
msgid "Filter SockBufSize"
|
||||
msgstr ""
|
||||
|
||||
msgid "Live TV Priority"
|
||||
msgstr "Priorytet lokalnych ur±dzeñ"
|
||||
|
||||
msgid "Minimum Priority"
|
||||
msgstr "Minimalny priorytet"
|
||||
|
||||
msgid "Maximum Priority"
|
||||
msgstr "Maksymalny priorytet"
|
||||
|
||||
msgid "Broadcast Systems / Cost"
|
||||
msgstr "Koszt / systemy transmisji"
|
||||
|
||||
msgid "VTP Streaming Client"
|
||||
msgstr "Klient strumieniowania VTP"
|
||||
|
||||
msgid "Suspend Server"
|
||||
msgstr "Wstrzymaj serwer"
|
||||
|
||||
msgid "Server is suspended"
|
||||
msgstr "Serwer jest wstrzymany"
|
||||
|
||||
msgid "Couldn't suspend Server!"
|
||||
msgstr "Nie mo¿na wstrzymaæ serwera!"
|
||||
62
client/po/ru_RU.po
Normal file
62
client/po/ru_RU.po
Normal file
@@ -0,0 +1,62 @@
|
||||
# VDR streamdev plugin language source file.
|
||||
# Copyright (C) 2008 streamdev development team. See http://streamdev.vdr-developer.org
|
||||
# This file is distributed under the same license as the VDR streamdev package.
|
||||
# Frank Schmirler <vdrdev@schmirler.de>, 2008
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: streamdev 0.5.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdrdev@schmirler.de>\n"
|
||||
"POT-Creation-Date: 2015-01-16 22:32+0100\n"
|
||||
"PO-Revision-Date: 2008-06-26 15:36+0100\n"
|
||||
"Last-Translator: Oleg Roitburd <oleg@roitburd.de>\n"
|
||||
"Language-Team: Russian <vdr@linuxtv.org>\n"
|
||||
"Language: ru\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=ISO-8859-5\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "Hide Mainmenu Entry"
|
||||
msgstr "ÁßàïâÐâì Ò ÓÛÐÒÝÞÜ ÜÕÝî"
|
||||
|
||||
msgid "Simultaneously used Devices"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remote IP"
|
||||
msgstr "ÃÔÐÛÕÝÝëÙ IP"
|
||||
|
||||
msgid "Remote Port"
|
||||
msgstr "ÃÔÐÛÕÝÝëÙ ßÞàâ"
|
||||
|
||||
msgid "Timeout (s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Filter Streaming"
|
||||
msgstr "ÄØÛìâà ßÞâÞÚÐ"
|
||||
|
||||
msgid "Filter SockBufSize"
|
||||
msgstr ""
|
||||
|
||||
msgid "Live TV Priority"
|
||||
msgstr ""
|
||||
|
||||
msgid "Minimum Priority"
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum Priority"
|
||||
msgstr ""
|
||||
|
||||
msgid "Broadcast Systems / Cost"
|
||||
msgstr ""
|
||||
|
||||
msgid "VTP Streaming Client"
|
||||
msgstr "VTP Streaming ÚÛØÕÝâ"
|
||||
|
||||
msgid "Suspend Server"
|
||||
msgstr "¾áâÐÝÞÒØâì áÕàÒÕà"
|
||||
|
||||
msgid "Server is suspended"
|
||||
msgstr "ÁÕàÒÕà ÞáâÐÝÞÒÛÕÝ"
|
||||
|
||||
msgid "Couldn't suspend Server!"
|
||||
msgstr "ÝÕ ÜÞÓã ÞáâÐÝÞÒØâì áÕàÒÕà"
|
||||
64
client/po/sk_SK.po
Executable file
64
client/po/sk_SK.po
Executable file
@@ -0,0 +1,64 @@
|
||||
# VDR streamdev plugin language source file.
|
||||
# Copyright (C) 2009 streamdev development team. See http://streamdev.vdr-developer.org
|
||||
# This file is distributed under the same license as the VDR streamdev package.
|
||||
# Milan Hrala <hrala.milan@gmail.com>, 2009
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: streamdev_SK\n"
|
||||
"Report-Msgid-Bugs-To: <vdrdev@schmirler.de>\n"
|
||||
"POT-Creation-Date: 2015-01-16 22:32+0100\n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Milan Hrala <hrala.milan@gmail.com>\n"
|
||||
"Language-Team: Slovak <hrala.milan@gmail.com>\n"
|
||||
"Language: sk\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=iso-8859-2\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Poedit-Language: Slovak\n"
|
||||
"X-Poedit-Country: SLOVAKIA\n"
|
||||
|
||||
msgid "Hide Mainmenu Entry"
|
||||
msgstr "Schova» polo¾ku v hlavnom menu"
|
||||
|
||||
msgid "Simultaneously used Devices"
|
||||
msgstr "Súbe¾ne pou¾íva» zariadenia"
|
||||
|
||||
msgid "Remote IP"
|
||||
msgstr "Vzdialená IP"
|
||||
|
||||
msgid "Remote Port"
|
||||
msgstr "Vzdialený port"
|
||||
|
||||
msgid "Timeout (s)"
|
||||
msgstr "Èasový limit (s)"
|
||||
|
||||
msgid "Filter Streaming"
|
||||
msgstr "Filtrova» dátový prúd"
|
||||
|
||||
msgid "Filter SockBufSize"
|
||||
msgstr ""
|
||||
|
||||
msgid "Live TV Priority"
|
||||
msgstr "Priorita ¾ivého vysielania"
|
||||
|
||||
msgid "Minimum Priority"
|
||||
msgstr "Minimálna priorita"
|
||||
|
||||
msgid "Maximum Priority"
|
||||
msgstr "Maximálna priorita"
|
||||
|
||||
msgid "Broadcast Systems / Cost"
|
||||
msgstr "Systémy vysielania / Hodnota"
|
||||
|
||||
msgid "VTP Streaming Client"
|
||||
msgstr "VDR klient streamovania"
|
||||
|
||||
msgid "Suspend Server"
|
||||
msgstr "Pozastavi» server"
|
||||
|
||||
msgid "Server is suspended"
|
||||
msgstr "Server je doèasne pozastavený"
|
||||
|
||||
msgid "Couldn't suspend Server!"
|
||||
msgstr "Server sa nepodarilo pozastavi»!"
|
||||
@@ -1,22 +1,32 @@
|
||||
/*
|
||||
* $Id: setup.c,v 1.5 2008/04/07 14:50:32 schmirl Exp $
|
||||
* $Id: setup.c,v 1.10 2010/06/08 05:55:17 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include <vdr/menuitems.h>
|
||||
|
||||
#include "client/setup.h"
|
||||
#include "client/device.h"
|
||||
#include "i18n.h"
|
||||
#include "client/streamdev-client.h"
|
||||
|
||||
#ifndef MINPRIORITY
|
||||
#define MINPRIORITY -MAXPRIORITY
|
||||
#endif
|
||||
|
||||
cStreamdevClientSetup StreamdevClientSetup;
|
||||
|
||||
cStreamdevClientSetup::cStreamdevClientSetup(void) {
|
||||
StartClient = false;
|
||||
RemotePort = 2004;
|
||||
Timeout = 2;
|
||||
StreamFilters = false;
|
||||
SyncEPG = false;
|
||||
HideMenuEntry = false;
|
||||
LivePriority = 0;
|
||||
MinPriority = MINPRIORITY;
|
||||
MaxPriority = MAXPRIORITY;
|
||||
#if APIVERSNUM >= 10700
|
||||
NumProvidedSystems = 1;
|
||||
#endif
|
||||
strcpy(RemoteIp, "");
|
||||
FilterSockBufSize = 0;
|
||||
}
|
||||
|
||||
bool cStreamdevClientSetup::SetupParse(const char *Name, const char *Value) {
|
||||
@@ -28,22 +38,40 @@ bool cStreamdevClientSetup::SetupParse(const char *Name, const char *Value) {
|
||||
strcpy(RemoteIp, Value);
|
||||
}
|
||||
else if (strcmp(Name, "RemotePort") == 0) RemotePort = atoi(Value);
|
||||
else if (strcmp(Name, "Timeout") == 0) Timeout = atoi(Value);
|
||||
else if (strcmp(Name, "StreamFilters") == 0) StreamFilters = atoi(Value);
|
||||
else if (strcmp(Name, "SyncEPG") == 0) SyncEPG = atoi(Value);
|
||||
else if (strcmp(Name, "HideMenuEntry") == 0) HideMenuEntry = atoi(Value);
|
||||
else if (strcmp(Name, "LivePriority") == 0) LivePriority = atoi(Value);
|
||||
else if (strcmp(Name, "MinPriority") == 0) MinPriority = atoi(Value);
|
||||
else if (strcmp(Name, "MaxPriority") == 0) MaxPriority = atoi(Value);
|
||||
else if (strcmp(Name, "FilterSockBufSize") == 0) FilterSockBufSize = atoi(Value);
|
||||
#if APIVERSNUM >= 10700
|
||||
else if (strcmp(Name, "NumProvidedSystems") == 0) NumProvidedSystems = atoi(Value);
|
||||
#endif
|
||||
else return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
cStreamdevClientMenuSetupPage::cStreamdevClientMenuSetupPage(void) {
|
||||
cStreamdevClientMenuSetupPage::cStreamdevClientMenuSetupPage(cPluginStreamdevClient *Plugin) {
|
||||
m_Plugin = Plugin;
|
||||
m_NewSetup = StreamdevClientSetup;
|
||||
|
||||
AddBoolEdit (tr("Hide Mainmenu Entry"),m_NewSetup.HideMenuEntry);
|
||||
AddBoolEdit (tr("Start Client"), m_NewSetup.StartClient);
|
||||
AddIpEdit (tr("Remote IP"), m_NewSetup.RemoteIp);
|
||||
AddShortEdit(tr("Remote Port"), m_NewSetup.RemotePort);
|
||||
AddBoolEdit (tr("Filter Streaming"), m_NewSetup.StreamFilters);
|
||||
AddBoolEdit (tr("Synchronize EPG"), m_NewSetup.SyncEPG);
|
||||
Add(new cMenuEditBoolItem(tr("Hide Mainmenu Entry"), &m_NewSetup.HideMenuEntry));
|
||||
Add(new cMenuEditIntItem (tr("Simultaneously used Devices"), &m_NewSetup.StartClient, 0, STREAMDEV_MAXDEVICES));
|
||||
Add(new cMenuEditIpItem (tr("Remote IP"), m_NewSetup.RemoteIp));
|
||||
Add(new cMenuEditIntItem (tr("Remote Port"), &m_NewSetup.RemotePort, 0, 65535));
|
||||
Add(new cMenuEditIntItem (tr("Timeout (s)"), &m_NewSetup.Timeout, 1, 15));
|
||||
Add(new cMenuEditBoolItem(tr("Filter Streaming"), &m_NewSetup.StreamFilters));
|
||||
if(m_NewSetup.StreamFilters)
|
||||
Add(new cMenuEditIntItem (tr("Filter SockBufSize"), &m_NewSetup.FilterSockBufSize, 0, 8192000));
|
||||
Add(new cMenuEditIntItem (tr("Live TV Priority"), &m_NewSetup.LivePriority, MINPRIORITY, MAXPRIORITY));
|
||||
Add(new cMenuEditIntItem (tr("Minimum Priority"), &m_NewSetup.MinPriority, MINPRIORITY, MAXPRIORITY));
|
||||
Add(new cMenuEditIntItem (tr("Maximum Priority"), &m_NewSetup.MaxPriority, MINPRIORITY, MAXPRIORITY));
|
||||
#if APIVERSNUM >= 10715
|
||||
Add(new cMenuEditIntItem (tr("Broadcast Systems / Cost"), &m_NewSetup.NumProvidedSystems, 1, 15));
|
||||
#elif APIVERSNUM >= 10700
|
||||
Add(new cMenuEditIntItem (tr("Broadcast Systems / Cost"), &m_NewSetup.NumProvidedSystems, 1, 4));
|
||||
#endif
|
||||
SetCurrent(Get(0));
|
||||
}
|
||||
|
||||
@@ -51,23 +79,25 @@ cStreamdevClientMenuSetupPage::~cStreamdevClientMenuSetupPage() {
|
||||
}
|
||||
|
||||
void cStreamdevClientMenuSetupPage::Store(void) {
|
||||
if (m_NewSetup.StartClient != StreamdevClientSetup.StartClient) {
|
||||
if (m_NewSetup.StartClient)
|
||||
cStreamdevDevice::Init();
|
||||
}
|
||||
|
||||
SetupStore("StartClient", m_NewSetup.StartClient);
|
||||
if (strcmp(m_NewSetup.RemoteIp, "") == 0)
|
||||
SetupStore("RemoteIp", "-none-");
|
||||
else
|
||||
SetupStore("RemoteIp", m_NewSetup.RemoteIp);
|
||||
SetupStore("RemotePort", m_NewSetup.RemotePort);
|
||||
SetupStore("Timeout", m_NewSetup.Timeout);
|
||||
SetupStore("StreamFilters", m_NewSetup.StreamFilters);
|
||||
SetupStore("SyncEPG", m_NewSetup.SyncEPG);
|
||||
SetupStore("HideMenuEntry", m_NewSetup.HideMenuEntry);
|
||||
SetupStore("LivePriority", m_NewSetup.LivePriority);
|
||||
SetupStore("MinPriority", m_NewSetup.MinPriority);
|
||||
SetupStore("MaxPriority", m_NewSetup.MaxPriority);
|
||||
#if APIVERSNUM >= 10700
|
||||
SetupStore("NumProvidedSystems", m_NewSetup.NumProvidedSystems);
|
||||
#endif
|
||||
SetupStore("FilterSockBufSize", m_NewSetup.FilterSockBufSize);
|
||||
|
||||
StreamdevClientSetup = m_NewSetup;
|
||||
|
||||
cStreamdevDevice::ReInit();
|
||||
m_Plugin->Initialize();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: setup.h,v 1.4 2008/04/07 14:50:32 schmirl Exp $
|
||||
* $Id: setup.h,v 1.7 2010/06/08 05:55:17 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SETUPCLIENT_H
|
||||
@@ -15,22 +15,32 @@ struct cStreamdevClientSetup {
|
||||
int StartClient;
|
||||
char RemoteIp[20];
|
||||
int RemotePort;
|
||||
int Timeout;
|
||||
int StreamFilters;
|
||||
int SyncEPG;
|
||||
int FilterSockBufSize;
|
||||
int HideMenuEntry;
|
||||
int LivePriority;
|
||||
int MinPriority;
|
||||
int MaxPriority;
|
||||
#if APIVERSNUM >= 10700
|
||||
int NumProvidedSystems;
|
||||
#endif
|
||||
};
|
||||
|
||||
extern cStreamdevClientSetup StreamdevClientSetup;
|
||||
|
||||
class cStreamdevClientMenuSetupPage: public cStreamdevMenuSetupPage {
|
||||
class cPluginStreamdevClient;
|
||||
|
||||
class cStreamdevClientMenuSetupPage: public cMenuSetupPage {
|
||||
private:
|
||||
cStreamdevClientSetup m_NewSetup;
|
||||
cPluginStreamdevClient *m_Plugin;
|
||||
cStreamdevClientSetup m_NewSetup;
|
||||
|
||||
protected:
|
||||
virtual void Store(void);
|
||||
|
||||
public:
|
||||
cStreamdevClientMenuSetupPage(void);
|
||||
cStreamdevClientMenuSetupPage(cPluginStreamdevClient *Plugin);
|
||||
virtual ~cStreamdevClientMenuSetupPage();
|
||||
};
|
||||
|
||||
|
||||
330
client/socket.c
330
client/socket.c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: socket.c,v 1.11 2008/04/07 14:40:40 schmirl Exp $
|
||||
* $Id: socket.c,v 1.15 2010/08/18 10:26:55 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include <tools/select.h>
|
||||
@@ -11,16 +11,24 @@
|
||||
|
||||
#define MINLOGREPEAT 10 //don't log connect failures too often (seconds)
|
||||
|
||||
#include "client/socket.h"
|
||||
#include "client/setup.h"
|
||||
#include "common.h"
|
||||
#include "i18n.h"
|
||||
// timeout for writing to command socket
|
||||
#define WRITE_TIMEOUT_MS 200
|
||||
#define QUIT_TIMEOUT_MS 500
|
||||
|
||||
cClientSocket ClientSocket;
|
||||
#include "client/socket.h"
|
||||
#include "common.h"
|
||||
|
||||
cClientSocket::cClientSocket(void)
|
||||
{
|
||||
memset(m_DataSockets, 0, sizeof(cTBSocket*) * si_Count);
|
||||
m_ServerVersion = 0;
|
||||
m_Priority = -100;
|
||||
m_Prio = false;
|
||||
m_Abort = false;
|
||||
m_LastSignalUpdate = 0;
|
||||
m_LastSignalStrength = -1;
|
||||
m_LastSignalQuality = -1;
|
||||
m_LastDev = -1;
|
||||
Reset();
|
||||
}
|
||||
|
||||
@@ -32,53 +40,62 @@ cClientSocket::~cClientSocket()
|
||||
|
||||
void cClientSocket::Reset(void)
|
||||
{
|
||||
for (int it = 0; it < si_Count; ++it) {
|
||||
if (m_DataSockets[it] != NULL)
|
||||
DELETENULL(m_DataSockets[it]);
|
||||
}
|
||||
for (int it = 0; it < si_Count; ++it)
|
||||
DELETENULL(m_DataSockets[it]);
|
||||
m_Priority = -100;
|
||||
}
|
||||
|
||||
cTBSocket *cClientSocket::DataSocket(eSocketId Id) const {
|
||||
return m_DataSockets[Id];
|
||||
}
|
||||
|
||||
bool cClientSocket::Command(const std::string &Command, uint Expected, uint TimeoutMs)
|
||||
bool cClientSocket::Command(const std::string &Command, uint Expected)
|
||||
{
|
||||
errno = 0;
|
||||
uint code = 0;
|
||||
std::string buffer;
|
||||
if (Send(Command) && Receive(Command, &code, &buffer)) {
|
||||
if (code == Expected)
|
||||
return true;
|
||||
|
||||
dsyslog("streamdev-client: Command '%s' rejected by %s:%d: %s",
|
||||
Command.c_str(), RemoteIp().c_str(), RemotePort(), buffer.c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool cClientSocket::Send(const std::string &Command)
|
||||
{
|
||||
std::string pkt = Command + "\015\012";
|
||||
Dprintf("OUT: |%s|\n", Command.c_str());
|
||||
|
||||
cTimeMs starttime;
|
||||
if (!TimedWrite(pkt.c_str(), pkt.size(), TimeoutMs)) {
|
||||
esyslog("Streamdev: Lost connection to %s:%d: %s", RemoteIp().c_str(), RemotePort(),
|
||||
strerror(errno));
|
||||
errno = 0;
|
||||
if (!TimedWrite(pkt.c_str(), pkt.size(), WRITE_TIMEOUT_MS)) {
|
||||
esyslog("ERROR: streamdev-client: Failed sending command '%s' to %s:%d: %s",
|
||||
Command.c_str(), RemoteIp().c_str(), RemotePort(), strerror(errno));
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t elapsed = starttime.Elapsed();
|
||||
if (Expected != 0) { // XXX+ What if elapsed > TimeoutMs?
|
||||
TimeoutMs -= elapsed;
|
||||
return Expect(Expected, NULL, TimeoutMs);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cClientSocket::Expect(uint Expected, std::string *Result, uint TimeoutMs) {
|
||||
char *endptr;
|
||||
#define TIMEOUT_MS 1000
|
||||
bool cClientSocket::Receive(const std::string &Command, uint *Code, std::string *Result, uint TimeoutMs) {
|
||||
int bufcount;
|
||||
bool res;
|
||||
|
||||
errno = 0;
|
||||
|
||||
if ((bufcount = ReadUntil(m_Buffer, sizeof(m_Buffer) - 1, "\012", TimeoutMs)) == -1) {
|
||||
esyslog("Streamdev: Lost connection to %s:%d: %s", RemoteIp().c_str(), RemotePort(),
|
||||
strerror(errno));
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
do
|
||||
{
|
||||
errno = 0;
|
||||
bufcount = ReadUntil(m_Buffer, sizeof(m_Buffer) - 1, "\012", TimeoutMs < TIMEOUT_MS ? TimeoutMs : TIMEOUT_MS);
|
||||
if (bufcount == -1) {
|
||||
if (m_Abort)
|
||||
return false;
|
||||
if (errno != ETIMEDOUT || TimeoutMs <= TIMEOUT_MS) {
|
||||
esyslog("ERROR: streamdev-client: Failed reading reply to '%s' from %s:%d: %s",
|
||||
Command.c_str(), RemoteIp().c_str(), RemotePort(), strerror(errno));
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
TimeoutMs -= TIMEOUT_MS;
|
||||
}
|
||||
} while (bufcount == -1);
|
||||
if (m_Buffer[bufcount - 1] == '\015')
|
||||
--bufcount;
|
||||
m_Buffer[bufcount] = '\0';
|
||||
@@ -86,9 +103,9 @@ bool cClientSocket::Expect(uint Expected, std::string *Result, uint TimeoutMs) {
|
||||
|
||||
if (Result != NULL)
|
||||
*Result = m_Buffer;
|
||||
|
||||
res = strtoul(m_Buffer, &endptr, 10) == Expected;
|
||||
return res;
|
||||
if (Code != NULL)
|
||||
*Code = strtoul(m_Buffer, NULL, 10);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cClientSocket::CheckConnection(void) {
|
||||
@@ -97,25 +114,22 @@ bool cClientSocket::CheckConnection(void) {
|
||||
if (IsOpen()) {
|
||||
cTBSelect select;
|
||||
|
||||
Dprintf("connection open\n");
|
||||
|
||||
// XXX+ check if connection is still alive (is there a better way?)
|
||||
// There REALLY shouldn't be anything readable according to PROTOCOL here
|
||||
// If there is, assume it's an eof signal (subseq. read would return 0)
|
||||
select.Add(*this, false);
|
||||
int res;
|
||||
if ((res = select.Select(0)) == 0) {
|
||||
Dprintf("select said nothing happened\n");
|
||||
return true;
|
||||
}
|
||||
Dprintf("closing connection (res was %d)", res);
|
||||
Dprintf("closing connection (res was %d)\n", res);
|
||||
Close();
|
||||
}
|
||||
|
||||
if (!Connect(StreamdevClientSetup.RemoteIp, StreamdevClientSetup.RemotePort)){
|
||||
if (!Connect(StreamdevClientSetup.RemoteIp, StreamdevClientSetup.RemotePort, StreamdevClientSetup.Timeout * 1000)){
|
||||
static time_t lastTime = 0;
|
||||
if (time(NULL) - lastTime > MINLOGREPEAT) {
|
||||
esyslog("ERROR: Streamdev: Couldn't connect to %s:%d: %s",
|
||||
esyslog("ERROR: streamdev-client: Couldn't connect to %s:%d: %s",
|
||||
(const char*)StreamdevClientSetup.RemoteIp,
|
||||
StreamdevClientSetup.RemotePort, strerror(errno));
|
||||
lastTime = time(NULL);
|
||||
@@ -123,28 +137,51 @@ bool cClientSocket::CheckConnection(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Expect(220)) {
|
||||
if (errno == 0)
|
||||
esyslog("ERROR: Streamdev: Didn't receive greeting from %s:%d",
|
||||
RemoteIp().c_str(), RemotePort());
|
||||
uint code = 0;
|
||||
std::string buffer;
|
||||
if (!Receive("<connect>", &code, &buffer)) {
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
if (code != 220) {
|
||||
esyslog("ERROR: streamdev-client: Didn't receive greeting from %s:%d: %s",
|
||||
RemoteIp().c_str(), RemotePort(), buffer.c_str());
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Command("CAPS TSPIDS", 220)) {
|
||||
if (errno == 0)
|
||||
esyslog("ERROR: Streamdev: Couldn't negotiate capabilities on %s:%d",
|
||||
RemoteIp().c_str(), RemotePort());
|
||||
Close();
|
||||
return false;
|
||||
unsigned int major, minor;
|
||||
if (sscanf(buffer.c_str(), "%*u VTP/%u.%u", &major, &minor) == 2)
|
||||
m_ServerVersion = major * 100 + minor;
|
||||
|
||||
if (m_ServerVersion == 0) {
|
||||
if (!Command("CAPS TSPIDS", 220)) {
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *Filters = "";
|
||||
if(Command("CAPS FILTERS", 220))
|
||||
Filters = ",FILTERS";
|
||||
|
||||
const char *Prio = "";
|
||||
if(Command("CAPS PRIO", 220)) {
|
||||
Prio = ",PRIO";
|
||||
m_Prio = true;
|
||||
}
|
||||
isyslog("streamdev-client: Connected to server %s:%d using capabilities TSPIDS%s%s",
|
||||
RemoteIp().c_str(), RemotePort(), Filters, Prio);
|
||||
}
|
||||
else {
|
||||
if(!Command("VERS 1.0", 220)) {
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
m_Prio = true;
|
||||
isyslog("streamdev-client: Connected to server %s:%d using protocol version %u.%u",
|
||||
RemoteIp().c_str(), RemotePort(), major, minor);
|
||||
}
|
||||
|
||||
const char *Filters = "";
|
||||
if(Command("CAPS FILTERS", 220))
|
||||
Filters = ",FILTERS";
|
||||
|
||||
isyslog("Streamdev: Connected to server %s:%d using capabilities TSPIDS%s",
|
||||
RemoteIp().c_str(), RemotePort(), Filters);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -155,17 +192,19 @@ bool cClientSocket::ProvidesChannel(const cChannel *Channel, int Priority) {
|
||||
|
||||
std::string command = (std::string)"PROV " + (const char*)itoa(Priority) + " "
|
||||
+ (const char*)Channel->GetChannelID().ToString();
|
||||
if (!Command(command))
|
||||
if (!Send(command))
|
||||
return false;
|
||||
|
||||
uint code;
|
||||
std::string buffer;
|
||||
if (!Expect(220, &buffer)) {
|
||||
if (buffer.substr(0, 3) != "560" && errno == 0)
|
||||
esyslog("ERROR: Streamdev: Couldn't check if %s:%d provides channel %s",
|
||||
RemoteIp().c_str(), RemotePort(), Channel->Name());
|
||||
if (!Receive(command, &code, &buffer))
|
||||
return false;
|
||||
if (code != 220 && code != 560) {
|
||||
esyslog("streamdev-client: Unexpected reply to '%s' from %s:%d: %s",
|
||||
command.c_str(), RemoteIp().c_str(), RemotePort(), buffer.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return code == 220;
|
||||
}
|
||||
|
||||
bool cClientSocket::CreateDataConnection(eSocketId Id) {
|
||||
@@ -177,7 +216,7 @@ bool cClientSocket::CreateDataConnection(eSocketId Id) {
|
||||
DELETENULL(m_DataSockets[Id]);
|
||||
|
||||
if (!listen.Listen(LocalIp(), 0, 1)) {
|
||||
esyslog("ERROR: Streamdev: Couldn't create data connection: %s",
|
||||
esyslog("ERROR: streamdev-client: Couldn't create data connection: %s",
|
||||
strerror(errno));
|
||||
return false;
|
||||
}
|
||||
@@ -192,13 +231,8 @@ bool cClientSocket::CreateDataConnection(eSocketId Id) {
|
||||
|
||||
CMD_LOCK;
|
||||
|
||||
if (!Command(command, 220)) {
|
||||
Dprintf("error: %m\n");
|
||||
if (errno == 0)
|
||||
esyslog("ERROR: Streamdev: Couldn't establish data connection to %s:%d",
|
||||
RemoteIp().c_str(), RemotePort());
|
||||
if (!Command(command, 220))
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The server SHOULD do the following:
|
||||
* - get PORT command
|
||||
@@ -208,7 +242,7 @@ bool cClientSocket::CreateDataConnection(eSocketId Id) {
|
||||
|
||||
m_DataSockets[Id] = new cTBSocket;
|
||||
if (!m_DataSockets[Id]->Accept(listen)) {
|
||||
esyslog("ERROR: Streamdev: Couldn't establish data connection to %s:%d%s%s",
|
||||
esyslog("ERROR: streamdev-client: Couldn't establish data connection to %s:%d%s%s",
|
||||
RemoteIp().c_str(), RemotePort(), errno == 0 ? "" : ": ",
|
||||
errno == 0 ? "" : strerror(errno));
|
||||
DELETENULL(m_DataSockets[Id]);
|
||||
@@ -219,18 +253,12 @@ bool cClientSocket::CreateDataConnection(eSocketId Id) {
|
||||
}
|
||||
|
||||
bool cClientSocket::CloseDataConnection(eSocketId Id) {
|
||||
//if (!CheckConnection()) return false;
|
||||
|
||||
CMD_LOCK;
|
||||
|
||||
if(Id == siLive || Id == siLiveFilter)
|
||||
if (m_DataSockets[Id] != NULL) {
|
||||
std::string command = (std::string)"ABRT " + (const char*)itoa(Id);
|
||||
if (!Command(command, 220)) {
|
||||
if (errno == 0)
|
||||
esyslog("ERROR: Streamdev: Couldn't cleanly close data connection");
|
||||
//return false;
|
||||
}
|
||||
Command(command, 220);
|
||||
DELETENULL(m_DataSockets[Id]);
|
||||
}
|
||||
return true;
|
||||
@@ -242,29 +270,63 @@ bool cClientSocket::SetChannelDevice(const cChannel *Channel) {
|
||||
CMD_LOCK;
|
||||
|
||||
std::string command = (std::string)"TUNE "
|
||||
+ (const char*)Channel->GetChannelID().ToString();
|
||||
if (!Command(command, 220)) {
|
||||
if (errno == 0)
|
||||
esyslog("ERROR: Streamdev: Couldn't tune %s:%d to channel %s",
|
||||
RemoteIp().c_str(), RemotePort(), Channel->Name());
|
||||
+ (const char*)Channel->GetChannelID().ToString();
|
||||
if (!Command(command, 220))
|
||||
return false;
|
||||
}
|
||||
|
||||
m_LastSignalUpdate = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cClientSocket::SetPriority(int Priority) {
|
||||
if (Priority == m_Priority)
|
||||
return true;
|
||||
|
||||
if (!CheckConnection()) return false;
|
||||
|
||||
CMD_LOCK;
|
||||
|
||||
std::string command = (std::string)"PRIO " + (const char*)itoa(Priority);
|
||||
if (!Command(command, 220))
|
||||
return false;
|
||||
|
||||
m_Priority = Priority;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cClientSocket::GetSignal(int *SignalStrength, int *SignalQuality, int *Dev) {
|
||||
if (!CheckConnection()) return -1;
|
||||
|
||||
CMD_LOCK;
|
||||
|
||||
if (m_LastSignalUpdate != time(NULL)) {
|
||||
uint code = 0;
|
||||
std::string buffer;
|
||||
std::string command("SGNL");
|
||||
if (!Send(command) || !Receive(command, &code, &buffer) || code != 220
|
||||
|| sscanf(buffer.c_str(), "%*d %d %d:%d", &m_LastDev, &m_LastSignalStrength, &m_LastSignalQuality) != 3) {
|
||||
m_LastDev = -1;
|
||||
m_LastSignalStrength = -1;
|
||||
m_LastSignalQuality = -1;
|
||||
}
|
||||
m_LastSignalUpdate = time(NULL);
|
||||
}
|
||||
if (SignalStrength)
|
||||
*SignalStrength = m_LastSignalStrength;
|
||||
if (SignalQuality)
|
||||
*SignalQuality = m_LastSignalQuality;
|
||||
if (Dev)
|
||||
*Dev = m_LastDev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool cClientSocket::SetPid(int Pid, bool On) {
|
||||
if (!CheckConnection()) return false;
|
||||
|
||||
CMD_LOCK;
|
||||
|
||||
std::string command = (std::string)(On ? "ADDP " : "DELP ") + (const char*)itoa(Pid);
|
||||
if (!Command(command, 220)) {
|
||||
if (errno == 0)
|
||||
esyslog("Streamdev: Pid %d not available from %s:%d", Pid, LocalIp().c_str(),
|
||||
LocalPort());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return Command(command, 220);
|
||||
}
|
||||
|
||||
bool cClientSocket::SetFilter(ushort Pid, uchar Tid, uchar Mask, bool On) {
|
||||
@@ -274,13 +336,7 @@ bool cClientSocket::SetFilter(ushort Pid, uchar Tid, uchar Mask, bool On) {
|
||||
|
||||
std::string command = (std::string)(On ? "ADDF " : "DELF ") + (const char*)itoa(Pid)
|
||||
+ " " + (const char*)itoa(Tid) + " " + (const char*)itoa(Mask);
|
||||
if (!Command(command, 220)) {
|
||||
if (errno == 0)
|
||||
esyslog("Streamdev: Filter %hu, %hhu, %hhu not available from %s:%d",
|
||||
Pid, Tid, Mask, LocalIp().c_str(), LocalPort());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return Command(command, 220);
|
||||
}
|
||||
|
||||
bool cClientSocket::CloseDvr(void) {
|
||||
@@ -290,73 +346,20 @@ bool cClientSocket::CloseDvr(void) {
|
||||
|
||||
if (m_DataSockets[siLive] != NULL) {
|
||||
std::string command = (std::string)"ABRT " + (const char*)itoa(siLive);
|
||||
if (!Command(command, 220)) {
|
||||
if (errno == 0)
|
||||
esyslog("ERROR: Streamdev: Couldn't cleanly close data connection");
|
||||
if (!Command(command, 220))
|
||||
return false;
|
||||
}
|
||||
|
||||
DELETENULL(m_DataSockets[siLive]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cClientSocket::SynchronizeEPG(void) {
|
||||
std::string buffer;
|
||||
bool result;
|
||||
FILE *epgfd;
|
||||
|
||||
if (!CheckConnection()) return false;
|
||||
|
||||
isyslog("Streamdev: Synchronizing EPG from server\n");
|
||||
bool cClientSocket::Quit(void) {
|
||||
m_Abort = true;
|
||||
if (!IsOpen()) return false;
|
||||
|
||||
CMD_LOCK;
|
||||
|
||||
if (!Command("LSTE"))
|
||||
return false;
|
||||
|
||||
if ((epgfd = tmpfile()) == NULL) {
|
||||
esyslog("ERROR: Streamdev: Error while processing EPG data: %s",
|
||||
strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
while ((result = Expect(215, &buffer))) {
|
||||
if (buffer[3] == ' ') break;
|
||||
fputs(buffer.c_str() + 4, epgfd);
|
||||
fputc('\n', epgfd);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
if (errno == 0)
|
||||
esyslog("ERROR: Streamdev: Couldn't fetch EPG data from %s:%d",
|
||||
RemoteIp().c_str(), RemotePort());
|
||||
fclose(epgfd);
|
||||
return false;
|
||||
}
|
||||
|
||||
rewind(epgfd);
|
||||
if (cSchedules::Read(epgfd))
|
||||
cSchedules::Cleanup(true);
|
||||
else {
|
||||
esyslog("ERROR: Streamdev: Parsing EPG data failed");
|
||||
fclose(epgfd);
|
||||
return false;
|
||||
}
|
||||
fclose(epgfd);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cClientSocket::Quit(void) {
|
||||
bool res;
|
||||
|
||||
if (!CheckConnection()) return false;
|
||||
|
||||
if (!(res = Command("QUIT", 221))) {
|
||||
if (errno == 0)
|
||||
esyslog("ERROR: Streamdev: Couldn't quit command connection to %s:%d",
|
||||
RemoteIp().c_str(), RemotePort());
|
||||
}
|
||||
std::string command("QUIT");
|
||||
bool res = Send(command) && Receive(command, NULL, NULL, QUIT_TIMEOUT_MS);
|
||||
Close();
|
||||
return res;
|
||||
}
|
||||
@@ -366,10 +369,5 @@ bool cClientSocket::SuspendServer(void) {
|
||||
|
||||
CMD_LOCK;
|
||||
|
||||
if (!Command("SUSP", 220)) {
|
||||
if (errno == 0)
|
||||
esyslog("ERROR: Streamdev: Couldn't suspend server");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return Command("SUSP", 220);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: socket.h,v 1.6 2008/04/07 14:40:40 schmirl Exp $
|
||||
* $Id: socket.h,v 1.8 2010/08/18 10:26:55 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_CLIENT_CONNECTION_H
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <tools/socket.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "client/setup.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
@@ -20,19 +21,27 @@ private:
|
||||
cTBSocket *m_DataSockets[si_Count];
|
||||
cMutex m_Mutex;
|
||||
char m_Buffer[BUFSIZ + 1]; // various uses
|
||||
unsigned int m_ServerVersion;
|
||||
bool m_Prio; // server supports command PRIO
|
||||
int m_Priority; // current device priority
|
||||
bool m_Abort; // quit command pending
|
||||
|
||||
time_t m_LastSignalUpdate;
|
||||
int m_LastSignalStrength;
|
||||
int m_LastSignalQuality;
|
||||
int m_LastDev;
|
||||
protected:
|
||||
/* Send Command, and return true if the command results in Expected.
|
||||
Returns false on failure, setting errno appropriately if it has been
|
||||
a system failure. If Expected is zero, returns immediately after
|
||||
sending the command. */
|
||||
bool Command(const std::string &Command, uint Expected = 0, uint TimeoutMs = 1500);
|
||||
Returns false on failure. */
|
||||
bool Command(const std::string &Command, uint Expected);
|
||||
|
||||
/* Fetch results from an ongoing Command called with Expected == 0. Returns
|
||||
true if the response has the code Expected, returning an internal buffer
|
||||
in the array pointer pointed to by Result. Returns false on failure,
|
||||
setting errno appropriately if it has been a system failure. */
|
||||
bool Expect(uint Expected, std::string *Result = NULL, uint TimeoutMs = 1500);
|
||||
/* Send the given command. Returns false on failure. */
|
||||
bool Send(const std::string &Command);
|
||||
|
||||
/* Fetch results from an ongoing Command. The status code and the
|
||||
buffer holding the server's response are stored in Code and Result
|
||||
if non-NULL. Returns false on failure. */
|
||||
bool Receive(const std::string &Command, uint *Code = NULL, std::string *Result = NULL, uint TimeoutMs = StreamdevClientSetup.Timeout * 1000);
|
||||
|
||||
public:
|
||||
cClientSocket(void);
|
||||
@@ -45,16 +54,18 @@ public:
|
||||
bool CreateDataConnection(eSocketId Id);
|
||||
bool CloseDataConnection(eSocketId Id);
|
||||
bool SetChannelDevice(const cChannel *Channel);
|
||||
bool SupportsPrio() { return m_Prio; }
|
||||
unsigned int ServerVersion() { return m_ServerVersion; }
|
||||
int Priority() const { return m_Priority; }
|
||||
bool SetPriority(int Priority);
|
||||
bool SetPid(int Pid, bool On);
|
||||
bool SetFilter(ushort Pid, uchar Tid, uchar Mask, bool On);
|
||||
bool GetSignal(int *SignalStrength, int *SignalQuality, int *Dev);
|
||||
bool CloseDvr(void);
|
||||
bool SynchronizeEPG(void);
|
||||
bool SuspendServer(void);
|
||||
bool Quit(void);
|
||||
|
||||
cTBSocket *DataSocket(eSocketId Id) const;
|
||||
};
|
||||
|
||||
extern class cClientSocket ClientSocket;
|
||||
|
||||
#endif // VDR_STREAMDEV_CLIENT_CONNECTION_H
|
||||
|
||||
72
client/streamdev-client.c
Normal file
72
client/streamdev-client.c
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* streamdev.c: A plugin for the Video Disk Recorder
|
||||
*
|
||||
* See the README file for copyright information and how to reach the author.
|
||||
*
|
||||
* $Id: streamdev-client.c,v 1.3 2010/08/18 10:26:56 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include "streamdev-client.h"
|
||||
#include "client/device.h"
|
||||
#include "client/setup.h"
|
||||
|
||||
#if !defined(APIVERSNUM) || APIVERSNUM < 10516
|
||||
#error "VDR-1.5.16 API version or greater is required!"
|
||||
#endif
|
||||
|
||||
const char *cPluginStreamdevClient::DESCRIPTION = trNOOP("VTP Streaming Client");
|
||||
|
||||
cPluginStreamdevClient::cPluginStreamdevClient(void): m_Devices() {
|
||||
}
|
||||
|
||||
cPluginStreamdevClient::~cPluginStreamdevClient() {
|
||||
}
|
||||
|
||||
const char *cPluginStreamdevClient::Description(void) {
|
||||
return tr(DESCRIPTION);
|
||||
}
|
||||
|
||||
bool cPluginStreamdevClient::Initialize(void) {
|
||||
for (int i = 0; i < STREAMDEV_MAXDEVICES; i++) {
|
||||
if (m_Devices[i])
|
||||
m_Devices[i]->ReInit(i >= StreamdevClientSetup.StartClient);
|
||||
else if (i < StreamdevClientSetup.StartClient)
|
||||
m_Devices[i] = new cStreamdevDevice();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *cPluginStreamdevClient::MainMenuEntry(void) {
|
||||
return StreamdevClientSetup.StartClient && !StreamdevClientSetup.HideMenuEntry ? tr("Suspend Server") : NULL;
|
||||
}
|
||||
|
||||
cOsdObject *cPluginStreamdevClient::MainMenuAction(void) {
|
||||
if (StreamdevClientSetup.StartClient && m_Devices[0]->SuspendServer())
|
||||
Skins.Message(mtInfo, tr("Server is suspended"));
|
||||
else
|
||||
Skins.Message(mtError, tr("Couldn't suspend Server!"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cMenuSetupPage *cPluginStreamdevClient::SetupMenu(void) {
|
||||
return new cStreamdevClientMenuSetupPage(this);
|
||||
}
|
||||
|
||||
bool cPluginStreamdevClient::SetupParse(const char *Name, const char *Value) {
|
||||
return StreamdevClientSetup.SetupParse(Name, Value);
|
||||
}
|
||||
|
||||
bool cPluginStreamdevClient::Service(const char *Id, void *Data) {
|
||||
if (!strcmp(Id, LOOP_PREVENTION_SERVICE)) {
|
||||
cStreamdevDevice::DenyChannel((const cChannel*) Data);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cPluginStreamdevClient::MainThreadHook(void) {
|
||||
for (int i = 0; i < StreamdevClientSetup.StartClient; i++)
|
||||
m_Devices[i]->UpdatePriority();
|
||||
}
|
||||
|
||||
VDRPLUGINCREATOR(cPluginStreamdevClient); // Don't touch this!
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: streamdev-client.h,v 1.1 2004/12/30 22:43:59 lordjaxom Exp $
|
||||
* $Id: streamdev-client.h,v 1.3 2010/08/18 10:26:56 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEVCLIENT_H
|
||||
@@ -9,21 +9,26 @@
|
||||
|
||||
#include <vdr/plugin.h>
|
||||
|
||||
#define STREAMDEV_MAXDEVICES 8
|
||||
class cStreamdevDevice;
|
||||
|
||||
class cPluginStreamdevClient : public cPlugin {
|
||||
private:
|
||||
static const char *DESCRIPTION;
|
||||
static const char *DESCRIPTION;
|
||||
cStreamdevDevice *m_Devices[STREAMDEV_MAXDEVICES];
|
||||
|
||||
public:
|
||||
cPluginStreamdevClient(void);
|
||||
virtual ~cPluginStreamdevClient();
|
||||
virtual const char *Version(void) { return VERSION; }
|
||||
virtual const char *Description(void);
|
||||
virtual bool Start(void);
|
||||
virtual void Housekeeping(void);
|
||||
virtual bool Initialize(void);
|
||||
virtual const char *MainMenuEntry(void);
|
||||
virtual cOsdObject *MainMenuAction(void);
|
||||
virtual cMenuSetupPage *SetupMenu(void);
|
||||
virtual bool SetupParse(const char *Name, const char *Value);
|
||||
virtual bool Service(const char *Id, void *Data = NULL);
|
||||
virtual void MainThreadHook(void);
|
||||
};
|
||||
|
||||
#endif // VDR_STREAMDEVCLIENT_H
|
||||
140
common.c
140
common.c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: common.c,v 1.7 2008/04/07 14:27:27 schmirl Exp $
|
||||
* $Id: common.c,v 1.12 2010/07/19 13:49:24 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include <vdr/channels.h>
|
||||
@@ -7,146 +7,12 @@
|
||||
|
||||
#include "common.h"
|
||||
#include "tools/select.h"
|
||||
#include "i18n.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
const char *VERSION = "0.4.0-pre";
|
||||
const char *VERSION = "0.6.1-git";
|
||||
|
||||
const char *StreamTypes[st_Count] = {
|
||||
"TS",
|
||||
"PES",
|
||||
"PS",
|
||||
"ES",
|
||||
"Extern",
|
||||
"", // used internally only
|
||||
};
|
||||
|
||||
const char *SuspendModes[sm_Count] = {
|
||||
"Offer suspend mode",
|
||||
"Always suspended",
|
||||
"Never suspended"
|
||||
};
|
||||
|
||||
const char IpCharacters[] = "0123456789.";
|
||||
|
||||
char *GetNextLine(char *String, uint Length, uint &Offset) {
|
||||
char *last, *first;
|
||||
|
||||
first = String + Offset;
|
||||
for (last = first; last < String + Length; ++last) {
|
||||
if (*last == '\012') {
|
||||
if (*(last - 1) == '\015')
|
||||
*(last - 1) = '\0';
|
||||
|
||||
*last++ = '\0';
|
||||
Dprintf("IN: |%s|\n", first);
|
||||
Offset = last - String;
|
||||
return first;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const cChannel *ChannelFromString(const char *String, int *Apid) {
|
||||
const cChannel *channel = NULL;
|
||||
char *string = strdup(String);
|
||||
char *ptr, *end;
|
||||
int apididx = 0;
|
||||
|
||||
if ((ptr = strrchr(string, '+')) != NULL) {
|
||||
*(ptr++) = '\0';
|
||||
apididx = strtoul(ptr, &end, 10);
|
||||
Dprintf("found apididx: %d\n", apididx);
|
||||
}
|
||||
|
||||
if (isnumber(string)) {
|
||||
int temp = strtol(String, NULL, 10);
|
||||
if (temp >= 1 && temp <= Channels.MaxNumber())
|
||||
channel = Channels.GetByNumber(temp);
|
||||
} else {
|
||||
channel = Channels.GetByChannelID(tChannelID::FromString(string));
|
||||
|
||||
if (channel == NULL) {
|
||||
int i = 1;
|
||||
while ((channel = Channels.GetByNumber(i, 1)) != NULL) {
|
||||
if (String == channel->Name())
|
||||
break;
|
||||
|
||||
i = channel->Number() + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (channel != NULL && apididx > 0) {
|
||||
int apid = 0, index = 1;
|
||||
|
||||
for (int i = 0; channel->Apid(i) != 0; ++i, ++index) {
|
||||
if (index == apididx) {
|
||||
apid = channel->Apid(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (apid == 0) {
|
||||
for (int i = 0; channel->Dpid(i) != 0; ++i, ++index) {
|
||||
if (index == apididx) {
|
||||
apid = channel->Dpid(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Apid != NULL)
|
||||
*Apid = apid;
|
||||
}
|
||||
|
||||
free(string);
|
||||
return channel;
|
||||
}
|
||||
|
||||
void cStreamdevMenuSetupPage::AddCategory(const char *Title) {
|
||||
char *buffer = NULL;
|
||||
|
||||
asprintf(&buffer, "--- %s -------------------------------------------------"
|
||||
"---------------", Title );
|
||||
|
||||
cOsdItem *item = new cOsdItem(buffer);
|
||||
free(buffer);
|
||||
item->SetSelectable(false);
|
||||
Add(item);
|
||||
}
|
||||
|
||||
void cStreamdevMenuSetupPage::AddBoolEdit(const char *Title, int &Value) {
|
||||
Add(new cMenuEditBoolItem(Title, &Value));
|
||||
}
|
||||
|
||||
void cStreamdevMenuSetupPage::AddIpEdit(const char *Title, char *Value) {
|
||||
Add(new cMenuEditIpItem(Title, Value));
|
||||
}
|
||||
|
||||
void cStreamdevMenuSetupPage::AddShortEdit(const char *Title, int &Value) {
|
||||
AddRangeEdit(Title, Value, 0, 65535);
|
||||
}
|
||||
|
||||
void cStreamdevMenuSetupPage::AddRangeEdit(const char *Title, int &Value,
|
||||
int Min, int Max) {
|
||||
Add(new cMenuEditIntItem(Title, &Value, Min, Max));
|
||||
}
|
||||
|
||||
void cStreamdevMenuSetupPage::AddSuspEdit(const char *Title, int &Value) {
|
||||
static const char *SuspendModesTR[sm_Count] = { NULL };
|
||||
|
||||
if (SuspendModesTR[0] == NULL) {
|
||||
for (int i = 0; i < sm_Count; ++i)
|
||||
SuspendModesTR[i] = tr(SuspendModes[i]);
|
||||
}
|
||||
|
||||
Add(new cMenuEditStraItem(Title, &Value, sm_Count, SuspendModesTR));
|
||||
}
|
||||
void cStreamdevMenuSetupPage::AddTypeEdit(const char *Title, int &Value) {
|
||||
Add(new cMenuEditStraItem(Title, &Value, st_CountSetup, StreamTypes));
|
||||
}
|
||||
const char cMenuEditIpItem::IpCharacters[] = "0123456789.";
|
||||
|
||||
cMenuEditIpItem::cMenuEditIpItem(const char *Name, char *Value):
|
||||
cMenuEditItem(Name) {
|
||||
|
||||
67
common.h
67
common.h
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: common.h,v 1.11 2008/04/07 14:40:39 schmirl Exp $
|
||||
* $Id: common.h,v 1.16 2010/07/19 13:49:24 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_COMMON_H
|
||||
@@ -17,84 +17,51 @@
|
||||
#include "tools/socket.h"
|
||||
|
||||
#ifdef DEBUG
|
||||
# include <stdio.h>
|
||||
# define Dprintf(x...) fprintf(stderr, x)
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#define Dprintf(fmt, x...) {\
|
||||
struct timespec ts;\
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);\
|
||||
fprintf(stderr, "%ld.%.3ld [%d] "fmt,\
|
||||
ts.tv_sec, ts.tv_nsec / 1000000, cThread::ThreadId(), ##x);\
|
||||
}
|
||||
#else
|
||||
# define Dprintf(x...)
|
||||
#define Dprintf(x...)
|
||||
#endif
|
||||
|
||||
# define TRANSPONDER(c1, c2) (c1->Transponder() == c2->Transponder())
|
||||
#define MAXPARSEBUFFER KILOBYTE(16)
|
||||
|
||||
# define MAXPARSEBUFFER KILOBYTE(16)
|
||||
/* Service ID for loop prevention */
|
||||
#define LOOP_PREVENTION_SERVICE "StreamdevLoopPrevention"
|
||||
|
||||
/* Check if a channel is a radio station. */
|
||||
#define ISRADIO(x) ((x)->Vpid()==0||(x)->Vpid()==1||(x)->Vpid()==0x1fff)
|
||||
|
||||
class cChannel;
|
||||
|
||||
char *GetNextLine(char *String, uint Length, uint &Offset);
|
||||
|
||||
const cChannel *ChannelFromString(const char *String, int *Apid = NULL);
|
||||
|
||||
/* Disable logging if BUFCOUNT buffer overflows occur within BUFOVERTIME
|
||||
milliseconds. Enable logging again if there is no error within BUFOVERTIME
|
||||
milliseconds. */
|
||||
#define BUFOVERTIME 5000
|
||||
#define BUFOVERCOUNT 100
|
||||
|
||||
#define POLLFAIL esyslog("Streamdev: Polling failed: %s", strerror(errno))
|
||||
#define WRITEFAIL esyslog("Streamdev: Writing failed: %s", strerror(errno))
|
||||
#define READFAIL esyslog("Streamdev: Reading failed: %s", strerror(errno))
|
||||
#define CHECKPOLL(x) if ((x)<0){POLLFAIL; return false;}
|
||||
#define CHECKWRITE(x) if ((x)<0) { WRITEFAIL; return false; }
|
||||
#define CHECKREAD(x) if ((x)<0) { READFAIL; return false; }
|
||||
|
||||
enum eStreamType {
|
||||
stTS,
|
||||
stPES,
|
||||
stPS,
|
||||
stES,
|
||||
stExtern,
|
||||
stEXT,
|
||||
stTSPIDS,
|
||||
|
||||
#define st_CountSetup (stExtern+1)
|
||||
#define st_Count (stTSPIDS+1)
|
||||
};
|
||||
|
||||
enum eSuspendMode {
|
||||
smOffer,
|
||||
smAlways,
|
||||
smNever,
|
||||
sm_Count
|
||||
st_Count
|
||||
};
|
||||
|
||||
enum eSocketId {
|
||||
siLive,
|
||||
siReplay,
|
||||
siLiveFilter,
|
||||
siDataRespond,
|
||||
si_Count
|
||||
};
|
||||
|
||||
extern const char *VERSION;
|
||||
extern const char *StreamTypes[st_Count];
|
||||
extern const char *SuspendModes[sm_Count];
|
||||
extern const char IpCharacters[];
|
||||
|
||||
class cStreamdevMenuSetupPage: public cMenuSetupPage {
|
||||
protected:
|
||||
void AddCategory(const char *Title);
|
||||
virtual void Store(void) = 0;
|
||||
|
||||
void AddBoolEdit(const char *Title, int &Value);
|
||||
void AddIpEdit(const char *Title, char *Value);
|
||||
void AddShortEdit(const char *Title, int &Value);
|
||||
void AddRangeEdit(const char *Title, int &Value, int Min, int Max);
|
||||
void AddSuspEdit(const char *Title, int &Value);
|
||||
void AddTypeEdit(const char *Title, int &Value);
|
||||
};
|
||||
|
||||
class cMenuEditIpItem: public cMenuEditItem {
|
||||
private:
|
||||
static const char IpCharacters[];
|
||||
char *value;
|
||||
int curNum;
|
||||
int pos;
|
||||
|
||||
786
i18n.c
786
i18n.c
@@ -1,786 +0,0 @@
|
||||
/*
|
||||
* $Id: i18n.c,v 1.8 2008/04/07 14:50:32 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include "i18n.h"
|
||||
|
||||
const char *i18n_name = NULL;
|
||||
|
||||
const tI18nPhrase Phrases[] = {
|
||||
{ "VDR Streaming Server", // English
|
||||
"VDR Streaming Server", // Deutsch
|
||||
"", // Slovenski
|
||||
"", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"VDR-suoratoistopalvelin", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika / Greek
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "VTP Streaming Client", // English
|
||||
"VTP Streaming Client", // Deutsch
|
||||
"", // Slovenski
|
||||
"", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"VTP-suoratoistoasiakas ", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika / Greek
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Start VDR-to-VDR Server", // English
|
||||
"VDR-zu-VDR Server starten", // Deutsch
|
||||
"", // Slovenski
|
||||
"Avvia il Server VDR-toVDR", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"Käynnistä VDR-palvelin", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika / Greek
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Start HTTP Server", // English
|
||||
"HTTP Server starten", // Deutsch
|
||||
"", // Slovenski
|
||||
"Avvia il Server HTTP", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"Käynnistä HTTP-palvelin", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "HTTP Streamtype", // English
|
||||
"HTTP Streamtyp", // Deutsch
|
||||
"", // Slovenski
|
||||
"Tipo di Stream HTTP", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"HTTP-lähetysmuoto", // Suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Start Client", // English
|
||||
"Client starten", // Deutsch
|
||||
"", // Slovenski
|
||||
"Avvia il Client", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"Käynnistä VDR-asiakas", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "VDR-to-VDR Server Port", // English
|
||||
"Port des VDR-zu-VDR Servers", // Deutsch
|
||||
"", // Slovenski
|
||||
"Porta del Server VDR-to-VDR", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"VDR-palvelimen portti", // Suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "HTTP Server Port", // English
|
||||
"Port des HTTP Servers", // Deutsch
|
||||
"", // Slovenski
|
||||
"Porta del Server HTTP", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"HTTP-palvelimen portti", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Maximum Number of Clients", // English
|
||||
"Maximalanzahl an Clients", // Deutsch
|
||||
"", // Slovenski
|
||||
"Numero Massimo di Client", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"Suurin sallittu asiakkaiden määrä", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Remote IP", // English
|
||||
"IP der Gegenseite", // Deutsch
|
||||
"", // Slovenski
|
||||
"Indirizzo IP del Server", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"Etäkoneen IP-osoite", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Remote Port", // English
|
||||
"Port der Gegenseite", // Deutsch
|
||||
"", // Slovenski
|
||||
"Porta del Server Remoto", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"Etäkoneen portti", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Remote Streamtype", // English
|
||||
"Streamtyp von Gegenseite", // Deutsch
|
||||
"", // Slovenski
|
||||
"Tipo di Stream", // Italiano (oppure Flusso ?)
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"Etäkoneen lähetysmuoto", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Common Settings", // English
|
||||
"Allgemeines", // Deutsch
|
||||
"", // Slovenski
|
||||
"Settaggi Comuni", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"Yleiset asetukset", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "VDR-to-VDR Server", // English
|
||||
"VDR-zu-VDR Server", // Deutsch
|
||||
"", // Slovenski
|
||||
"Server VDR-to-VDR", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"VDR-palvelin", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "HTTP Server", // English
|
||||
"HTTP Server", // Deutsch
|
||||
"", // Slovenski
|
||||
"Server HTTP", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"HTTP-palvelin", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "VDR-to-VDR Client", // English
|
||||
"VDR-zu-VDR Client", // Deutsch
|
||||
"", // Slovenski
|
||||
"Client VDR-to-VDR", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"VDR-asiakas", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Please restart VDR to activate changes", // English
|
||||
"Bitte starten Sie für die Änderungen VDR neu", // Deutsch
|
||||
"", // Slovenski
|
||||
"Riavviare VDR per attivare i cambiamenti", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"Aktivoi muutokset käynnistämällä VDR uudelleen", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Synchronize EPG", // English
|
||||
"EPG synchronisieren", // Deutsch
|
||||
"", // Slovenski
|
||||
"", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"Päivitä ohjelmaopas", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Suspend Live TV", // English
|
||||
"Live-TV pausieren", // Deutsch
|
||||
"", // Slovenski
|
||||
"", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"Pysäytä suora TV-lähetys", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Suspend behaviour", // English
|
||||
"Pausierverhalten", // Deutsch
|
||||
"", // Slovenski
|
||||
"", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"Pysäytystoiminto", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Offer suspend mode", // English
|
||||
"Pausieren anbieten", // Deutsch
|
||||
"", // Slovenski
|
||||
"", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"tyrkytä", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Always suspended", // English
|
||||
"Immer pausiert", // Deutsch
|
||||
"", // Slovenski
|
||||
"", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"aina", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Never suspended", // English
|
||||
"Nie pausiert", // Deutsch
|
||||
"", // Slovenski
|
||||
"", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"ei koskaan", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Suspend Server", // English
|
||||
"Server pausieren", // Deutsch
|
||||
"", // Slovenski
|
||||
"", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"Pysäytä palvelin", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Server is suspended", // English
|
||||
"Server ist pausiert", // Deutsch
|
||||
"", // Slovenski
|
||||
"", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"Palvelin on pysäytetty", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Couldn't suspend Server!", // English
|
||||
"Konnte Server nicht pausieren!", // Deutsch
|
||||
"", // Slovenski
|
||||
"", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"Palvelinta ei onnistuttu pysäyttämään!", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Client may suspend", // English
|
||||
"Client darf pausieren", // Deutsch
|
||||
"", // Slovenski
|
||||
"", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"Asiakas saa pysäyttää palvelimen", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Bind to IP", // English
|
||||
"", // Deutsch
|
||||
"", // Slovenski
|
||||
"", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"Sido osoitteeseen", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Filter Streaming", // English
|
||||
"", // Deutsch
|
||||
"", // Slovenski
|
||||
"", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"Suodatetun tiedon suoratoisto", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Streaming active", // English
|
||||
"Streamen im Gange", // Deutsch
|
||||
"", // Slovenski
|
||||
"", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"Suoratoistopalvelin aktiivinen", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ "Hide Mainmenu Entry", // English
|
||||
"Hauptmenüeintrag verstecken", // Deutsch
|
||||
"", // Slovenski
|
||||
"", // Italiano
|
||||
"", // Nederlands
|
||||
"", // Português
|
||||
"", // Français
|
||||
"", // Norsk
|
||||
"Piilota valinta päävalikosta", // suomi
|
||||
"", // Polski
|
||||
"", // Español
|
||||
"", // Ellinika
|
||||
"", // Svenska
|
||||
"", // Romaneste
|
||||
"", // Magyar
|
||||
"", // Catala
|
||||
"", // Russian
|
||||
"", // Hrvatski
|
||||
"", // Eesti
|
||||
"", // Dansk
|
||||
"", // Czech
|
||||
#if VDRVERSNUM >= 10502
|
||||
"", // Türkçe
|
||||
#endif
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
16
i18n.h
16
i18n.h
@@ -1,16 +0,0 @@
|
||||
/*
|
||||
* $Id: i18n.h,v 1.1 2004/12/30 22:43:58 lordjaxom Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_I18N_H
|
||||
#define VDR_STREAMDEV_I18N_H
|
||||
|
||||
#include <vdr/i18n.h>
|
||||
|
||||
extern const char *i18n_name;
|
||||
extern const tI18nPhrase Phrases[];
|
||||
|
||||
#undef tr
|
||||
#define tr(s) I18nTranslate(s, i18n_name)
|
||||
|
||||
#endif // VDR_STREAMDEV_I18N_H
|
||||
@@ -1,25 +1,38 @@
|
||||
INCS = -I.
|
||||
CFLAGS = -g -Wall -O2 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -fPIC
|
||||
MFLAG = -M
|
||||
OBJS = ctools.o ringbuffy.o remux.o transform.o
|
||||
SRC = $(wildcard *.c)
|
||||
#
|
||||
# Makefile for a Video Disk Recorder plugin
|
||||
#
|
||||
# $Id: Makefile,v 1.5 2010/07/30 10:49:28 schmirl Exp $
|
||||
|
||||
DESTDIR = /usr/local
|
||||
### The object files (add further files here):
|
||||
|
||||
OBJS = ctools.o remux.o ringbuffy.o transform.o
|
||||
|
||||
### Disable attribute warn_unused_result
|
||||
|
||||
DEFINES += -U_FORTIFY_SOURCE
|
||||
|
||||
### The main target:
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
clean:
|
||||
- rm -f *.o *~ *.a .depend
|
||||
|
||||
libdvbmpegtools.a: $(OBJS)
|
||||
ar -rcs libdvbmpegtools.a $(OBJS)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -c $(CFLAGS) $(INCS) $(DEFINES) $<
|
||||
### Implicit rules:
|
||||
|
||||
.depend:
|
||||
$(CXX) $(DEFINES) $(MFLAG) $(SRC) $(INCS)> .depend
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $<
|
||||
|
||||
### Dependencies:
|
||||
|
||||
MAKEDEP = $(CC) -MM -MG
|
||||
DEPFILE = .dependencies
|
||||
|
||||
-include .depend
|
||||
$(DEPFILE): Makefile
|
||||
@$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@
|
||||
|
||||
-include $(DEPFILE)
|
||||
|
||||
### Targets:
|
||||
|
||||
clean:
|
||||
@-rm -f $(OBJS) $(DEPFILE) *.a core* *~
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
* Or, point your browser to http://www.gnu.org/copyleft/gpl.html
|
||||
*
|
||||
|
||||
@@ -298,7 +298,6 @@ void write_pes(int fd, pes_packet *p){
|
||||
}
|
||||
|
||||
static unsigned int find_length(int f){
|
||||
uint64_t p = 0;
|
||||
uint64_t start = 0;
|
||||
uint64_t q = 0;
|
||||
int found = 0;
|
||||
@@ -309,7 +308,7 @@ static unsigned int find_length(int f){
|
||||
start -=2;
|
||||
lseek(f,start,SEEK_SET);
|
||||
while ( neof > 0 && !found ){
|
||||
p = lseek(f,0,SEEK_CUR);
|
||||
lseek(f,0,SEEK_CUR);
|
||||
neof = save_read(f,&sync4,4);
|
||||
if (sync4[0] == 0x00 && sync4[1] == 0x00 && sync4[2] == 0x01) {
|
||||
switch ( sync4[3] ) {
|
||||
@@ -558,7 +557,7 @@ int read_pes(int f, pes_packet *p){
|
||||
|
||||
while (neof > 0 && !found) {
|
||||
po = lseek(f,0,SEEK_CUR);
|
||||
if (po < 0) return -1;
|
||||
if (po == (off_t) -1) return -1;
|
||||
if ((neof = save_read(f,&sync4,4)) < 4) return -1;
|
||||
if (sync4[0] == 0x00 && sync4[1] == 0x00 && sync4[2] == 0x01) {
|
||||
p->stream_id = sync4[3];
|
||||
@@ -596,7 +595,10 @@ int read_pes(int f, pes_packet *p){
|
||||
|
||||
if (p->length >0){
|
||||
buf = (uint8_t *) malloc(p->length);
|
||||
if((neof = save_read(f,buf,p->length))< p->length) return -1;
|
||||
if((neof = save_read(f,buf,p->length))< p->length){
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
cread_pes((char *)buf,p);
|
||||
free(buf);
|
||||
} else return 0;
|
||||
@@ -1331,7 +1333,7 @@ void tfilter(trans *p)
|
||||
{
|
||||
int l,c;
|
||||
int tpid;
|
||||
uint8_t flag,flags;
|
||||
uint8_t flags;
|
||||
uint8_t adapt_length = 0;
|
||||
uint8_t cpid[2];
|
||||
|
||||
@@ -1347,7 +1349,6 @@ void tfilter(trans *p)
|
||||
tpid);
|
||||
}
|
||||
|
||||
flag = cpid[0];
|
||||
flags = p->packet[3];
|
||||
|
||||
if ( flags & ADAPT_FIELD ) {
|
||||
@@ -1876,528 +1877,3 @@ int write_ps_header(uint8_t *buf,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#define MAX_BASE 80
|
||||
#define MAX_PATH 256
|
||||
#define MAX_EXT 10
|
||||
|
||||
int break_up_filename(char *name, char *base_name, char *path, char *ext)
|
||||
{
|
||||
int l,i,sstop,sstart;
|
||||
|
||||
l = strlen(name);
|
||||
sstop = l;
|
||||
sstart = -1;
|
||||
for( i= l-1; i >= 0; i--){
|
||||
if (sstop == l && name[i] == '.') sstop = i;
|
||||
if (sstart<0 && name[i] == '/') sstart = i+1;
|
||||
}
|
||||
if (sstart < 0) sstart = 0;
|
||||
if (sstop-sstart < MAX_BASE){
|
||||
strncpy(base_name, name+sstart, sstop-sstart);
|
||||
base_name[sstop-sstart]=0;
|
||||
if(sstart > 0){
|
||||
if( l - sstop + sstart < MAX_PATH){
|
||||
strncpy(path, name, sstart);
|
||||
path[sstart] = 0;
|
||||
} else {
|
||||
fprintf(stderr,"PATH too long\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
} else {
|
||||
strcpy(path, "./");
|
||||
}
|
||||
|
||||
if(sstop < l){
|
||||
if( l - sstop -1 < MAX_EXT){
|
||||
strncpy(ext, name+sstop+1, l-sstop-1);
|
||||
ext[l-sstop-1]=0;
|
||||
} else {
|
||||
fprintf(stderr,"Extension too long\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
} else {
|
||||
strcpy(ext, "");
|
||||
}
|
||||
|
||||
} else {
|
||||
fprintf(stderr,"Name too long\n");
|
||||
return -1;
|
||||
}
|
||||
/*
|
||||
printf("%d %d\n",sstart, sstop);
|
||||
printf("%s %d\n",name, strlen(name));
|
||||
printf("%s %d\n",base_name, strlen(base_name));
|
||||
printf("%s %d\n",path,strlen(path));
|
||||
printf("%s %d\n",ext,strlen(ext));
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int seek_mpg_start(uint8_t *buf, int size)
|
||||
{
|
||||
int found = 0;
|
||||
int c=0;
|
||||
int seq = 0;
|
||||
int mpeg = 0;
|
||||
int mark = 0;
|
||||
|
||||
while ( !seq ){
|
||||
while (found != 4){
|
||||
switch (found) {
|
||||
case 0:
|
||||
if ( buf[c] == 0x00 ) found++;
|
||||
c++;
|
||||
break;
|
||||
case 1:
|
||||
if ( buf[c] == 0x00 ) found++;
|
||||
else found = 0;
|
||||
c++;
|
||||
break;
|
||||
case 2:
|
||||
if ( buf[c] == 0x01 ) found++;
|
||||
else found = 0;
|
||||
if ( buf[c] == 0x00 ) found = 2;
|
||||
c++;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if ( (buf[c] & 0xe0) == 0xe0 ) found++;
|
||||
else found = 0;
|
||||
c++;
|
||||
break;
|
||||
}
|
||||
if (c >= size) return -1;
|
||||
}
|
||||
|
||||
if (found == 4){
|
||||
mark = c-4;
|
||||
c+=2;
|
||||
if (c >= size) return -1;
|
||||
|
||||
if ( (buf[c] & 0xC0) == 0x80 ){
|
||||
mpeg = 2;
|
||||
c += 2;
|
||||
if (c >= size) return -1;
|
||||
c += buf[c]+1;
|
||||
if (c >= size) return -1;
|
||||
} else {
|
||||
mpeg = 1;
|
||||
while( buf[c] == 0xFF ) {
|
||||
c++;
|
||||
if (c >= size) return -1;
|
||||
}
|
||||
if ( (buf[c] & 0xC0) == 0x40) c+=2;
|
||||
if (c >= size) return -1;
|
||||
if ( (buf[c] & 0x30) ){
|
||||
if ( (buf[c] & 0x30) == 0x20) c+=5;
|
||||
else c+=10;
|
||||
} else c++;
|
||||
if (c >= size) return -1;
|
||||
}
|
||||
|
||||
if ( buf[c] == 0x00 &&
|
||||
buf[c+1] == 0x00 &&
|
||||
buf[c+2] == 0x01 &&
|
||||
buf[c+3] == 0xB3 )
|
||||
seq = 1;
|
||||
}
|
||||
found = 0;
|
||||
}
|
||||
|
||||
return size-mark;
|
||||
}
|
||||
|
||||
|
||||
void write_mpg(int fstart, uint64_t length, int fdin, int fdout)
|
||||
{
|
||||
// uint8_t mpeg_end[4] = { 0x00, 0x00, 0x01, 0xB9 };
|
||||
uint8_t *buf;
|
||||
uint64_t l=0;
|
||||
uint64_t count = 0;
|
||||
struct stat sb;
|
||||
int buf_size;
|
||||
|
||||
fstat (fdout, &sb);
|
||||
buf_size = sb.st_blksize;
|
||||
|
||||
buf = (uint8_t *) alloca (buf_size + sizeof (int));
|
||||
|
||||
lseek(fdin, fstart, SEEK_SET);
|
||||
|
||||
while ( count < length && (l = read(fdin,buf,buf_size)) >= 0){
|
||||
if (l > 0) count+=l;
|
||||
write(fdout,buf,l);
|
||||
printf("written %02.2f%%\r",(100.*count)/length);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
//write( fdout, mpeg_end, 4);
|
||||
}
|
||||
|
||||
|
||||
#define CHECKBUF (1024*1024)
|
||||
#define ONE_GIG (1024UL*1024UL*1024UL)
|
||||
void split_mpg(char *name, uint64_t size)
|
||||
{
|
||||
char base_name[MAX_BASE];
|
||||
char path[MAX_PATH];
|
||||
char ext[MAX_EXT];
|
||||
char new_name[256];
|
||||
uint8_t buf[CHECKBUF];
|
||||
int fdin;
|
||||
int fdout;
|
||||
uint64_t length = 0;
|
||||
uint64_t last;
|
||||
int i;
|
||||
int mark, csize;
|
||||
struct stat sb;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
fstat (fdin, &sb);
|
||||
|
||||
length = sb.st_size;
|
||||
if ( length < ONE_GIG )
|
||||
printf("Filelength = %2.2f MB\n", length/1024./1024.);
|
||||
else
|
||||
printf("Filelength = %2.2f GB\n", length/1024./1024./1024.);
|
||||
|
||||
if ( length < size ) length = size;
|
||||
|
||||
printf("Splitting %s into Files with size <= %2.2f MB\n",name,
|
||||
size/1024./1024.);
|
||||
|
||||
csize = CHECKBUF;
|
||||
read(fdin, buf, csize);
|
||||
if ( (mark = seek_mpg_start(buf,csize)) < 0){
|
||||
fprintf(stderr,"Couldn't find sequence header\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
last = csize-mark;
|
||||
|
||||
for ( i = 0 ; i < length/size; i++){
|
||||
csize = CHECKBUF;
|
||||
|
||||
if (csize > length-last) csize = length-last;
|
||||
lseek(fdin, last+size-csize, SEEK_SET);
|
||||
read(fdin, buf, csize);
|
||||
if ( (mark = seek_mpg_start(buf,csize)) < 0){
|
||||
fprintf(stderr,"Couldn't find sequence header\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
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);
|
||||
exit(1);
|
||||
}
|
||||
write_mpg(last, size-mark, fdin, fdout);
|
||||
last = last + size - mark;
|
||||
}
|
||||
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);
|
||||
exit(1);
|
||||
}
|
||||
write_mpg(last, length-last, fdin, fdout);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void cut_mpg(char *name, uint64_t size)
|
||||
{
|
||||
char base_name[MAX_BASE];
|
||||
char path[MAX_PATH];
|
||||
char ext[MAX_EXT];
|
||||
char new_name[256];
|
||||
uint8_t buf[CHECKBUF];
|
||||
int fdin;
|
||||
int fdout;
|
||||
uint64_t length = 0;
|
||||
uint64_t last;
|
||||
int mark, csize;
|
||||
struct stat sb;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
fstat (fdin, &sb);
|
||||
|
||||
length = sb.st_size;
|
||||
if ( length < ONE_GIG )
|
||||
printf("Filelength = %2.2f MB\n", length/1024./1024.);
|
||||
else
|
||||
printf("Filelength = %2.2f GB\n", length/1024./1024./1024.);
|
||||
|
||||
if ( length < size ) length = size;
|
||||
|
||||
printf("Splitting %s into 2 Files with length %.2f MB and %.2f MB\n",
|
||||
name, size/1024./1024., (length-size)/1024./1024.);
|
||||
|
||||
csize = CHECKBUF;
|
||||
read(fdin, buf, csize);
|
||||
if ( (mark = seek_mpg_start(buf,csize)) < 0){
|
||||
fprintf(stderr,"Couldn't find sequence header\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
last = csize-mark;
|
||||
|
||||
if (csize > length-last) csize = length-last;
|
||||
lseek(fdin, last+size-csize, SEEK_SET);
|
||||
read(fdin, buf, csize);
|
||||
if ( (mark = seek_mpg_start(buf,csize)) < 0){
|
||||
fprintf(stderr,"Couldn't find sequence header\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
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);
|
||||
exit(1);
|
||||
}
|
||||
write_mpg(last, size-mark, fdin, fdout);
|
||||
last = last + size - mark;
|
||||
|
||||
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);
|
||||
exit(1);
|
||||
}
|
||||
write_mpg(last, length-last, fdin, fdout);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void write_all (int fd, const char *data, int length)
|
||||
{
|
||||
int r;
|
||||
|
||||
while (length) {
|
||||
if ((r = write(fd, data, length)) > 0) {
|
||||
data += r;
|
||||
length -= r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void read_all (int fd, char *data, int length)
|
||||
{
|
||||
int c = 0;
|
||||
|
||||
while(1) {
|
||||
if( read(fd, data+c, 1) == 1) {
|
||||
c++;
|
||||
if(data[c-1] == '\n') {
|
||||
data[c] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
fprintf (stderr, "Error reading socket\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
char *url2host (char *url, char **name, uint32_t *ip, uint32_t *port)
|
||||
{
|
||||
char *murl;
|
||||
struct hostent *hoste;
|
||||
struct in_addr haddr;
|
||||
int found_ip = 1;
|
||||
|
||||
if (!(strncmp(url, "http://", 7)))
|
||||
url += 7;
|
||||
|
||||
*name = strdup(url);
|
||||
if (!(*name)) {
|
||||
*name = NULL;
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
murl = url;
|
||||
while (*murl && *murl != ':' && *murl != '/') {
|
||||
if ((*murl < '0' || *murl > '9') && *murl != '.')
|
||||
found_ip = 0;
|
||||
murl++;
|
||||
}
|
||||
|
||||
(*name)[murl - url] = 0;
|
||||
if (found_ip) {
|
||||
if ((*ip = inet_addr(*name)) == INADDR_NONE)
|
||||
return (NULL);
|
||||
} else {
|
||||
if (!(hoste = gethostbyname(*name)))
|
||||
return (NULL);
|
||||
memcpy (&haddr, hoste->h_addr, sizeof(haddr));
|
||||
*ip = haddr.s_addr;
|
||||
}
|
||||
|
||||
if (!*murl || *murl == '/') {
|
||||
*port = 80;
|
||||
return (murl);
|
||||
}
|
||||
*port = atoi(++murl);
|
||||
|
||||
while (*murl && *murl != '/')
|
||||
murl++;
|
||||
return (murl);
|
||||
}
|
||||
|
||||
#define ACCEPT "Accept: video/mpeg, video/x-mpegurl, */*\r\n"
|
||||
|
||||
int http_open (char *url)
|
||||
{
|
||||
char purl[1024], *host, req[1024], *sptr;
|
||||
uint32_t ip;
|
||||
uint32_t port;
|
||||
int sock;
|
||||
int reloc, relocnum = 0;
|
||||
struct sockaddr_in server;
|
||||
int mfd;
|
||||
|
||||
strncpy (purl, url, 1023);
|
||||
purl[1023] = '\0';
|
||||
|
||||
do {
|
||||
host = NULL;
|
||||
strcpy (req, "GET ");
|
||||
if (!(sptr = url2host(purl, &host, &ip, &port))) {
|
||||
fprintf (stderr, "Unknown host\n");
|
||||
exit (1);
|
||||
}
|
||||
strcat (req, sptr);
|
||||
sprintf (req + strlen(req),
|
||||
" HTTP/1.0\r\nUser-Agent: %s/%s\r\n",
|
||||
"whatever", "you want");
|
||||
if (host) {
|
||||
sprintf(req + strlen(req),
|
||||
"Host: %s:%u\r\n", host, port);
|
||||
free (host);
|
||||
}
|
||||
|
||||
strcat (req, ACCEPT);
|
||||
strcat (req, "\r\n");
|
||||
|
||||
server.sin_port = htons(port);
|
||||
server.sin_family = AF_INET;
|
||||
server.sin_addr.s_addr = ip;
|
||||
|
||||
if ((sock = socket(PF_INET, SOCK_STREAM, 6)) < 0) {
|
||||
perror ("socket");
|
||||
exit (1);
|
||||
}
|
||||
|
||||
if (connect(sock, (struct sockaddr *)&server,
|
||||
sizeof(server))) {
|
||||
perror ("connect");
|
||||
exit (1);
|
||||
}
|
||||
|
||||
write_all (sock, req, strlen(req));
|
||||
if (!(mfd = fileno(fdopen(sock, "rb")))) {
|
||||
perror ("open");
|
||||
exit (1);
|
||||
}
|
||||
reloc = 0;
|
||||
purl[0] = '\0';
|
||||
read_all (mfd, req, 1023);
|
||||
if ((sptr = strchr(req, ' '))) {
|
||||
switch (sptr[1]) {
|
||||
case '2':
|
||||
break;
|
||||
case '3':
|
||||
reloc = 1;
|
||||
default:
|
||||
fprintf (stderr, "HTTP req failed:%s",
|
||||
sptr+1);
|
||||
exit (1);
|
||||
}
|
||||
}
|
||||
do {
|
||||
read_all (mfd,req, 1023);
|
||||
if (!strncmp(req, "Location:", 9))
|
||||
strncpy (purl, req+10, 1023);
|
||||
} while (req[0] != '\r' && req[0] != '\n');
|
||||
} while (reloc && purl[0] && relocnum++ < 3);
|
||||
if (reloc) {
|
||||
fprintf (stderr, "Too many HTTP relocations.\n");
|
||||
exit (1);
|
||||
}
|
||||
|
||||
return sock;
|
||||
}
|
||||
|
||||
extern int errno;
|
||||
const char * strerrno (void)
|
||||
{
|
||||
return strerror(errno);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
* Or, point your browser to http://www.gnu.org/copyleft/gpl.html
|
||||
*
|
||||
|
||||
@@ -387,16 +387,7 @@ extern "C" {
|
||||
uint8_t buffer2_scale,
|
||||
uint32_t buffer2_size);
|
||||
|
||||
|
||||
int seek_mpg_start(uint8_t *buf, int size);
|
||||
|
||||
|
||||
void split_mpg(char *name, uint64_t size);
|
||||
void cut_mpg(char *name, uint64_t size);
|
||||
int http_open (char *url);
|
||||
ssize_t save_read(int fd, void *buf, size_t count);
|
||||
|
||||
const char * strerrno(void);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
* Or, point your browser to http://www.gnu.org/copyleft/gpl.html
|
||||
*
|
||||
|
||||
@@ -388,10 +388,9 @@ int refill_buffy(Remux *rem)
|
||||
|
||||
|
||||
while ( acount > MAX_PLENGTH && vcount > MAX_PLENGTH && count < 10){
|
||||
int neof;
|
||||
count++;
|
||||
init_pes(&pes);
|
||||
if ((neof = read_pes(fin,&pes)) <= 0) return -1;
|
||||
if (read_pes(fin,&pes) <= 0) return -1;
|
||||
switch(pes.stream_id){
|
||||
case AUDIO_STREAM_S ... AUDIO_STREAM_E:
|
||||
rem->apes++;
|
||||
@@ -702,22 +701,6 @@ void init_remux(Remux *rem, int fin, int fout, int mult)
|
||||
rem->time_off = 0;
|
||||
}
|
||||
|
||||
uint32_t bytes2pts(int bytes, int rate)
|
||||
{
|
||||
if (bytes < 0xFFFFFFFFUL/720000UL)
|
||||
return (uint32_t)(bytes*720000UL/rate);
|
||||
else
|
||||
return (uint32_t)(bytes/rate*720000UL);
|
||||
}
|
||||
|
||||
long pts2bytes( uint32_t pts, int rate)
|
||||
{
|
||||
if (pts < 0xEFFFFFFFUL/rate)
|
||||
return (pts*rate/720000);
|
||||
else
|
||||
return (pts* (rate/720000));
|
||||
}
|
||||
|
||||
int write_audio_pes( Remux *rem, uint8_t *buf, int *alength)
|
||||
{
|
||||
int add;
|
||||
@@ -773,7 +756,6 @@ int write_video_pes( Remux *rem, uint8_t *buf, int *vlength)
|
||||
int pos = 0;
|
||||
int p = 0;
|
||||
uint32_t pts = 0;
|
||||
uint32_t dts = 0;
|
||||
int stuff = 0;
|
||||
int length = *vlength;
|
||||
long diff = 0;
|
||||
@@ -804,7 +786,6 @@ int write_video_pes( Remux *rem, uint8_t *buf, int *vlength)
|
||||
if (add < 0) return -1;
|
||||
pos += add;
|
||||
rem->vpts_old = rem->vpts;
|
||||
dts = rem->vdts;
|
||||
rem->vpts = rem->vpts_list[0].PTS;
|
||||
rem->vdts = rem->vpts_list[0].dts;
|
||||
if ( diff > 0) rem->SCR += diff;
|
||||
@@ -1088,12 +1069,6 @@ struct remux_s{
|
||||
} REMUX;
|
||||
|
||||
|
||||
void init_REMUX(REMUX *rem)
|
||||
{
|
||||
rem->num_pbuf = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#define REPACK 2048
|
||||
#define ABUF_SIZE REPACK*1024
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
* Or, point your browser to http://www.gnu.org/copyleft/gpl.html
|
||||
*
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "ringbuffy.h"
|
||||
@@ -191,9 +192,8 @@ int ring_read_file(ringbuffy *rbuf, int fd, int count)
|
||||
}
|
||||
|
||||
int ring_rest(ringbuffy *rbuf){
|
||||
int diff, free, pos, rest;
|
||||
int diff, free, pos;
|
||||
pos = rbuf->read_pos;
|
||||
rest = rbuf->size - pos;
|
||||
diff = rbuf->write_pos - pos;
|
||||
free = (diff >= 0) ? diff : rbuf->size+diff;
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef RINGBUFFY_H
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
* Or, point your browser to http://www.gnu.org/copyleft/gpl.html
|
||||
*
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
* Or, point your browser to http://www.gnu.org/copyleft/gpl.html
|
||||
*
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
|
||||
|
||||
#define MAX_PLENGTH 0xFFFF
|
||||
#define MMAX_PLENGTH (8*MAX_PLENGTH)
|
||||
#define MMAX_PLENGTH (64*MAX_PLENGTH)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# The cannels.conf ca field can be used to bind a channel to a specific
|
||||
# device. The streamdev-client does not consider this information, so
|
||||
# there's no way to keep VDR from using streamdev for a specific
|
||||
# channel. Apply this patch if you need this feature.
|
||||
#
|
||||
# This fix should probably become part of streamdev. However as it
|
||||
# changes the behaviour of streamdev, I decided to keep it as a separate
|
||||
# patch until there is something like a new official streamdev release.
|
||||
#
|
||||
--- client/device.h.bak 2006-11-09 12:25:21.000000000 +0100
|
||||
+++ client/device.h 2006-11-09 12:26:57.000000000 +0100
|
||||
@@ -50,6 +50,7 @@
|
||||
cStreamdevDevice(void);
|
||||
virtual ~cStreamdevDevice();
|
||||
|
||||
+ virtual int ProvidesCa(const cChannel *Channel) const;
|
||||
virtual bool ProvidesSource(int Source) const;
|
||||
virtual bool ProvidesTransponder(const cChannel *Channel) const;
|
||||
virtual bool ProvidesChannel(const cChannel *Channel, int Priority = -1,
|
||||
--- client/device.c.bak 2006-11-09 12:23:24.000000000 +0100
|
||||
+++ client/device.c 2006-11-09 12:35:48.000000000 +0100
|
||||
@@ -57,6 +57,12 @@
|
||||
#endif
|
||||
}
|
||||
|
||||
+int cStreamdevDevice::ProvidesCa(const cChannel *Channel) const
|
||||
+{
|
||||
+ // Encrypted is acceptable for now. Will ask the server later.
|
||||
+ return Channel->Ca() <= CA_DVB_MAX ? cDevice::ProvidesCa(Channel) : 1;
|
||||
+}
|
||||
+
|
||||
bool cStreamdevDevice::ProvidesSource(int Source) const {
|
||||
Dprintf("ProvidesSource, Source=%d\n", Source);
|
||||
return false;
|
||||
@@ -78,7 +84,7 @@
|
||||
if (ClientSocket.DataSocket(siLive) != NULL
|
||||
&& TRANSPONDER(Channel, m_Channel))
|
||||
res = true;
|
||||
- else {
|
||||
+ else if (ProvidesCa(Channel)) {
|
||||
res = prio && ClientSocket.ProvidesChannel(Channel, Priority);
|
||||
ndr = true;
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
# If you have two or more VDRs and you like them to mutually share
|
||||
# there DVB cards you might need to apply this patch first.
|
||||
#
|
||||
# IMPORTANT: As this patch does not only modify streamdev-server but
|
||||
# also an exported method of VDR, you will need to
|
||||
#
|
||||
# !!!!! RECOMPILE VDR AND ALL PLUGINS !!!!!
|
||||
#
|
||||
# Why do I need the patch?
|
||||
# --------------------------
|
||||
# Before switching channels VDR will consider all of its devices to
|
||||
# find the one with the least impact. This includes the device provided
|
||||
# by the streamdev-client plugin. Streamdev-client will forward the
|
||||
# request to its server which in turn checks all of its devices. Now if
|
||||
# the server is running streamdev-client, too, the request will again
|
||||
# be forwarded to its server and finally you will endup in a loop.
|
||||
#
|
||||
# What does the patch do?
|
||||
# -----------------------
|
||||
# The patch adds the additional parameter "bool DVBCardsOnly" to VDR's
|
||||
# device selection method cDevice::GetDevice(...). The parameter
|
||||
# defaults to false which gives you the standard behaviour of GetDevice.
|
||||
# When set to true, GetDevice will use only those devices with a card
|
||||
# index < MAXDVBDEVICES, so only real DVB cards will be considered.
|
||||
# Other devices like streamdev-client or DVB cards provided by plugin
|
||||
# (Hauppauge PVR) won't be used.
|
||||
#
|
||||
# Author: Frank Schmirler (http://vdr.schmirler.de)
|
||||
#
|
||||
--- device.h.orig 2006-11-15 12:01:34.000000000 +0100
|
||||
+++ device.h 2006-11-15 12:02:15.000000000 +0100
|
||||
@@ -128,7 +128,7 @@
|
||||
///< Gets the device with the given Index.
|
||||
///< \param Index must be in the range 0..numDevices-1.
|
||||
///< \return A pointer to the device, or NULL if the Index was invalid.
|
||||
- static cDevice *GetDevice(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL);
|
||||
+ static cDevice *GetDevice(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL, bool DVBCardsOnly = false);
|
||||
///< Returns a device that is able to receive the given Channel at the
|
||||
///< given Priority, with the least impact on active recordings and
|
||||
///< live viewing.
|
||||
--- device.c.orig 2006-11-15 12:01:30.000000000 +0100
|
||||
+++ device.c 2006-11-22 12:28:05.000000000 +0100
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
#include "device.h"
|
||||
+#include "dvbdevice.h"
|
||||
#include <errno.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
@@ -278,11 +279,13 @@
|
||||
return (0 <= Index && Index < numDevices) ? device[Index] : NULL;
|
||||
}
|
||||
|
||||
-cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers)
|
||||
+cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers, bool DVBCardsOnly)
|
||||
{
|
||||
cDevice *d = NULL;
|
||||
uint Impact = 0xFFFFFFFF; // we're looking for a device with the least impact
|
||||
for (int i = 0; i < numDevices; i++) {
|
||||
+ if (DVBCardsOnly && device[i]->CardIndex() >= MAXDVBDEVICES)
|
||||
+ continue;
|
||||
bool ndr;
|
||||
if (device[i]->ProvidesChannel(Channel, Priority, &ndr)) { // this device is basicly able to do the job
|
||||
// Put together an integer number that reflects the "impact" using
|
||||
--- PLUGINS/src/streamdev/server/connection.c.orig 2006-11-15 12:10:11.000000000 +0100
|
||||
+++ PLUGINS/src/streamdev/server/connection.c 2006-11-15 12:10:59.000000000 +0100
|
||||
@@ -132,7 +132,7 @@
|
||||
Dprintf(" * GetDevice(const cChannel*, int)\n");
|
||||
Dprintf(" * -------------------------------\n");
|
||||
|
||||
- device = cDevice::GetDevice(Channel, Priority);
|
||||
+ device = cDevice::GetDevice(Channel, Priority, NULL, true);
|
||||
|
||||
Dprintf(" * Found following device: %p (%d)\n", device,
|
||||
device ? device->CardIndex() + 1 : 0);
|
||||
@@ -150,7 +150,7 @@
|
||||
const cChannel *current = Channels.GetByNumber(cDevice::CurrentChannel());
|
||||
isyslog("streamdev-server: Detaching current receiver");
|
||||
Detach();
|
||||
- device = cDevice::GetDevice(Channel, Priority);
|
||||
+ device = cDevice::GetDevice(Channel, Priority, NULL, true);
|
||||
Attach();
|
||||
Dprintf(" * Found following device: %p (%d)\n", device,
|
||||
device ? device->CardIndex() + 1 : 0);
|
||||
@@ -1,88 +0,0 @@
|
||||
# If you have two or more VDRs and you like them to mutually share
|
||||
# there DVB cards you might need to apply this patch first.
|
||||
#
|
||||
# This is a modified version of the patch for VDRs with BIGPATCH.
|
||||
# Thanks to p_body@vdrportal.
|
||||
#
|
||||
# IMPORTANT: As this patch does not only modify streamdev-server but
|
||||
# also an exported method of VDR, you will need to
|
||||
#
|
||||
# !!!!! RECOMPILE VDR AND ALL PLUGINS !!!!!
|
||||
#
|
||||
# Why do I need the patch?
|
||||
# --------------------------
|
||||
# Before switching channels VDR will consider all of its devices to
|
||||
# find the one with the least impact. This includes the device provided
|
||||
# by the streamdev-client plugin. Streamdev-client will forward the
|
||||
# request to its server which in turn checks all of its devices. Now if
|
||||
# the server is running streamdev-client, too, the request will again
|
||||
# be forwarded to its server and finally you will endup in a loop.
|
||||
#
|
||||
# What does the patch do?
|
||||
# -----------------------
|
||||
# The patch adds the additional parameter "bool DVBCardsOnly" to VDR's
|
||||
# device selection method cDevice::GetDevice(...). The parameter
|
||||
# defaults to false which gives you the standard behaviour of GetDevice.
|
||||
# When set to true, GetDevice will use only those devices with a card
|
||||
# index < MAXDVBDEVICES, so only real DVB cards will be considered.
|
||||
# Other devices like streamdev-client or DVB cards provided by plugin
|
||||
# (Hauppauge PVR) won't be used.
|
||||
#
|
||||
# Author: Frank Schmirler (http://vdr.schmirler.de)
|
||||
#
|
||||
--- device.h.orig 2006-11-15 12:01:34.000000000 +0100
|
||||
+++ device.h 2006-11-15 12:02:15.000000000 +0100
|
||||
@@ -128,7 +128,7 @@
|
||||
///< Gets the device with the given Index.
|
||||
///< \param Index must be in the range 0..numDevices-1.
|
||||
///< \return A pointer to the device, or NULL if the Index was invalid.
|
||||
- static cDevice *GetDevice(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL, bool LiveView = false);
|
||||
+ static cDevice *GetDevice(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL, bool LiveView = false, bool DVBCardsOnly = false);
|
||||
///< Returns a device that is able to receive the given Channel at the
|
||||
///< given Priority, with the least impact on active recordings and
|
||||
///< live viewing.
|
||||
--- device.c.orig 2006-11-15 12:01:30.000000000 +0100
|
||||
+++ device.c 2006-11-22 12:28:05.000000000 +0100
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
#include "device.h"
|
||||
+#include "dvbdevice.h"
|
||||
#include <errno.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
@@ -278,11 +279,13 @@
|
||||
return (0 <= Index && Index < numDevices) ? device[Index] : NULL;
|
||||
}
|
||||
|
||||
-cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers, bool LiveView)
|
||||
+cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers, bool LiveView, bool DVBCardsOnly)
|
||||
{
|
||||
cDevice *d = NULL;
|
||||
uint Impact = 0xFFFFFFFF; // we're looking for a device with the least impact
|
||||
for (int i = 0; i < numDevices; i++) {
|
||||
+ if (DVBCardsOnly && device[i]->CardIndex() >= MAXDVBDEVICES)
|
||||
+ continue;
|
||||
bool ndr;
|
||||
if (device[i]->ProvidesChannel(Channel, Priority, &ndr)) { // this device is basicly able to do the job
|
||||
// Put together an integer number that reflects the "impact" using
|
||||
--- PLUGINS/src/streamdev/server/connection.c.orig 2006-11-15 12:10:11.000000000 +0100
|
||||
+++ PLUGINS/src/streamdev/server/connection.c 2006-11-15 12:10:59.000000000 +0100
|
||||
@@ -132,7 +132,7 @@
|
||||
Dprintf(" * GetDevice(const cChannel*, int)\n");
|
||||
Dprintf(" * -------------------------------\n");
|
||||
|
||||
- device = cDevice::GetDevice(Channel, Priority);
|
||||
+ device = cDevice::GetDevice(Channel, Priority, NULL, NULL, true);
|
||||
|
||||
Dprintf(" * Found following device: %p (%d)\n", device,
|
||||
device ? device->CardIndex() + 1 : 0);
|
||||
@@ -150,7 +150,7 @@
|
||||
const cChannel *current = Channels.GetByNumber(cDevice::CurrentChannel());
|
||||
isyslog("streamdev-server: Detaching current receiver");
|
||||
Detach();
|
||||
- device = cDevice::GetDevice(Channel, Priority);
|
||||
+ device = cDevice::GetDevice(Channel, Priority, NULL, NULL, true);
|
||||
Attach();
|
||||
Dprintf(" * Found following device: %p (%d)\n", device,
|
||||
device ? device->CardIndex() + 1 : 0);
|
||||
@@ -1,102 +0,0 @@
|
||||
# Apply this patch to VDR if you want to use a fullfeatured DVB card
|
||||
# as pure output device. Infact the patch will keep VDR from using the
|
||||
# tuner of any local DVB card (also budget cards). It will not affect
|
||||
# other input devices like e.g. streamdev-client or DVB cards provided
|
||||
# by plugins (e.g. Hauppauge PVR).
|
||||
#
|
||||
# By default the patch is DISABLED. There will be a new OSD menu entry
|
||||
# in Setup->DVB which allows you to enable or disable the patch at any
|
||||
# time.
|
||||
diff -ru vdr-1.4.3.orig/config.c vdr-1.4.3/config.c
|
||||
--- vdr-1.4.3.orig/config.c 2006-07-22 13:57:51.000000000 +0200
|
||||
+++ vdr-1.4.3/config.c 2006-11-16 08:16:37.000000000 +0100
|
||||
@@ -273,6 +273,7 @@
|
||||
CurrentChannel = -1;
|
||||
CurrentVolume = MAXVOLUME;
|
||||
CurrentDolby = 0;
|
||||
+ LocalChannelProvide = 1;
|
||||
InitialChannel = 0;
|
||||
InitialVolume = -1;
|
||||
}
|
||||
@@ -434,6 +435,7 @@
|
||||
else if (!strcasecmp(Name, "CurrentChannel")) CurrentChannel = atoi(Value);
|
||||
else if (!strcasecmp(Name, "CurrentVolume")) CurrentVolume = atoi(Value);
|
||||
else if (!strcasecmp(Name, "CurrentDolby")) CurrentDolby = atoi(Value);
|
||||
+ else if (!strcasecmp(Name, "LocalChannelProvide")) LocalChannelProvide = atoi(Value);
|
||||
else if (!strcasecmp(Name, "InitialChannel")) InitialChannel = atoi(Value);
|
||||
else if (!strcasecmp(Name, "InitialVolume")) InitialVolume = atoi(Value);
|
||||
else
|
||||
@@ -502,6 +504,7 @@
|
||||
Store("CurrentChannel", CurrentChannel);
|
||||
Store("CurrentVolume", CurrentVolume);
|
||||
Store("CurrentDolby", CurrentDolby);
|
||||
+ Store("LocalChannelProvide",LocalChannelProvide);
|
||||
Store("InitialChannel", InitialChannel);
|
||||
Store("InitialVolume", InitialVolume);
|
||||
|
||||
diff -ru vdr-1.4.3.orig/config.h vdr-1.4.3/config.h
|
||||
--- vdr-1.4.3.orig/config.h 2006-09-23 15:56:08.000000000 +0200
|
||||
+++ vdr-1.4.3/config.h 2006-11-16 08:16:57.000000000 +0100
|
||||
@@ -250,6 +250,7 @@
|
||||
int CurrentChannel;
|
||||
int CurrentVolume;
|
||||
int CurrentDolby;
|
||||
+ int LocalChannelProvide;
|
||||
int InitialChannel;
|
||||
int InitialVolume;
|
||||
int __EndData__;
|
||||
diff -ru vdr-1.4.3.orig/dvbdevice.c vdr-1.4.3/dvbdevice.c
|
||||
--- vdr-1.4.3.orig/dvbdevice.c 2006-08-14 11:38:32.000000000 +0200
|
||||
+++ vdr-1.4.3/dvbdevice.c 2006-11-16 08:17:58.000000000 +0100
|
||||
@@ -766,6 +766,8 @@
|
||||
|
||||
bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers) const
|
||||
{
|
||||
+ if (Setup.LocalChannelProvide != 1)
|
||||
+ return false;
|
||||
bool result = false;
|
||||
bool hasPriority = Priority < 0 || Priority > this->Priority();
|
||||
bool needsDetachReceivers = false;
|
||||
diff -ru vdr-1.4.3.orig/i18n.c vdr-1.4.3/i18n.c
|
||||
--- vdr-1.4.3.orig/i18n.c 2006-09-16 11:08:30.000000000 +0200
|
||||
+++ vdr-1.4.3/i18n.c 2006-11-16 08:36:53.000000000 +0100
|
||||
@@ -3546,6 +3546,28 @@
|
||||
"Foretrukket sprog",
|
||||
"Preferovaný jazyk",
|
||||
},
|
||||
+ { "Setup.DVB$Use DVB receivers",
|
||||
+ "DVB Empfangsteile benutzen",
|
||||
+ "",
|
||||
+ "",
|
||||
+ "",
|
||||
+ "",
|
||||
+ "",
|
||||
+ "",
|
||||
+ "",
|
||||
+ "",
|
||||
+ "",
|
||||
+ "",
|
||||
+ "",
|
||||
+ "",
|
||||
+ "",
|
||||
+ "",
|
||||
+ "",
|
||||
+ "",
|
||||
+ "",
|
||||
+ "",
|
||||
+ "",
|
||||
+ },
|
||||
{ "Setup.DVB$Primary DVB interface",
|
||||
"Primäres DVB-Interface",
|
||||
"Primarna naprava",
|
||||
diff -ru vdr-1.4.3.orig/menu.c vdr-1.4.3/menu.c
|
||||
--- vdr-1.4.3.orig/menu.c 2006-07-23 11:23:11.000000000 +0200
|
||||
+++ vdr-1.4.3/menu.c 2006-11-16 08:37:27.000000000 +0100
|
||||
@@ -2354,6 +2354,7 @@
|
||||
|
||||
Clear();
|
||||
|
||||
+ Add(new cMenuEditBoolItem(tr("Setup.DVB$Use DVB receivers"), &data.LocalChannelProvide));
|
||||
Add(new cMenuEditIntItem( tr("Setup.DVB$Primary DVB interface"), &data.PrimaryDVB, 1, cDevice::NumDevices()));
|
||||
Add(new cMenuEditBoolItem(tr("Setup.DVB$Video format"), &data.VideoFormat, "4:3", "16:9"));
|
||||
if (data.VideoFormat == 0)
|
||||
13
patches/vdr-1.6.0-1.7.29-ignore_missing_cam.diff
Normal file
13
patches/vdr-1.6.0-1.7.29-ignore_missing_cam.diff
Normal file
@@ -0,0 +1,13 @@
|
||||
--- device.c.orig 2008-03-28 11:47:25.000000000 +0100
|
||||
+++ device.c 2008-03-28 11:47:09.000000000 +0100
|
||||
@@ -375,8 +375,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
- if (!NumUsableSlots)
|
||||
- return NULL; // no CAM is able to decrypt this channel
|
||||
+// if (!NumUsableSlots)
|
||||
+// return NULL; // no CAM is able to decrypt this channel
|
||||
}
|
||||
|
||||
bool NeedsDetachReceivers = false;
|
||||
78
patches/vdr-1.6.0-1.7.29-intcamdevices.patch
Normal file
78
patches/vdr-1.6.0-1.7.29-intcamdevices.patch
Normal file
@@ -0,0 +1,78 @@
|
||||
Index: vdr-1.6.0-nocamdevices/device.c
|
||||
===================================================================
|
||||
--- vdr-1.6.0-nocamdevices/device.c
|
||||
+++ vdr-1.6.0-nocamdevices/device.c 2008-04-27 18:55:37.000000000 +0300
|
||||
@@ -363,6 +363,7 @@
|
||||
int NumCamSlots = CamSlots.Count();
|
||||
int SlotPriority[NumCamSlots];
|
||||
int NumUsableSlots = 0;
|
||||
+ bool InternalCamNeeded = false;
|
||||
if (Channel->Ca() >= CA_ENCRYPTED_MIN) {
|
||||
for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) {
|
||||
SlotPriority[CamSlot->Index()] = MAXPRIORITY + 1; // assumes it can't be used
|
||||
@@ -376,7 +377,7 @@
|
||||
}
|
||||
}
|
||||
if (!NumUsableSlots)
|
||||
- return NULL; // no CAM is able to decrypt this channel
|
||||
+ InternalCamNeeded = true; // no CAM is able to decrypt this channel
|
||||
}
|
||||
|
||||
bool NeedsDetachReceivers = false;
|
||||
@@ -392,11 +393,13 @@
|
||||
continue; // this device shall be temporarily avoided
|
||||
if (Channel->Ca() && Channel->Ca() <= CA_DVB_MAX && Channel->Ca() != device[i]->CardIndex() + 1)
|
||||
continue; // a specific card was requested, but not this one
|
||||
- if (NumUsableSlots && !CamSlots.Get(j)->Assign(device[i], true))
|
||||
+ if (InternalCamNeeded && !device[i]->HasInternalCam())
|
||||
+ continue; // no CAM is able to decrypt this channel and the device uses vdr handled CAMs
|
||||
+ if (NumUsableSlots && !device[i]->HasInternalCam() && !CamSlots.Get(j)->Assign(device[i], true))
|
||||
continue; // CAM slot can't be used with this device
|
||||
bool ndr;
|
||||
if (device[i]->ProvidesChannel(Channel, Priority, &ndr)) { // this device is basicly able to do the job
|
||||
- if (NumUsableSlots && device[i]->CamSlot() && device[i]->CamSlot() != CamSlots.Get(j))
|
||||
+ if (NumUsableSlots && !device[i]->HasInternalCam() && device[i]->CamSlot() && device[i]->CamSlot() != CamSlots.Get(j))
|
||||
ndr = true; // using a different CAM slot requires detaching receivers
|
||||
// Put together an integer number that reflects the "impact" using
|
||||
// this device would have on the overall system. Each condition is represented
|
||||
@@ -410,18 +413,18 @@
|
||||
imp <<= 1; imp |= device[i]->Receiving(); // avoid devices that are receiving
|
||||
imp <<= 1; imp |= device[i] == cTransferControl::ReceiverDevice(); // avoid the Transfer Mode receiver device
|
||||
imp <<= 8; imp |= min(max(device[i]->Priority() + MAXPRIORITY, 0), 0xFF); // use the device with the lowest priority (+MAXPRIORITY to assure that values -99..99 can be used)
|
||||
- imp <<= 8; imp |= min(max((NumUsableSlots ? SlotPriority[j] : 0) + MAXPRIORITY, 0), 0xFF); // use the CAM slot with the lowest priority (+MAXPRIORITY to assure that values -99..99 can be used)
|
||||
+ imp <<= 8; imp |= min(max(((NumUsableSlots && !device[i]->HasInternalCam()) ? SlotPriority[j] : 0) + MAXPRIORITY, 0), 0xFF); // use the CAM slot with the lowest priority (+MAXPRIORITY to assure that values -99..99 can be used)
|
||||
imp <<= 1; imp |= ndr; // avoid devices if we need to detach existing receivers
|
||||
imp <<= 1; imp |= device[i]->IsPrimaryDevice(); // avoid the primary device
|
||||
- imp <<= 1; imp |= NumUsableSlots ? 0 : device[i]->HasCi(); // avoid cards with Common Interface for FTA channels
|
||||
+ imp <<= 1; imp |= (NumUsableSlots || InternalCamNeeded) ? 0 : device[i]->HasCi(); // avoid cards with Common Interface for FTA channels
|
||||
imp <<= 1; imp |= device[i]->HasDecoder(); // avoid full featured cards
|
||||
- imp <<= 1; imp |= NumUsableSlots ? !ChannelCamRelations.CamDecrypt(Channel->GetChannelID(), j + 1) : 0; // prefer CAMs that are known to decrypt this channel
|
||||
+ imp <<= 1; imp |= (NumUsableSlots && !device[i]->HasInternalCam()) ? !ChannelCamRelations.CamDecrypt(Channel->GetChannelID(), j + 1) : 0; // prefer CAMs that are known to decrypt this channel
|
||||
if (imp < Impact) {
|
||||
// This device has less impact than any previous one, so we take it.
|
||||
Impact = imp;
|
||||
d = device[i];
|
||||
NeedsDetachReceivers = ndr;
|
||||
- if (NumUsableSlots)
|
||||
+ if (NumUsableSlots && !device[i]->HasInternalCam())
|
||||
s = CamSlots.Get(j);
|
||||
}
|
||||
}
|
||||
Index: vdr-1.6.0-nocamdevices/device.h
|
||||
===================================================================
|
||||
--- vdr-1.6.0-nocamdevices/device.h
|
||||
+++ vdr-1.6.0-nocamdevices/device.h 2008-04-27 18:55:49.000000000 +0300
|
||||
@@ -335,6 +335,12 @@
|
||||
public:
|
||||
virtual bool HasCi(void);
|
||||
///< Returns true if this device has a Common Interface.
|
||||
+ virtual bool HasInternalCam(void) { return false; }
|
||||
+ ///< Returns true if this device handles encrypted channels itself
|
||||
+ ///< without VDR assistance. This can be e.g. when the device is a
|
||||
+ ///< client that gets the stream from another VDR instance that has
|
||||
+ ///< already decrypted the stream. In this case ProvidesChannel()
|
||||
+ ///< shall check whether the channel can be decrypted.
|
||||
void SetCamSlot(cCamSlot *CamSlot);
|
||||
///< Sets the given CamSlot to be used with this device.
|
||||
cCamSlot *CamSlot(void) const { return camSlot; }
|
||||
|
||||
34
remux/Makefile
Normal file
34
remux/Makefile
Normal file
@@ -0,0 +1,34 @@
|
||||
#
|
||||
# Makefile for a Video Disk Recorder plugin
|
||||
#
|
||||
# $Id: Makefile,v 1.2 2010/07/19 13:49:28 schmirl Exp $
|
||||
|
||||
### The object files (add further files here):
|
||||
|
||||
OBJS = tsremux.o ts2es.o ts2pes.o ts2ps.o extern.o
|
||||
|
||||
### The main target:
|
||||
|
||||
.PHONY: clean
|
||||
remux.a: $(OBJS)
|
||||
ar -rcs remux.a $^
|
||||
|
||||
### Implicit rules:
|
||||
|
||||
%.o: %.c
|
||||
$(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $<
|
||||
|
||||
### Dependencies:
|
||||
|
||||
MAKEDEP = $(CXX) -MM -MG
|
||||
DEPFILE = .dependencies
|
||||
|
||||
$(DEPFILE): Makefile
|
||||
@$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@
|
||||
|
||||
-include $(DEPFILE)
|
||||
|
||||
### Targets:
|
||||
|
||||
clean:
|
||||
@-rm -f $(OBJS) $(DEPFILE) *.a core* *~
|
||||
221
remux/extern.c
221
remux/extern.c
@@ -1,13 +1,19 @@
|
||||
#include "remux/extern.h"
|
||||
#include "server/server.h"
|
||||
#include "server/connection.h"
|
||||
#include "server/streamer.h"
|
||||
#include <vdr/channels.h>
|
||||
#include <vdr/remux.h>
|
||||
#include <vdr/tools.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
const char *g_ExternRemux = EXTERNREMUXPATH;
|
||||
namespace Streamdev {
|
||||
|
||||
#define MAXENV 63
|
||||
|
||||
class cTSExt: public cThread {
|
||||
private:
|
||||
@@ -20,16 +26,19 @@ protected:
|
||||
virtual void Action(void);
|
||||
|
||||
public:
|
||||
cTSExt(cRingBufferLinear *ResultBuffer, std::string Parameter);
|
||||
cTSExt(cRingBufferLinear *ResultBuffer, const cServerConnection *Connection, const cChannel *Channel, const cPatPmtParser *PatPmt, const int *Apids, const int *Dpids);
|
||||
virtual ~cTSExt();
|
||||
|
||||
void Put(const uchar *Data, int Count);
|
||||
};
|
||||
|
||||
cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, std::string Parameter):
|
||||
} // namespace Streamdev
|
||||
using namespace Streamdev;
|
||||
|
||||
cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, const cServerConnection *Connection, const cChannel *Channel, const cPatPmtParser *PatPmt, const int *Apids, const int *Dpids):
|
||||
m_ResultBuffer(ResultBuffer),
|
||||
m_Active(false),
|
||||
m_Process(0),
|
||||
m_Process(-1),
|
||||
m_Inpipe(0),
|
||||
m_Outpipe(0)
|
||||
{
|
||||
@@ -59,6 +68,150 @@ cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, std::string Parameter):
|
||||
|
||||
if (m_Process == 0) {
|
||||
// child process
|
||||
char *env[MAXENV + 1];
|
||||
int i = 0;
|
||||
|
||||
#define ADDENV(x...) if (asprintf(&env[i++], x) < 0) i--
|
||||
|
||||
// add channel ID, name and pids to environment
|
||||
if (Channel) {
|
||||
ADDENV("REMUX_CHANNEL_ID=%s", *Channel->GetChannelID().ToString());
|
||||
ADDENV("REMUX_CHANNEL_NAME=%s", Channel->Name());
|
||||
ADDENV("REMUX_VTYPE=%d", Channel->Vtype());
|
||||
if (Channel->Vpid())
|
||||
ADDENV("REMUX_VPID=%d", Channel->Vpid());
|
||||
if (Channel->Ppid() != Channel->Vpid())
|
||||
ADDENV("REMUX_PPID=%d", Channel->Ppid());
|
||||
if (Channel->Tpid())
|
||||
ADDENV("REMUX_TPID=%d", Channel->Tpid());
|
||||
}
|
||||
else if (PatPmt) {
|
||||
ADDENV("REMUX_VTYPE=%d", PatPmt->Vtype());
|
||||
if (PatPmt->Vpid())
|
||||
ADDENV("REMUX_VPID=%d", PatPmt->Vpid());
|
||||
if (PatPmt->Ppid() != PatPmt->Vpid())
|
||||
ADDENV("REMUX_PPID=%d", PatPmt->Ppid());
|
||||
}
|
||||
|
||||
std::string buffer;
|
||||
if (Apids && *Apids) {
|
||||
for (const int *pid = Apids; *pid; pid++)
|
||||
(buffer += (const char *) itoa(*pid)) += (*(pid + 1) ? " " : "");
|
||||
ADDENV("REMUX_APID=%s", buffer.c_str());
|
||||
|
||||
buffer.clear();
|
||||
for (const int *pid = Apids; *pid; pid++) {
|
||||
int j;
|
||||
if (Channel) {
|
||||
for (j = 0; Channel->Apid(j) && Channel->Apid(j) != *pid; j++)
|
||||
;
|
||||
(buffer += Channel->Alang(j)) += (*(pid + 1) ? " " : "");
|
||||
}
|
||||
else if (PatPmt) {
|
||||
for (j = 0; PatPmt->Apid(j) && PatPmt->Apid(j) != *pid; j++)
|
||||
;
|
||||
(buffer += PatPmt->Alang(j)) += (*(pid + 1) ? " " : "");
|
||||
}
|
||||
}
|
||||
ADDENV("REMUX_ALANG=%s", buffer.c_str());
|
||||
}
|
||||
|
||||
if (Dpids && *Dpids) {
|
||||
buffer.clear();
|
||||
for (const int *pid = Dpids; *pid; pid++)
|
||||
(buffer += (const char *) itoa(*pid)) += (*(pid + 1) ? " " : "");
|
||||
ADDENV("REMUX_DPID=%s", buffer.c_str());
|
||||
|
||||
buffer.clear();
|
||||
for (const int *pid = Dpids; *pid; pid++) {
|
||||
int j;
|
||||
if (Channel) {
|
||||
for (j = 0; Channel->Dpid(j) && Channel->Dpid(j) != *pid; j++)
|
||||
;
|
||||
(buffer += Channel->Dlang(j)) += (*(pid + 1) ? " " : "");
|
||||
}
|
||||
else if (PatPmt) {
|
||||
for (j = 0; PatPmt->Dpid(j) && PatPmt->Dpid(j) != *pid; j++)
|
||||
;
|
||||
(buffer += PatPmt->Dlang(j)) += (*(pid + 1) ? " " : "");
|
||||
}
|
||||
}
|
||||
ADDENV("REMUX_DLANG=%s", buffer.c_str());
|
||||
}
|
||||
|
||||
if (Channel && Channel->Spid(0)) {
|
||||
buffer.clear();
|
||||
for (const int *pid = Channel->Spids(); *pid; pid++)
|
||||
(buffer += (const char *) itoa(*pid)) += (*(pid + 1) ? " " : "");
|
||||
ADDENV("REMUX_SPID=%s", buffer.c_str());
|
||||
|
||||
buffer.clear();
|
||||
for (int j = 0; Channel->Spid(j); j++)
|
||||
(buffer += Channel->Slang(j)) += (Channel->Spid(j + 1) ? " " : "");
|
||||
ADDENV("REMUX_SLANG=%s", buffer.c_str());
|
||||
}
|
||||
else if (PatPmt && PatPmt->Spid(0)) {
|
||||
buffer.clear();
|
||||
for (const int *pid = PatPmt->Spids(); *pid; pid++)
|
||||
(buffer += (const char *) itoa(*pid)) += (*(pid + 1) ? " " : "");
|
||||
ADDENV("REMUX_SPID=%s", buffer.c_str());
|
||||
|
||||
buffer.clear();
|
||||
for (int j = 0; PatPmt->Spid(j); j++)
|
||||
(buffer += PatPmt->Slang(j)) += (PatPmt->Spid(j + 1) ? " " : "");
|
||||
ADDENV("REMUX_SLANG=%s", buffer.c_str());
|
||||
}
|
||||
|
||||
if (Connection) {
|
||||
// add vars for a CGI like interface
|
||||
// the following vars are not implemented:
|
||||
// REMOTE_HOST, REMOTE_IDENT, REMOTE_USER
|
||||
// CONTENT_TYPE, CONTENT_LENGTH,
|
||||
// SCRIPT_NAME, PATH_TRANSLATED, GATEWAY_INTERFACE
|
||||
ADDENV("REMOTE_ADDR=%s", Connection->RemoteIp().c_str());
|
||||
ADDENV("SERVER_NAME=%s", Connection->LocalIp().c_str());
|
||||
ADDENV("SERVER_PORT=%d", Connection->LocalPort());
|
||||
ADDENV("SERVER_PROTOCOL=%s", Connection->Protocol());
|
||||
ADDENV("SERVER_SOFTWARE=%s", VERSION);
|
||||
|
||||
for (tStrStrMap::const_iterator it = Connection->Headers().begin(); it != Connection->Headers().end(); ++it) {
|
||||
if (i >= MAXENV) {
|
||||
esyslog("streamdev-server: Too many headers for externremux.sh");
|
||||
break;
|
||||
}
|
||||
ADDENV("%s=%s", it->first.c_str(), it->second.c_str());
|
||||
}
|
||||
|
||||
// look for section parameters: /path;param1=value1;param2=value2/
|
||||
std::string::size_type begin, end;
|
||||
const static std::string PATH_INFO("PATH_INFO");
|
||||
|
||||
tStrStrMap::const_iterator it_pathinfo = Connection->Headers().find(PATH_INFO);
|
||||
const std::string& path = it_pathinfo == Connection->Headers().end() ? "/" : it_pathinfo->second;
|
||||
begin = path.find(';', 0);
|
||||
begin = path.find_first_not_of(';', begin);
|
||||
end = path.find_first_of(";/", begin);
|
||||
while (begin != std::string::npos && path[begin] != '/') {
|
||||
std::string param = path.substr(begin, end - begin);
|
||||
std::string::size_type e = param.find('=');
|
||||
|
||||
if (i >= MAXENV) {
|
||||
esyslog("streamdev-server: Too many parameters for externremux.sh");
|
||||
break;
|
||||
}
|
||||
else if (e > 0 && e != std::string::npos) {
|
||||
ADDENV("REMUX_PARAM_%s", param.c_str());
|
||||
}
|
||||
else
|
||||
esyslog("streamdev-server: Invalid externremux.sh parameter %s", param.c_str());
|
||||
|
||||
begin = path.find_first_not_of(';', end);
|
||||
end = path.find_first_of(";/", begin);
|
||||
}
|
||||
}
|
||||
|
||||
env[i] = NULL;
|
||||
|
||||
dup2(inpipe[0], STDIN_FILENO);
|
||||
close(inpipe[1]);
|
||||
dup2(outpipe[1], STDOUT_FILENO);
|
||||
@@ -68,9 +221,20 @@ cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, std::string Parameter):
|
||||
for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++)
|
||||
close(i); //close all dup'ed filedescriptors
|
||||
|
||||
std::string cmd = std::string(g_ExternRemux) + " " + Parameter;
|
||||
execl("/bin/sh", "sh", "-c", cmd.c_str(), NULL);
|
||||
_exit(-1);
|
||||
if (setpgid(0, 0) == -1)
|
||||
esyslog("streamdev-server: externremux setpgid failed: %m");
|
||||
|
||||
if (access(opt_remux, X_OK) == -1) {
|
||||
esyslog("streamdev-server %s: %m", opt_remux);
|
||||
_exit(-1);
|
||||
}
|
||||
|
||||
if (execle("/bin/sh", "sh", "-c", opt_remux, NULL, env) == -1) {
|
||||
esyslog("streamdev-server: externremux script '%s' execution failed: %m", opt_remux);
|
||||
_exit(-1);
|
||||
}
|
||||
// should never be reached
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
close(inpipe[0]);
|
||||
@@ -85,16 +249,31 @@ cTSExt::~cTSExt()
|
||||
m_Active = false;
|
||||
Cancel(3);
|
||||
if (m_Process > 0) {
|
||||
// close pipes
|
||||
close(m_Outpipe);
|
||||
close(m_Inpipe);
|
||||
kill(m_Process, SIGTERM);
|
||||
for (int i = 0; waitpid(m_Process, NULL, WNOHANG) == 0; i++) {
|
||||
if (i == 20) {
|
||||
esyslog("streamdev-server: externremux process won't stop - killing it");
|
||||
kill(m_Process, SIGKILL);
|
||||
}
|
||||
cCondWait::SleepMs(100);
|
||||
// signal and wait for termination
|
||||
if (kill(m_Process, SIGINT) < 0) {
|
||||
esyslog("streamdev-server: externremux SIGINT failed: %m");
|
||||
}
|
||||
else {
|
||||
int i = 0;
|
||||
int retval;
|
||||
while ((retval = waitpid(m_Process, NULL, WNOHANG)) == 0) {
|
||||
|
||||
if ((++i % 20) == 0) {
|
||||
esyslog("streamdev-server: externremux process won't stop - killing it");
|
||||
kill(m_Process, SIGKILL);
|
||||
}
|
||||
cCondWait::SleepMs(100);
|
||||
}
|
||||
|
||||
if (retval < 0)
|
||||
esyslog("streamdev-server: externremux process waitpid failed: %m");
|
||||
else
|
||||
Dprintf("streamdev-server: externremux child (%d) exited as expected\n", m_Process);
|
||||
}
|
||||
m_Process = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +303,7 @@ void cTSExt::Action(void)
|
||||
dsyslog("streamdev-server: buffer full while reading from externremux");
|
||||
|
||||
if (result == -1) {
|
||||
if (errno != EINTR) {
|
||||
if (errno != EINTR && errno != EAGAIN) {
|
||||
LOG_ERROR_STR("read failed");
|
||||
m_Active = false;
|
||||
}
|
||||
@@ -150,9 +329,15 @@ void cTSExt::Put(const uchar *Data, int Count)
|
||||
}
|
||||
}
|
||||
|
||||
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, Parameter))
|
||||
cExternRemux::cExternRemux(const cServerConnection *Connection, const cChannel *Channel, const int *Apids, const int *Dpids):
|
||||
m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE)),
|
||||
m_Remux(new cTSExt(m_ResultBuffer, Connection, Channel, NULL, Apids, Dpids))
|
||||
{
|
||||
m_ResultBuffer->SetTimeouts(500, 100);
|
||||
}
|
||||
cExternRemux::cExternRemux(const cServerConnection *Connection, const cPatPmtParser *PatPmt, const int *Apids, const int *Dpids):
|
||||
m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE)),
|
||||
m_Remux(new cTSExt(m_ResultBuffer, Connection, NULL, PatPmt, Apids, Dpids))
|
||||
{
|
||||
m_ResultBuffer->SetTimeouts(500, 100);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
#include <vdr/ringbuffer.h>
|
||||
#include <string>
|
||||
|
||||
extern const char *g_ExternRemux;
|
||||
class cChannel;
|
||||
class cPatPmtParser;
|
||||
class cServerConnection;
|
||||
|
||||
namespace Streamdev {
|
||||
|
||||
class cTSExt;
|
||||
|
||||
@@ -15,7 +19,8 @@ private:
|
||||
cTSExt *m_Remux;
|
||||
|
||||
public:
|
||||
cExternRemux(int VPid, const int *APids, const int *Dpids, const int *SPids, std::string Parameter);
|
||||
cExternRemux(const cServerConnection *Connection, const cChannel *Channel, const int *APids, const int *Dpids);
|
||||
cExternRemux(const cServerConnection *Connection, const cPatPmtParser *PatPmt, const int *APids, const int *Dpids);
|
||||
virtual ~cExternRemux();
|
||||
|
||||
int Put(const uchar *Data, int Count);
|
||||
@@ -23,4 +28,6 @@ public:
|
||||
void Del(int Count) { m_ResultBuffer->Del(Count); }
|
||||
};
|
||||
|
||||
} // namespace Streamdev
|
||||
|
||||
#endif // VDR_STREAMDEV_EXTERNREMUX_H
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#include "remux/ts2es.h"
|
||||
#include "server/streamer.h"
|
||||
#include "libdvbmpeg/transform.h"
|
||||
#include "common.h"
|
||||
#include <vdr/device.h>
|
||||
|
||||
// from VDR's remux.c
|
||||
#define MAXNONUSEFULDATA (10*1024*1024)
|
||||
|
||||
namespace Streamdev {
|
||||
|
||||
class cTS2ES: public ipack {
|
||||
friend void PutES(uint8_t *Buffer, int Size, void *Data);
|
||||
|
||||
@@ -32,6 +33,9 @@ void PutES(uint8_t *Buffer, int Size, void *Data)
|
||||
This->start = 1;
|
||||
}
|
||||
|
||||
} // namespace Streamdev
|
||||
using namespace Streamdev;
|
||||
|
||||
cTS2ES::cTS2ES(cRingBufferLinear *ResultBuffer)
|
||||
{
|
||||
m_ResultBuffer = ResultBuffer;
|
||||
@@ -75,10 +79,10 @@ void cTS2ES::PutTSPacket(const uint8_t *Buffer) {
|
||||
|
||||
cTS2ESRemux::cTS2ESRemux(int Pid):
|
||||
m_Pid(Pid),
|
||||
m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE, IPACKS)),
|
||||
m_ResultBuffer(new cStreamdevBuffer(WRITERBUFSIZE, IPACKS)),
|
||||
m_Remux(new cTS2ES(m_ResultBuffer))
|
||||
{
|
||||
m_ResultBuffer->SetTimeouts(0, 100);
|
||||
m_ResultBuffer->SetTimeouts(100, 100);
|
||||
}
|
||||
|
||||
cTS2ESRemux::~cTS2ESRemux()
|
||||
@@ -111,8 +115,10 @@ int cTS2ESRemux::Put(const uchar *Data, int Count)
|
||||
break;
|
||||
if (Data[i] != TS_SYNC_BYTE)
|
||||
break;
|
||||
if (m_ResultBuffer->Free() < 2 * IPACKS)
|
||||
if (m_ResultBuffer->Free() < 2 * IPACKS) {
|
||||
m_ResultBuffer->WaitForPut();
|
||||
break; // A cTS2ES might write one full packet and also a small rest
|
||||
}
|
||||
int pid = cTSRemux::GetPid(Data + i + 1);
|
||||
if (Data[i + 3] & 0x10) { // got payload
|
||||
if (m_Pid == pid)
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
#define VDR_STREAMDEV_TS2ESREMUX_H
|
||||
|
||||
#include "remux/tsremux.h"
|
||||
#include <vdr/ringbuffer.h>
|
||||
#include "server/streamer.h"
|
||||
|
||||
namespace Streamdev {
|
||||
|
||||
class cTS2ES;
|
||||
class cRingBufferLinear;
|
||||
|
||||
class cTS2ESRemux: public cTSRemux {
|
||||
private:
|
||||
int m_Pid;
|
||||
cRingBufferLinear *m_ResultBuffer;
|
||||
cStreamdevBuffer *m_ResultBuffer;
|
||||
cTS2ES *m_Remux;
|
||||
|
||||
public:
|
||||
@@ -22,4 +23,6 @@ public:
|
||||
void Del(int Count) { m_ResultBuffer->Del(Count); }
|
||||
};
|
||||
|
||||
} // namespace Streamdev
|
||||
|
||||
#endif // VDR_STREAMDEV_TS2ESREMUX_H
|
||||
|
||||
2017
remux/ts2pes.c
Normal file
2017
remux/ts2pes.c
Normal file
File diff suppressed because it is too large
Load Diff
56
remux/ts2pes.h
Normal file
56
remux/ts2pes.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* ts2pes.h: A streaming MPEG2 remultiplexer
|
||||
*
|
||||
* This file is based on a copy of remux.h from Klaus Schmidinger's
|
||||
* VDR, version 1.6.0.
|
||||
*
|
||||
* $Id: ts2pes.h,v 1.4 2009/07/06 06:11:11 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_TS2PES_H
|
||||
#define VDR_STREAMDEV_TS2PES_H
|
||||
|
||||
#include "remux/tsremux.h"
|
||||
#include "server/streamer.h"
|
||||
|
||||
#define MAXTRACKS 64
|
||||
|
||||
namespace Streamdev {
|
||||
|
||||
class cTS2PES;
|
||||
|
||||
class cTS2PESRemux: public cTSRemux {
|
||||
private:
|
||||
bool noVideo;
|
||||
bool synced;
|
||||
int skipped;
|
||||
cTS2PES *ts2pes[MAXTRACKS];
|
||||
int numTracks;
|
||||
cStreamdevBuffer *resultBuffer;
|
||||
int resultSkipped;
|
||||
public:
|
||||
cTS2PESRemux(int VPid, const int *APids, const int *DPids, const int *SPids);
|
||||
///< Creates a new remuxer for the given PIDs. VPid is the video PID, while
|
||||
///< APids, DPids and SPids are pointers to zero terminated lists of audio,
|
||||
///< dolby and subtitle PIDs (the pointers may be NULL if there is no such
|
||||
///< PID).
|
||||
virtual ~cTS2PESRemux();
|
||||
int Put(const uchar *Data, int Count);
|
||||
///< Puts at most Count bytes of Data into the remuxer.
|
||||
///< \return Returns the number of bytes actually consumed from Data.
|
||||
uchar *Get(int &Count);
|
||||
///< Gets all currently available data from the remuxer.
|
||||
///< \return Count contains the number of bytes the result points to, and
|
||||
void Del(int Count);
|
||||
///< Deletes Count bytes from the remuxer. Count must be the number returned
|
||||
///< from a previous call to Get(). Several calls to Del() with fractions of
|
||||
///< a previously returned Count may be made, but the total sum of all Count
|
||||
///< values must be exactly what the previous Get() has returned.
|
||||
void Clear(void);
|
||||
///< Clears the remuxer of all data it might still contain, keeping the PID
|
||||
///< settings as they are.
|
||||
};
|
||||
|
||||
} // namespace Streamdev
|
||||
|
||||
#endif // VDR_STREAMDEV_TS2PES_H
|
||||
@@ -1,8 +1,12 @@
|
||||
#ifdef STREAMDEV_PS
|
||||
|
||||
#include "remux/ts2ps.h"
|
||||
#include "server/streamer.h"
|
||||
#include <vdr/channels.h>
|
||||
#include <vdr/device.h>
|
||||
|
||||
namespace Streamdev {
|
||||
|
||||
class cTS2PS {
|
||||
friend void PutPES(uint8_t *Buffer, int Size, void *Data);
|
||||
|
||||
@@ -28,6 +32,9 @@ void PutPES(uint8_t *Buffer, int Size, void *Data)
|
||||
esyslog("ERROR: result buffer overflow, dropped %d out of %d byte", Size - n, Size);
|
||||
}
|
||||
|
||||
} // namespace Streamdev
|
||||
using namespace Streamdev;
|
||||
|
||||
cTS2PS::cTS2PS(cRingBufferLinear *ResultBuffer, int Pid, uint8_t AudioCid)
|
||||
{
|
||||
m_ResultBuffer = ResultBuffer;
|
||||
@@ -74,13 +81,13 @@ void cTS2PS::PutTSPacket(const uint8_t *Buffer)
|
||||
|
||||
cTS2PSRemux::cTS2PSRemux(int VPid, const int *APids, const int *DPids, const int *SPids):
|
||||
m_NumTracks(0),
|
||||
m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE, IPACKS)),
|
||||
m_ResultBuffer(new cStreamdevBuffer(WRITERBUFSIZE, IPACKS)),
|
||||
m_ResultSkipped(0),
|
||||
m_Skipped(0),
|
||||
m_Synced(false),
|
||||
m_IsRadio(VPid == 0 || VPid == 1 || VPid == 0x1FFF)
|
||||
{
|
||||
m_ResultBuffer->SetTimeouts(0, 100);
|
||||
m_ResultBuffer->SetTimeouts(100, 100);
|
||||
|
||||
if (VPid)
|
||||
m_Remux[m_NumTracks++] = new cTS2PS(m_ResultBuffer, VPid);
|
||||
@@ -124,8 +131,10 @@ int cTS2PSRemux::Put(const uchar *Data, int Count)
|
||||
break;
|
||||
if (Data[i] != TS_SYNC_BYTE)
|
||||
break;
|
||||
if (m_ResultBuffer->Free() < 2 * IPACKS)
|
||||
if (m_ResultBuffer->Free() < 2 * IPACKS) {
|
||||
m_ResultBuffer->WaitForPut();
|
||||
break; // A cTS2PS might write one full packet and also a small rest
|
||||
}
|
||||
int pid = GetPid(Data + i + 1);
|
||||
if (Data[i + 3] & 0x10) { // got payload
|
||||
for (int t = 0; t < m_NumTracks; t++) {
|
||||
@@ -209,3 +218,4 @@ uchar *cTS2PSRemux::Get(int &Count)
|
||||
return resultData;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
#ifndef VDR_STREAMDEV_TS2PESREMUX_H
|
||||
#define VDR_STREAMDEV_TS2PESREMUX_H
|
||||
#ifdef STREAMDEV_PS
|
||||
|
||||
#ifndef VDR_STREAMDEV_TS2PSREMUX_H
|
||||
#define VDR_STREAMDEV_TS2PSREMUX_H
|
||||
|
||||
#include "remux/tsremux.h"
|
||||
#include <vdr/remux.h>
|
||||
#include <vdr/ringbuffer.h>
|
||||
#include "server/streamer.h"
|
||||
|
||||
#ifndef MAXTRACKS
|
||||
#define MAXTRACKS 64
|
||||
#endif
|
||||
|
||||
namespace Streamdev {
|
||||
|
||||
class cTS2PS;
|
||||
|
||||
@@ -11,7 +18,7 @@ class cTS2PSRemux: public cTSRemux {
|
||||
private:
|
||||
int m_NumTracks;
|
||||
cTS2PS *m_Remux[MAXTRACKS];
|
||||
cRingBufferLinear *m_ResultBuffer;
|
||||
cStreamdevBuffer *m_ResultBuffer;
|
||||
int m_ResultSkipped;
|
||||
int m_Skipped;
|
||||
bool m_Synced;
|
||||
@@ -26,4 +33,8 @@ public:
|
||||
void Del(int Count) { m_ResultBuffer->Del(Count); }
|
||||
};
|
||||
|
||||
#endif // VDR_STREAMDEV_TS2PESREMUX_H
|
||||
} // namespace Streamdev
|
||||
|
||||
#endif // VDR_STREAMDEV_TS2PSREMUX_H
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
#include "remux/tsremux.h"
|
||||
|
||||
#define SC_PICTURE 0x00 // "picture header"
|
||||
#define SC_PICTURE 0x00 // "picture header"
|
||||
#define PID_MASK_HI 0x1F
|
||||
#define VIDEO_STREAM_S 0xE0
|
||||
|
||||
using namespace Streamdev;
|
||||
|
||||
void cTSRemux::SetBrokenLink(uchar *Data, int Length)
|
||||
{
|
||||
if (Length > 9 && Data[0] == 0 && Data[1] == 0 && Data[2] == 1 && (Data[3] & 0xF0) == VIDEO_STREAM_S) {
|
||||
for (int i = Data[8] + 9; i < Length - 7; i++) { // +9 to skip video packet header
|
||||
int PesPayloadOffset = 0;
|
||||
if (AnalyzePesHeader(Data, Length, PesPayloadOffset) >= phMPEG1 && (Data[3] & 0xF0) == VIDEO_STREAM_S) {
|
||||
for (int i = PesPayloadOffset; i < Length - 7; i++) {
|
||||
if (Data[i] == 0 && Data[i + 1] == 0 && Data[i + 2] == 1 && Data[i + 3] == 0xB8) {
|
||||
if (!(Data[i + 7] & 0x40)) // set flag only if GOP is not closed
|
||||
Data[i + 7] |= 0x20;
|
||||
@@ -39,17 +44,40 @@ int cTSRemux::ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &P
|
||||
// If the return value is -1 the packet was not completely in the buffer.
|
||||
int Length = GetPacketLength(Data, Count, Offset);
|
||||
if (Length > 0) {
|
||||
if (Length >= 8) {
|
||||
int i = Offset + 8; // the minimum length of the video packet header
|
||||
i += Data[i] + 1; // possible additional header bytes
|
||||
for (; i < Offset + Length - 5; i++) {
|
||||
if (Data[i] == 0 && Data[i + 1] == 0 && Data[i + 2] == 1) {
|
||||
switch (Data[i + 3]) {
|
||||
case SC_PICTURE: PictureType = (Data[i + 5] >> 3) & 0x07;
|
||||
return Length;
|
||||
int PesPayloadOffset = 0;
|
||||
if (AnalyzePesHeader(Data + Offset, Length, PesPayloadOffset) >= phMPEG1) {
|
||||
const uchar *p = Data + Offset + PesPayloadOffset + 2;
|
||||
const uchar *pLimit = Data + Offset + Length - 3;
|
||||
#ifdef TEST_cVideoRepacker
|
||||
// cVideoRepacker ensures that a new PES packet is started for a new sequence,
|
||||
// group or picture which allows us to easily skip scanning through a huge
|
||||
// amount of video data.
|
||||
if (p < pLimit) {
|
||||
if (p[-2] || p[-1] || p[0] != 0x01)
|
||||
pLimit = 0; // skip scanning: packet doesn't start with 0x000001
|
||||
else {
|
||||
switch (p[1]) {
|
||||
case SC_SEQUENCE:
|
||||
case SC_GROUP:
|
||||
case SC_PICTURE:
|
||||
break;
|
||||
default: // skip scanning: packet doesn't start a new sequence, group or picture
|
||||
pLimit = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
while (p < pLimit && (p = (const uchar *)memchr(p, 0x01, pLimit - p))) {
|
||||
if (!p[-2] && !p[-1]) { // found 0x000001
|
||||
switch (p[1]) {
|
||||
case SC_PICTURE: PictureType = (p[3] >> 3) & 0x07;
|
||||
return Length;
|
||||
}
|
||||
p += 4; // continue scanning after 0x01ssxxyy
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
p += 3; // continue scanning after 0x01xxyy
|
||||
}
|
||||
}
|
||||
PictureType = NO_PICTURE;
|
||||
return Length;
|
||||
|
||||
@@ -4,30 +4,25 @@
|
||||
#include "libdvbmpeg/transform.h"
|
||||
#include <vdr/remux.h>
|
||||
|
||||
#define RESULTBUFFERSIZE KILOBYTE(256)
|
||||
// Picture types:
|
||||
#define NO_PICTURE 0
|
||||
|
||||
namespace Streamdev {
|
||||
|
||||
class cTSRemux {
|
||||
protected:
|
||||
/*uchar m_ResultBuffer[RESULTBUFFERSIZE];
|
||||
int m_ResultCount;
|
||||
int m_ResultDelivered;
|
||||
int m_Synced;
|
||||
int m_Skipped;
|
||||
int m_Sync;
|
||||
|
||||
|
||||
virtual void PutTSPacket(int Pid, const uint8_t *Data) = 0;
|
||||
|
||||
public:
|
||||
cTSRemux(bool Sync = true);
|
||||
virtual ~cTSRemux();
|
||||
virtual ~cTSRemux() {};
|
||||
|
||||
virtual uchar *Process(const uchar *Data, int &Count, int &Result);*/
|
||||
virtual int Put(const uchar *Data, int Count) = 0;
|
||||
virtual uchar *Get(int &Count) = 0;
|
||||
virtual void Del(int Count) = 0;
|
||||
|
||||
static void SetBrokenLink(uchar *Data, int Length);
|
||||
static int GetPid(const uchar *Data);
|
||||
static int GetPacketLength(const uchar *Data, int Count, int Offset);
|
||||
static int GetPacketLength(const uchar *Data, int Count, int Offset);
|
||||
static int ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &PictureType);
|
||||
};
|
||||
|
||||
} // namespace Streamdev
|
||||
|
||||
#endif // VDR_STREAMDEV_TSREMUX_H
|
||||
|
||||
89
server/Makefile
Normal file
89
server/Makefile
Normal file
@@ -0,0 +1,89 @@
|
||||
#
|
||||
# Makefile for a Video Disk Recorder plugin
|
||||
#
|
||||
# $Id: $
|
||||
|
||||
# The official name of this plugin.
|
||||
# This name will be used in the '-P...' option of VDR to load the plugin.
|
||||
# By default the main source file also carries this name.
|
||||
|
||||
PLUGIN = streamdev-server
|
||||
|
||||
### The name of the shared object file:
|
||||
|
||||
SOFILE = libvdr-$(PLUGIN).so
|
||||
|
||||
### Includes and Defines (add further entries here):
|
||||
|
||||
DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
|
||||
|
||||
### The object files (add further files here):
|
||||
|
||||
COMMONOBJS = ../common.o
|
||||
|
||||
SERVEROBJS = $(PLUGIN).o \
|
||||
server.o component.o connection.o \
|
||||
componentVTP.o connectionVTP.o \
|
||||
componentHTTP.o connectionHTTP.o menuHTTP.o \
|
||||
componentIGMP.o connectionIGMP.o \
|
||||
streamer.o livestreamer.o livefilter.o recstreamer.o recplayer.o \
|
||||
menu.o suspend.o setup.o
|
||||
|
||||
### The main target:
|
||||
|
||||
all: $(SOFILE) i18n
|
||||
|
||||
### Implicit rules:
|
||||
|
||||
%.o: %.c
|
||||
$(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $<
|
||||
|
||||
### Dependencies:
|
||||
|
||||
MAKEDEP = $(CXX) -MM -MG
|
||||
DEPFILE = .dependencies
|
||||
$(DEPFILE): Makefile
|
||||
@$(MAKEDEP) $(CXXFLAGS) $(DEFINES) $(INCLUDES) $(SERVEROBJS:%.o=%.c) $(COMMONOBJS:%.o=%.c) > $@
|
||||
|
||||
-include $(DEPFILE)
|
||||
|
||||
### Internationalization (I18N):
|
||||
|
||||
PODIR = po
|
||||
I18Npo = $(wildcard $(PODIR)/*.po)
|
||||
I18Nmo = $(addsuffix .mo, $(foreach file, $(I18Npo), $(basename $(file))))
|
||||
I18Nmsgs = $(addprefix $(DESTDIR)$(LOCDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
|
||||
I18Npot = $(PODIR)/$(PLUGIN).pot
|
||||
|
||||
%.mo: %.po
|
||||
msgfmt -c -o $@ $<
|
||||
|
||||
$(I18Npot): $(SERVEROBJS:%.o=%.c)
|
||||
xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name=vdr-$(PLUGIN) --package-version=$(VERSION) --msgid-bugs-address='<vdrdev@schmirler.de>' -o $@ `ls $^`
|
||||
|
||||
%.po: $(I18Npot)
|
||||
msgmerge -U --no-wrap --no-location --backup=none -q -N $@ $<
|
||||
@touch $@
|
||||
|
||||
$(I18Nmsgs): $(DESTDIR)$(LOCDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
|
||||
install -D -m644 $< $@
|
||||
|
||||
.PHONY: i18n
|
||||
i18n: $(I18Nmo) $(I18Npot)
|
||||
|
||||
install-i18n: $(I18Nmsgs)
|
||||
|
||||
### Targets:
|
||||
|
||||
$(SOFILE): $(SERVEROBJS) $(COMMONOBJS) \
|
||||
../tools/sockettools.a ../remux/remux.a ../libdvbmpeg/libdvbmpegtools.a
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $^ -o $@
|
||||
|
||||
install-lib: $(SOFILE)
|
||||
install -D $^ $(DESTDIR)$(LIBDIR)/$^.$(APIVERSION)
|
||||
|
||||
install: install-lib install-i18n
|
||||
|
||||
clean:
|
||||
@-rm -f $(PODIR)/*.mo $(PODIR)/*.pot
|
||||
@-rm -f $(COMMONOBJS) $(SERVEROBJS) $(DEPFILE) *.so *.tgz core* *~
|
||||
82
server/Makefile-1.7.33
Normal file
82
server/Makefile-1.7.33
Normal file
@@ -0,0 +1,82 @@
|
||||
#
|
||||
# Makefile for a Video Disk Recorder plugin
|
||||
#
|
||||
# $Id: Makefile,v 1.2 2010/07/19 13:49:31 schmirl Exp $
|
||||
|
||||
# The official name of this plugin.
|
||||
# This name will be used in the '-P...' option of VDR to load the plugin.
|
||||
# By default the main source file also carries this name.
|
||||
#
|
||||
PLUGIN = streamdev-server
|
||||
|
||||
### Includes and Defines (add further entries here):
|
||||
|
||||
DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
|
||||
|
||||
### The object files (add further files here):
|
||||
|
||||
COMMONOBJS = ../common.o
|
||||
|
||||
SERVEROBJS = $(PLUGIN).o \
|
||||
server.o component.o connection.o \
|
||||
componentVTP.o connectionVTP.o \
|
||||
componentHTTP.o connectionHTTP.o menuHTTP.o \
|
||||
componentIGMP.o connectionIGMP.o \
|
||||
streamer.o livestreamer.o livefilter.o recstreamer.o recplayer.o \
|
||||
menu.o suspend.o setup.o
|
||||
|
||||
### The main target:
|
||||
|
||||
.PHONY: all i18n clean
|
||||
all: libvdr-$(PLUGIN).so i18n
|
||||
|
||||
### Implicit rules:
|
||||
|
||||
%.o: %.c
|
||||
$(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $<
|
||||
|
||||
### Dependencies:
|
||||
|
||||
MAKEDEP = $(CXX) -MM -MG
|
||||
DEPFILE = .dependencies
|
||||
|
||||
$(DEPFILE): Makefile
|
||||
@$(MAKEDEP) $(DEFINES) $(INCLUDES) $(SERVEROBJS:%.o=%.c) $(COMMONOBJS:%.o=%.c) > $@
|
||||
|
||||
-include $(DEPFILE)
|
||||
|
||||
### Internationalization (I18N):
|
||||
|
||||
PODIR = po
|
||||
LOCALEDIR = $(VDRDIR)/locale
|
||||
I18Npo = $(wildcard $(PODIR)/*.po)
|
||||
I18Nmsgs = $(addprefix $(LOCALEDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
|
||||
I18Npot = $(PODIR)/$(PLUGIN).pot
|
||||
|
||||
%.mo: %.po
|
||||
msgfmt -c -o $@ $<
|
||||
|
||||
$(I18Npot): $(SERVEROBJS:%.o=%.c)
|
||||
xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --msgid-bugs-address='<http://www.vdr-developer.org/mantisbt/>' -o $@ $^
|
||||
|
||||
%.po: $(I18Npot)
|
||||
msgmerge -U --no-wrap --no-location --backup=none -q $@ $<
|
||||
@touch $@
|
||||
|
||||
$(I18Nmsgs): $(LOCALEDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
|
||||
@mkdir -p $(dir $@)
|
||||
cp $< $@
|
||||
|
||||
i18n: $(I18Nmsgs)
|
||||
|
||||
### Targets:
|
||||
|
||||
libvdr-$(PLUGIN).so: $(SERVEROBJS) $(COMMONOBJS) \
|
||||
../tools/sockettools.a ../remux/remux.a ../libdvbmpeg/libdvbmpegtools.a
|
||||
|
||||
%.so:
|
||||
$(CXX) $(CXXFLAGS) -shared $^ -o $@
|
||||
@cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION)
|
||||
|
||||
clean:
|
||||
@-rm -f $(COMMONOBJS) $(SERVEROBJS) $(DEPFILE) $(PODIR)/*.mo $(PODIR)/*.pot *.so *.tgz core* *~
|
||||
@@ -1,13 +1,14 @@
|
||||
/*
|
||||
* $Id: component.c,v 1.3 2005/05/09 20:22:29 lordjaxom Exp $
|
||||
* $Id: component.c,v 1.4 2009/02/13 10:39:22 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include "server/component.h"
|
||||
#include "server/connection.h"
|
||||
|
||||
cServerComponent::cServerComponent(const char *Protocol, const char *ListenIp,
|
||||
uint ListenPort):
|
||||
uint ListenPort, int Type, int IpProto):
|
||||
m_Protocol(Protocol),
|
||||
m_Listen(Type, IpProto),
|
||||
m_ListenIp(ListenIp),
|
||||
m_ListenPort(ListenPort)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: component.h,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $
|
||||
* $Id: component.h,v 1.3 2009/02/13 10:39:22 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SERVERS_COMPONENT_H
|
||||
@@ -17,8 +17,8 @@ class cServerConnection;
|
||||
|
||||
class cServerComponent: public cListObject {
|
||||
private:
|
||||
cTBSocket m_Listen;
|
||||
const char *m_Protocol;
|
||||
cTBSocket m_Listen;
|
||||
const char *m_ListenIp;
|
||||
uint m_ListenPort;
|
||||
|
||||
@@ -27,7 +27,7 @@ protected:
|
||||
virtual cServerConnection *NewClient(void) = 0;
|
||||
|
||||
public:
|
||||
cServerComponent(const char *Protocol, const char *ListenIp, uint ListenPort);
|
||||
cServerComponent(const char *Protocol, const char *ListenIp, uint ListenPort, int Type = SOCK_STREAM, int IpProto = 0);
|
||||
virtual ~cServerComponent();
|
||||
|
||||
/* Starts listening on the specified Port, override if you want to do things
|
||||
|
||||
488
server/componentIGMP.c
Normal file
488
server/componentIGMP.c
Normal file
@@ -0,0 +1,488 @@
|
||||
/*
|
||||
* $Id: componentIGMP.c,v 1.2 2009/07/03 21:44:19 schmirl Exp $
|
||||
*/
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/igmp.h>
|
||||
|
||||
#include "server/componentIGMP.h"
|
||||
#include "server/connectionIGMP.h"
|
||||
#include "server/server.h"
|
||||
#include "server/setup.h"
|
||||
|
||||
#ifndef IGMP_ALL_HOSTS
|
||||
#define IGMP_ALL_HOSTS htonl(0xE0000001L)
|
||||
#endif
|
||||
#ifndef IGMP_ALL_ROUTER
|
||||
#define IGMP_ALL_ROUTER htonl(0xE0000002L)
|
||||
#endif
|
||||
|
||||
// IGMP parameters according to RFC2236. All time values in seconds.
|
||||
#define IGMP_ROBUSTNESS 2
|
||||
#define IGMP_QUERY_INTERVAL 125
|
||||
#define IGMP_QUERY_RESPONSE_INTERVAL 10
|
||||
#define IGMP_GROUP_MEMBERSHIP_INTERVAL (2 * IGMP_QUERY_INTERVAL + IGMP_QUERY_RESPONSE_INTERVAL)
|
||||
#define IGMP_OTHER_QUERIER_PRESENT_INTERVAL (2 * IGMP_QUERY_INTERVAL + IGMP_QUERY_RESPONSE_INTERVAL / 2)
|
||||
#define IGMP_STARTUP_QUERY_INTERVAL (IGMP_QUERY_INTERVAL / 4)
|
||||
#define IGMP_STARTUP_QUERY_COUNT IGMP_ROBUSTNESS
|
||||
// This value is 1/10 sec. RFC default is 10. Reduced to minimum to free unused channels ASAP
|
||||
#define IGMP_LAST_MEMBER_QUERY_INTERVAL_TS 1
|
||||
#define IGMP_LAST_MEMBER_QUERY_COUNT IGMP_ROBUSTNESS
|
||||
|
||||
// operations on struct timeval
|
||||
#define TV_CMP(a, cmp, b) (a.tv_sec == b.tv_sec ? a.tv_usec cmp b.tv_usec : a.tv_sec cmp b.tv_sec)
|
||||
#define TV_SET(tv) (tv.tv_sec || tv.tv_usec)
|
||||
#define TV_CLR(tv) memset(&tv, 0, sizeof(tv))
|
||||
#define TV_CPY(dst, src) memcpy(&dst, &src, sizeof(dst))
|
||||
#define TV_ADD(dst, ts) dst.tv_sec += ts / 10; dst.tv_usec += (ts % 10) * 100000; if (dst.tv_usec >= 1000000) { dst.tv_usec -= 1000000; dst.tv_sec++; }
|
||||
|
||||
class cMulticastGroup: public cListObject
|
||||
{
|
||||
public:
|
||||
in_addr_t group;
|
||||
in_addr_t reporter;
|
||||
struct timeval timeout;
|
||||
struct timeval v1timer;
|
||||
struct timeval retransmit;
|
||||
|
||||
cMulticastGroup(in_addr_t Group);
|
||||
};
|
||||
|
||||
cMulticastGroup::cMulticastGroup(in_addr_t Group) :
|
||||
group(Group),
|
||||
reporter(0)
|
||||
{
|
||||
TV_CLR(timeout);
|
||||
TV_CLR(v1timer);
|
||||
TV_CLR(retransmit);
|
||||
}
|
||||
|
||||
void logIGMP(uint8_t type, struct in_addr Src, struct in_addr Dst, struct in_addr Grp)
|
||||
{
|
||||
const char* msg;
|
||||
switch (type) {
|
||||
case IGMP_MEMBERSHIP_QUERY: msg = "membership query"; break;
|
||||
case IGMP_V1_MEMBERSHIP_REPORT: msg = "V1 membership report"; break;
|
||||
case IGMP_V2_MEMBERSHIP_REPORT: msg = "V2 membership report"; break;
|
||||
case IGMP_V2_LEAVE_GROUP: msg = "leave group"; break;
|
||||
default: msg = "unknown"; break;
|
||||
}
|
||||
char* s = strdup(inet_ntoa(Src));
|
||||
char* d = strdup(inet_ntoa(Dst));
|
||||
dsyslog("streamdev-server IGMP: Received %s from %s (dst %s) for %s", msg, s, d, inet_ntoa(Grp));
|
||||
free(s);
|
||||
free(d);
|
||||
}
|
||||
|
||||
/* Taken from http://tools.ietf.org/html/rfc1071 */
|
||||
uint16_t inetChecksum(uint16_t *addr, int count)
|
||||
{
|
||||
uint32_t sum = 0;
|
||||
while (count > 1) {
|
||||
sum += *addr++;
|
||||
count -= 2;
|
||||
}
|
||||
|
||||
if( count > 0 )
|
||||
sum += * (uint8_t *) addr;
|
||||
|
||||
while (sum>>16)
|
||||
sum = (sum & 0xffff) + (sum >> 16);
|
||||
|
||||
return ~sum;
|
||||
}
|
||||
|
||||
cComponentIGMP::cComponentIGMP(void):
|
||||
cServerComponent("IGMP", "0.0.0.0", 0, SOCK_RAW, IPPROTO_IGMP),
|
||||
cThread("IGMP timeout handler"),
|
||||
m_BindIp(inet_addr(StreamdevServerSetup.IGMPBindIP)),
|
||||
m_MaxChannelNumber(0),
|
||||
m_StartupQueryCount(IGMP_STARTUP_QUERY_COUNT),
|
||||
m_Querier(true)
|
||||
{
|
||||
}
|
||||
|
||||
cComponentIGMP::~cComponentIGMP(void)
|
||||
{
|
||||
}
|
||||
|
||||
#if APIVERSNUM >= 20300
|
||||
cMulticastGroup* cComponentIGMP::FindGroup(in_addr_t Group)
|
||||
#else
|
||||
cMulticastGroup* cComponentIGMP::FindGroup(in_addr_t Group) const
|
||||
#endif
|
||||
{
|
||||
cMulticastGroup *group = m_Groups.First();
|
||||
while (group && group->group != Group)
|
||||
group = m_Groups.Next(group);
|
||||
return group;
|
||||
}
|
||||
|
||||
bool cComponentIGMP::Initialize(void)
|
||||
{
|
||||
if (cServerComponent::Initialize() && IGMPMembership(IGMP_ALL_ROUTER))
|
||||
{
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_CHANNELS_READ;
|
||||
for (const cChannel *channel = Channels->First(); channel; channel = Channels->Next(channel))
|
||||
#else
|
||||
for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel))
|
||||
#endif
|
||||
{
|
||||
if (channel->GroupSep())
|
||||
continue;
|
||||
int num = channel->Number();
|
||||
if (!IGMPMembership(htonl(MULTICAST_PRIV_MIN + num)))
|
||||
break;
|
||||
m_MaxChannelNumber = num;
|
||||
}
|
||||
if (m_MaxChannelNumber == 0)
|
||||
{
|
||||
IGMPMembership(IGMP_ALL_ROUTER, false);
|
||||
esyslog("streamdev-server IGMP: no multicast group joined");
|
||||
}
|
||||
else
|
||||
{
|
||||
Start();
|
||||
}
|
||||
}
|
||||
return m_MaxChannelNumber > 0;
|
||||
}
|
||||
|
||||
void cComponentIGMP::Destruct(void)
|
||||
{
|
||||
if (m_MaxChannelNumber > 0)
|
||||
{
|
||||
Cancel(-1);
|
||||
m_CondWait.Signal();
|
||||
Cancel(2);
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_CHANNELS_READ;
|
||||
for (const cChannel *channel = Channels->First(); channel; channel = Channels->Next(channel))
|
||||
#else
|
||||
for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel))
|
||||
#endif
|
||||
{
|
||||
if (channel->GroupSep())
|
||||
continue;
|
||||
int num = channel->Number();
|
||||
if (num > m_MaxChannelNumber)
|
||||
break;
|
||||
IGMPMembership(htonl(MULTICAST_PRIV_MIN + num), false);
|
||||
}
|
||||
IGMPMembership(IGMP_ALL_ROUTER, false);
|
||||
}
|
||||
m_MaxChannelNumber = 0;
|
||||
cServerComponent::Destruct();
|
||||
}
|
||||
|
||||
cServerConnection *cComponentIGMP::NewClient(void)
|
||||
{
|
||||
return new cConnectionIGMP("IGMP", StreamdevServerSetup.IGMPClientPort, (eStreamType) StreamdevServerSetup.IGMPStreamType);
|
||||
}
|
||||
|
||||
cServerConnection* cComponentIGMP::Accept(void)
|
||||
{
|
||||
ssize_t recv_len;
|
||||
int ip_hdrlen, ip_datalen;
|
||||
struct ip *ip;
|
||||
struct igmp *igmp;
|
||||
|
||||
while ((recv_len = ::recvfrom(Socket(), m_ReadBuffer, sizeof(m_ReadBuffer), 0, NULL, NULL)) < 0 && errno == EINTR)
|
||||
errno = 0;
|
||||
|
||||
if (recv_len < 0) {
|
||||
esyslog("streamdev-server IGMP: read failed: %m");
|
||||
return NULL;
|
||||
}
|
||||
else if (recv_len < (ssize_t) sizeof(struct ip)) {
|
||||
esyslog("streamdev-server IGMP: IP packet too short");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ip = (struct ip*) m_ReadBuffer;
|
||||
|
||||
// filter out my own packets
|
||||
if (ip->ip_src.s_addr == m_BindIp)
|
||||
return NULL;
|
||||
|
||||
ip_hdrlen = ip->ip_hl << 2;
|
||||
#ifdef __FreeBSD__
|
||||
ip_datalen = ip->ip_len;
|
||||
#else
|
||||
ip_datalen = ntohs(ip->ip_len) - ip_hdrlen;
|
||||
#endif
|
||||
if (ip->ip_p != IPPROTO_IGMP) {
|
||||
esyslog("streamdev-server IGMP: Unexpected protocol %hhu", ip->ip_p);
|
||||
return NULL;
|
||||
}
|
||||
if (recv_len < ip_hdrlen + IGMP_MINLEN) {
|
||||
esyslog("streamdev-server IGMP: packet too short");
|
||||
return NULL;
|
||||
}
|
||||
igmp = (struct igmp*) (m_ReadBuffer + ip_hdrlen);
|
||||
uint16_t chksum = igmp->igmp_cksum;
|
||||
igmp->igmp_cksum = 0;
|
||||
if (chksum != inetChecksum((uint16_t *)igmp, ip_datalen))
|
||||
{
|
||||
esyslog("INVALID CHECKSUM %d %d %d %lu 0x%x 0x%x", (int) ntohs(ip->ip_len), ip_hdrlen, ip_datalen, (unsigned long int) recv_len, chksum, inetChecksum((uint16_t *)igmp, ip_datalen));
|
||||
return NULL;
|
||||
}
|
||||
logIGMP(igmp->igmp_type, ip->ip_src, ip->ip_dst, igmp->igmp_group);
|
||||
return ProcessMessage(igmp, igmp->igmp_group.s_addr, ip->ip_src.s_addr);
|
||||
}
|
||||
|
||||
cServerConnection* cComponentIGMP::ProcessMessage(struct igmp *Igmp, in_addr_t Group, in_addr_t Sender)
|
||||
{
|
||||
cServerConnection* conn = NULL;
|
||||
cMulticastGroup* group;
|
||||
LOCK_THREAD;
|
||||
switch (Igmp->igmp_type) {
|
||||
case IGMP_MEMBERSHIP_QUERY:
|
||||
if (ntohl(Sender) < ntohl(m_BindIp))
|
||||
IGMPStartOtherQuerierPresentTimer();
|
||||
break;
|
||||
case IGMP_V1_MEMBERSHIP_REPORT:
|
||||
case IGMP_V2_MEMBERSHIP_REPORT:
|
||||
group = FindGroup(Group);
|
||||
if (!group) {
|
||||
group = new cMulticastGroup(Group);
|
||||
m_Groups.Add(group);
|
||||
}
|
||||
conn = IGMPStartMulticast(group);
|
||||
IGMPStartTimer(group, Sender);
|
||||
if (Igmp->igmp_type == IGMP_V1_MEMBERSHIP_REPORT)
|
||||
IGMPStartV1HostTimer(group);
|
||||
break;
|
||||
case IGMP_V2_LEAVE_GROUP:
|
||||
group = FindGroup(Group);
|
||||
if (group && !TV_SET(group->v1timer)) {
|
||||
if (group->reporter == Sender) {
|
||||
IGMPStartTimerAfterLeave(group, m_Querier ? IGMP_LAST_MEMBER_QUERY_INTERVAL_TS : Igmp->igmp_code);
|
||||
if (m_Querier)
|
||||
IGMPSendGroupQuery(group);
|
||||
IGMPStartRetransmitTimer(group);
|
||||
}
|
||||
m_CondWait.Signal();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
void cComponentIGMP::Action()
|
||||
{
|
||||
while (Running()) {
|
||||
struct timeval now;
|
||||
struct timeval next;
|
||||
|
||||
gettimeofday(&now, NULL);
|
||||
TV_CPY(next, now);
|
||||
next.tv_sec += IGMP_QUERY_INTERVAL;
|
||||
|
||||
cMulticastGroup *del = NULL;
|
||||
{
|
||||
LOCK_THREAD;
|
||||
if (TV_CMP(m_GeneralQueryTimer, <, now)) {
|
||||
dsyslog("General Query");
|
||||
IGMPSendGeneralQuery();
|
||||
IGMPStartGeneralQueryTimer();
|
||||
}
|
||||
if (TV_CMP(next, >, m_GeneralQueryTimer))
|
||||
TV_CPY(next, m_GeneralQueryTimer);
|
||||
|
||||
for (cMulticastGroup *group = m_Groups.First(); group; group = m_Groups.Next(group)) {
|
||||
if (TV_CMP(group->timeout, <, now)) {
|
||||
IGMPStopMulticast(group);
|
||||
IGMPClearRetransmitTimer(group);
|
||||
if (del)
|
||||
m_Groups.Del(del);
|
||||
del = group;
|
||||
}
|
||||
else if (m_Querier && TV_SET(group->retransmit) && TV_CMP(group->retransmit, <, now)) {
|
||||
IGMPSendGroupQuery(group);
|
||||
IGMPStartRetransmitTimer(group);
|
||||
if (TV_CMP(next, >, group->retransmit))
|
||||
TV_CPY(next, group->retransmit);
|
||||
}
|
||||
else if (TV_SET(group->v1timer) && TV_CMP(group->v1timer, <, now)) {
|
||||
TV_CLR(group->v1timer);
|
||||
}
|
||||
else {
|
||||
if (TV_CMP(next, >, group->timeout))
|
||||
TV_CPY(next, group->timeout);
|
||||
if (TV_SET(group->retransmit) && TV_CMP(next, >, group->retransmit))
|
||||
TV_CPY(next, group->retransmit);
|
||||
if (TV_SET(group->v1timer) && TV_CMP(next, >, group->v1timer))
|
||||
TV_CPY(next, group->v1timer);
|
||||
}
|
||||
}
|
||||
if (del)
|
||||
m_Groups.Del(del);
|
||||
}
|
||||
|
||||
int sleep = (next.tv_sec - now.tv_sec) * 1000;
|
||||
sleep += (next.tv_usec - now.tv_usec) / 1000;
|
||||
if (next.tv_usec < now.tv_usec)
|
||||
sleep += 1000;
|
||||
dsyslog("Sleeping %d ms", sleep);
|
||||
m_CondWait.Wait(sleep);
|
||||
}
|
||||
}
|
||||
|
||||
bool cComponentIGMP::IGMPMembership(in_addr_t Group, bool Add)
|
||||
{
|
||||
struct ip_mreqn mreq;
|
||||
mreq.imr_multiaddr.s_addr = Group;
|
||||
mreq.imr_address.s_addr = INADDR_ANY;
|
||||
mreq.imr_ifindex = 0;
|
||||
if (setsockopt(Socket(), IPPROTO_IP, Add ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
|
||||
{
|
||||
esyslog("streamdev-server IGMP: unable to %s %s: %m", Add ? "join" : "leave", inet_ntoa(mreq.imr_multiaddr));
|
||||
if (errno == ENOBUFS)
|
||||
esyslog("consider increasing sys.net.ipv4.igmp_max_memberships");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void cComponentIGMP::IGMPSendQuery(in_addr_t Group, int Timeout)
|
||||
{
|
||||
struct sockaddr_in dst;
|
||||
struct igmp query;
|
||||
|
||||
dst.sin_family = AF_INET;
|
||||
dst.sin_port = IPPROTO_IGMP;
|
||||
dst.sin_addr.s_addr = Group;
|
||||
query.igmp_type = IGMP_MEMBERSHIP_QUERY;
|
||||
query.igmp_code = Timeout * 10;
|
||||
query.igmp_cksum = 0;
|
||||
query.igmp_group.s_addr = (Group == IGMP_ALL_HOSTS) ? 0 : Group;
|
||||
query.igmp_cksum = inetChecksum((uint16_t *) &query, sizeof(query));
|
||||
|
||||
for (int i = 0; i < 5 && ::sendto(Socket(), &query, sizeof(query), 0, (sockaddr*)&dst, sizeof(dst)) == -1; i++) {
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||
esyslog("streamdev-server IGMP: unable to query group %s: %m", inet_ntoa(dst.sin_addr));
|
||||
break;
|
||||
}
|
||||
cCondWait::SleepMs(10);
|
||||
}
|
||||
}
|
||||
|
||||
// Querier state actions
|
||||
void cComponentIGMP::IGMPStartGeneralQueryTimer()
|
||||
{
|
||||
m_Querier = true;
|
||||
if (m_StartupQueryCount) {
|
||||
gettimeofday(&m_GeneralQueryTimer, NULL);
|
||||
m_GeneralQueryTimer.tv_sec += IGMP_STARTUP_QUERY_INTERVAL;
|
||||
m_StartupQueryCount--;
|
||||
}
|
||||
else {
|
||||
gettimeofday(&m_GeneralQueryTimer, NULL);
|
||||
m_GeneralQueryTimer.tv_sec += IGMP_QUERY_INTERVAL;
|
||||
}
|
||||
}
|
||||
|
||||
void cComponentIGMP::IGMPStartOtherQuerierPresentTimer()
|
||||
{
|
||||
m_Querier = false;
|
||||
m_StartupQueryCount = 0;
|
||||
gettimeofday(&m_GeneralQueryTimer, NULL);
|
||||
m_GeneralQueryTimer.tv_sec += IGMP_OTHER_QUERIER_PRESENT_INTERVAL;
|
||||
}
|
||||
|
||||
void cComponentIGMP::IGMPSendGeneralQuery()
|
||||
{
|
||||
IGMPSendQuery(IGMP_ALL_HOSTS, IGMP_QUERY_RESPONSE_INTERVAL);
|
||||
}
|
||||
|
||||
// Group state actions
|
||||
void cComponentIGMP::IGMPStartTimer(cMulticastGroup* Group, in_addr_t Member)
|
||||
{
|
||||
gettimeofday(&Group->timeout, NULL);
|
||||
Group->timeout.tv_sec += IGMP_GROUP_MEMBERSHIP_INTERVAL;
|
||||
TV_CLR(Group->retransmit);
|
||||
Group->reporter = Member;
|
||||
|
||||
}
|
||||
|
||||
void cComponentIGMP::IGMPStartV1HostTimer(cMulticastGroup* Group)
|
||||
{
|
||||
gettimeofday(&Group->v1timer, NULL);
|
||||
Group->v1timer.tv_sec += IGMP_GROUP_MEMBERSHIP_INTERVAL;
|
||||
}
|
||||
|
||||
void cComponentIGMP::IGMPStartTimerAfterLeave(cMulticastGroup* Group, unsigned int MaxResponseTimeTs)
|
||||
{
|
||||
//Group->Update(time(NULL) + MaxResponseTime * IGMP_LAST_MEMBER_QUERY_COUNT / 10);
|
||||
MaxResponseTimeTs *= IGMP_LAST_MEMBER_QUERY_COUNT;
|
||||
gettimeofday(&Group->timeout, NULL);
|
||||
TV_ADD(Group->timeout, MaxResponseTimeTs);
|
||||
TV_CLR(Group->retransmit);
|
||||
Group->reporter = 0;
|
||||
}
|
||||
|
||||
void cComponentIGMP::IGMPStartRetransmitTimer(cMulticastGroup* Group)
|
||||
{
|
||||
gettimeofday(&Group->retransmit, NULL);
|
||||
TV_ADD(Group->retransmit, IGMP_LAST_MEMBER_QUERY_INTERVAL_TS);
|
||||
}
|
||||
|
||||
void cComponentIGMP::IGMPClearRetransmitTimer(cMulticastGroup* Group)
|
||||
{
|
||||
TV_CLR(Group->retransmit);
|
||||
}
|
||||
|
||||
void cComponentIGMP::IGMPSendGroupQuery(cMulticastGroup* Group)
|
||||
{
|
||||
IGMPSendQuery(Group->group, IGMP_LAST_MEMBER_QUERY_INTERVAL_TS);
|
||||
}
|
||||
|
||||
cServerConnection* cComponentIGMP::IGMPStartMulticast(cMulticastGroup* Group)
|
||||
{
|
||||
cServerConnection *conn = NULL;
|
||||
in_addr_t g = ntohl(Group->group);
|
||||
if (g > MULTICAST_PRIV_MIN && g <= MULTICAST_PRIV_MAX) {
|
||||
cThreadLock lock;
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_CHANNELS_READ;
|
||||
const cChannel *channel = Channels->GetByNumber(g - MULTICAST_PRIV_MIN);
|
||||
#else
|
||||
cChannel *channel = Channels.GetByNumber(g - MULTICAST_PRIV_MIN);
|
||||
#endif
|
||||
const cList<cServerConnection>& clients = cStreamdevServer::Clients(lock);
|
||||
#if APIVERSNUM >= 20300
|
||||
const cServerConnection *s = clients.First();
|
||||
#else
|
||||
cServerConnection *s = clients.First();
|
||||
#endif
|
||||
while (s) {
|
||||
if (s->RemoteIpAddr() == Group->group)
|
||||
break;
|
||||
s = clients.Next(s);
|
||||
}
|
||||
if (!s) {
|
||||
conn = NewClient();
|
||||
if (!((cConnectionIGMP *)conn)->SetChannel(channel, Group->group)) {
|
||||
DELETENULL(conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
void cComponentIGMP::IGMPStopMulticast(cMulticastGroup* Group)
|
||||
{
|
||||
cThreadLock lock;
|
||||
#if APIVERSNUM >= 20300
|
||||
cList<cServerConnection>& clients = cStreamdevServer::Clients(lock);
|
||||
#else
|
||||
const cList<cServerConnection>& clients = cStreamdevServer::Clients(lock);
|
||||
#endif
|
||||
for (cServerConnection *s = clients.First(); s; s = clients.Next(s)) {
|
||||
if (s->RemoteIpAddr() == Group->group)
|
||||
s->Close();
|
||||
}
|
||||
}
|
||||
66
server/componentIGMP.h
Normal file
66
server/componentIGMP.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* $Id: componentIGMP.h,v 1.1 2009/02/13 10:39:22 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_IGMPSERVER_H
|
||||
#define VDR_STREAMDEV_IGMPSERVER_H
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <vdr/thread.h>
|
||||
#include "server/component.h"
|
||||
#include "../common.h"
|
||||
|
||||
class cMulticastGroup;
|
||||
|
||||
class cComponentIGMP: public cServerComponent, public cThread {
|
||||
private:
|
||||
char m_ReadBuffer[2048];
|
||||
cList<cMulticastGroup> m_Groups;
|
||||
in_addr_t m_BindIp;
|
||||
int m_MaxChannelNumber;
|
||||
struct timeval m_GeneralQueryTimer;
|
||||
int m_StartupQueryCount;
|
||||
bool m_Querier;
|
||||
cCondWait m_CondWait;
|
||||
|
||||
#if APIVERSNUM >= 20300
|
||||
cMulticastGroup* FindGroup(in_addr_t Group);
|
||||
#else
|
||||
cMulticastGroup* FindGroup(in_addr_t Group) const;
|
||||
#endif
|
||||
|
||||
/* Add or remove local host to multicast group */
|
||||
bool IGMPMembership(in_addr_t Group, bool Add = true);
|
||||
void IGMPSendQuery(in_addr_t Group, int Timeout);
|
||||
|
||||
cServerConnection* ProcessMessage(struct igmp *Igmp, in_addr_t Group, in_addr_t Sender);
|
||||
|
||||
void IGMPStartGeneralQueryTimer();
|
||||
void IGMPStartOtherQuerierPresentTimer();
|
||||
void IGMPSendGeneralQuery();
|
||||
|
||||
void IGMPStartTimer(cMulticastGroup* Group, in_addr_t Member);
|
||||
void IGMPStartV1HostTimer(cMulticastGroup* Group);
|
||||
void IGMPStartTimerAfterLeave(cMulticastGroup* Group, unsigned int MaxResponseTime);
|
||||
void IGMPStartRetransmitTimer(cMulticastGroup* Group);
|
||||
void IGMPClearRetransmitTimer(cMulticastGroup* Group);
|
||||
void IGMPSendGroupQuery(cMulticastGroup* Group);
|
||||
cServerConnection* IGMPStartMulticast(cMulticastGroup* Group);
|
||||
void IGMPStopMulticast(cMulticastGroup* Group);
|
||||
|
||||
virtual void Action();
|
||||
|
||||
protected:
|
||||
virtual cServerConnection *NewClient(void);
|
||||
|
||||
public:
|
||||
virtual bool Initialize(void);
|
||||
virtual void Destruct(void);
|
||||
virtual cServerConnection* Accept(void);
|
||||
|
||||
cComponentIGMP(void);
|
||||
~cComponentIGMP(void);
|
||||
};
|
||||
|
||||
#endif // VDR_STREAMDEV_IGMPSERVER_H
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: connection.c,v 1.10 2007/05/07 12:25:11 schmirl Exp $
|
||||
* $Id: connection.c,v 1.16 2010/08/03 10:51:53 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include "server/connection.h"
|
||||
@@ -8,22 +8,103 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <vdr/tools.h>
|
||||
#include <vdr/thread.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
|
||||
cServerConnection::cServerConnection(const char *Protocol):
|
||||
cServerConnection::cServerConnection(const char *Protocol, int Type):
|
||||
cTBSocket(Type),
|
||||
m_Protocol(Protocol),
|
||||
m_DeferClose(false),
|
||||
m_Pending(false),
|
||||
m_ReadBytes(0),
|
||||
m_WriteBytes(0),
|
||||
m_WriteIndex(0)
|
||||
m_WriteIndex(0),
|
||||
m_Streamer(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
cServerConnection::~cServerConnection()
|
||||
{
|
||||
delete(m_Streamer);
|
||||
}
|
||||
|
||||
const cChannel* cServerConnection::ChannelFromString(const char *String, int *Apid, int *Dpid) {
|
||||
const cChannel *channel = NULL;
|
||||
char *string = strdup(String);
|
||||
char *ptr, *end;
|
||||
int apididx = 0;
|
||||
|
||||
if ((ptr = strrchr(string, '+')) != NULL) {
|
||||
*(ptr++) = '\0';
|
||||
apididx = strtoul(ptr, &end, 10);
|
||||
Dprintf("found apididx: %d\n", apididx);
|
||||
}
|
||||
|
||||
if (isnumber(string)) {
|
||||
int temp = strtol(String, NULL, 10);
|
||||
if (temp == 0)
|
||||
temp = cDevice::CurrentChannel();
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_CHANNELS_READ;
|
||||
if (temp >= 1 && temp <= Channels->MaxNumber())
|
||||
channel = Channels->GetByNumber(temp);
|
||||
#else
|
||||
if (temp >= 1 && temp <= Channels.MaxNumber())
|
||||
channel = Channels.GetByNumber(temp);
|
||||
#endif
|
||||
} else {
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_CHANNELS_READ;
|
||||
channel = Channels->GetByChannelID(tChannelID::FromString(string));
|
||||
#else
|
||||
channel = Channels.GetByChannelID(tChannelID::FromString(string));
|
||||
#endif
|
||||
|
||||
if (channel == NULL) {
|
||||
int i = 1;
|
||||
#if APIVERSNUM >= 20300
|
||||
while ((channel = Channels->GetByNumber(i, 1)) != NULL) {
|
||||
#else
|
||||
while ((channel = Channels.GetByNumber(i, 1)) != NULL) {
|
||||
#endif
|
||||
if (String == channel->Name())
|
||||
break;
|
||||
|
||||
i = channel->Number() + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (channel != NULL && apididx > 0) {
|
||||
int apid = 0, dpid = 0;
|
||||
int index = 1;
|
||||
|
||||
for (int i = 0; channel->Apid(i) != 0; ++i, ++index) {
|
||||
if (index == apididx) {
|
||||
apid = channel->Apid(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (apid == 0) {
|
||||
for (int i = 0; channel->Dpid(i) != 0; ++i, ++index) {
|
||||
if (index == apididx) {
|
||||
dpid = channel->Dpid(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Apid != NULL)
|
||||
*Apid = apid;
|
||||
if (Dpid != NULL)
|
||||
*Dpid = dpid;
|
||||
}
|
||||
|
||||
free(string);
|
||||
return channel;
|
||||
}
|
||||
|
||||
bool cServerConnection::Read(void)
|
||||
@@ -124,82 +205,14 @@ bool cServerConnection::Respond(const char *Message, bool Last, ...)
|
||||
return true;
|
||||
}
|
||||
|
||||
cDevice *cServerConnection::GetDevice(const cChannel *Channel, int Priority)
|
||||
bool cServerConnection::Close()
|
||||
{
|
||||
cDevice *device = NULL;
|
||||
|
||||
/*Dprintf("+ Statistics:\n");
|
||||
Dprintf("+ Current Channel: %d\n", cDevice::CurrentChannel());
|
||||
Dprintf("+ Current Device: %d\n", cDevice::ActualDevice()->CardIndex());
|
||||
Dprintf("+ Transfer Mode: %s\n", cDevice::ActualDevice()
|
||||
== cDevice::PrimaryDevice() ? "false" : "true");
|
||||
Dprintf("+ Replaying: %s\n", cDevice::PrimaryDevice()->Replaying() ? "true"
|
||||
: "false");*/
|
||||
|
||||
Dprintf(" * GetDevice(const cChannel*, int)\n");
|
||||
Dprintf(" * -------------------------------\n");
|
||||
|
||||
#if VDRVERSNUM < 10500
|
||||
device = cDevice::GetDevice(Channel, Priority);
|
||||
#else
|
||||
device = cDevice::GetDevice(Channel, Priority, false);
|
||||
#endif
|
||||
|
||||
Dprintf(" * Found following device: %p (%d)\n", device,
|
||||
device ? device->CardIndex() + 1 : 0);
|
||||
if (device == cDevice::ActualDevice())
|
||||
Dprintf(" * is actual device\n");
|
||||
if (!cSuspendCtl::IsActive() && StreamdevServerSetup.SuspendMode != smAlways)
|
||||
Dprintf(" * NOT suspended\n");
|
||||
|
||||
if (!device || (device == cDevice::ActualDevice()
|
||||
&& !cSuspendCtl::IsActive()
|
||||
&& StreamdevServerSetup.SuspendMode != smAlways)) {
|
||||
// mustn't switch actual device
|
||||
// maybe a device would be free if THIS connection did turn off its streams?
|
||||
Dprintf(" * trying again...\n");
|
||||
const cChannel *current = Channels.GetByNumber(cDevice::CurrentChannel());
|
||||
isyslog("streamdev-server: Detaching current receiver");
|
||||
Detach();
|
||||
#if VDRVERSNUM < 10500
|
||||
device = cDevice::GetDevice(Channel, Priority);
|
||||
#else
|
||||
device = cDevice::GetDevice(Channel, Priority, false);
|
||||
#endif
|
||||
Attach();
|
||||
Dprintf(" * Found following device: %p (%d)\n", device,
|
||||
device ? device->CardIndex() + 1 : 0);
|
||||
if (device == cDevice::ActualDevice())
|
||||
Dprintf(" * is actual device\n");
|
||||
if (!cSuspendCtl::IsActive()
|
||||
&& StreamdevServerSetup.SuspendMode != smAlways)
|
||||
Dprintf(" * NOT suspended\n");
|
||||
if (current && !TRANSPONDER(Channel, current))
|
||||
Dprintf(" * NOT same transponder\n");
|
||||
if (device && (device == cDevice::ActualDevice()
|
||||
&& !cSuspendCtl::IsActive()
|
||||
&& StreamdevServerSetup.SuspendMode != smAlways
|
||||
&& current != NULL
|
||||
&& !TRANSPONDER(Channel, current))) {
|
||||
// now we would have to switch away live tv...let's see if live tv
|
||||
// can be handled by another device
|
||||
cDevice *newdev = NULL;
|
||||
for (int i = 0; i < cDevice::NumDevices(); ++i) {
|
||||
cDevice *dev = cDevice::GetDevice(i);
|
||||
if (dev->ProvidesChannel(current, 0) && dev != device) {
|
||||
newdev = dev;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Dprintf(" * Found device for live tv: %p (%d)\n", newdev,
|
||||
newdev ? newdev->CardIndex() + 1 : 0);
|
||||
if (newdev == NULL || newdev == device)
|
||||
// no suitable device to continue live TV, giving up...
|
||||
device = NULL;
|
||||
else
|
||||
newdev->SwitchChannel(current, true);
|
||||
}
|
||||
}
|
||||
|
||||
return device;
|
||||
if (IsOpen())
|
||||
isyslog("streamdev-server: closing %s connection to %s:%d", Protocol(), RemoteIp().c_str(), RemotePort());
|
||||
return cTBSocket::Close();
|
||||
}
|
||||
|
||||
cString cServerConnection::ToText(char Delimiter) const
|
||||
{
|
||||
return cString::sprintf("%s%c%s:%d", Protocol(), Delimiter, RemoteIp().c_str(), RemotePort());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: connection.h,v 1.5 2007/04/16 11:01:02 schmirl Exp $
|
||||
* $Id: connection.h,v 1.10 2010/08/03 10:46:41 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SERVER_CONNECTION_H
|
||||
@@ -7,9 +7,15 @@
|
||||
|
||||
#include "tools/socket.h"
|
||||
#include "common.h"
|
||||
#include "server/streamer.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
typedef std::map<std::string,std::string> tStrStrMap;
|
||||
typedef std::pair<std::string,std::string> tStrStr;
|
||||
|
||||
class cChannel;
|
||||
class cDevice;
|
||||
|
||||
/* Basic capabilities of a straight text-based protocol, most functions
|
||||
virtual to support more complicated protocols */
|
||||
@@ -28,6 +34,10 @@ private:
|
||||
uint m_WriteBytes;
|
||||
uint m_WriteIndex;
|
||||
|
||||
cStreamdevStreamer *m_Streamer;
|
||||
|
||||
tStrStrMap m_Headers;
|
||||
|
||||
protected:
|
||||
/* Will be called when a command terminated by a newline has been
|
||||
received */
|
||||
@@ -41,12 +51,26 @@ protected:
|
||||
virtual bool Respond(const char *Message, bool Last = true, ...);
|
||||
//__attribute__ ((format (printf, 2, 4)));
|
||||
|
||||
/* Add a request header */
|
||||
void SetHeader(const char *Name, const char *Value, const char *Prefix = "") { m_Headers.insert(tStrStr(std::string(Prefix) + Name, Value)); }
|
||||
|
||||
/* Set the streamer */
|
||||
void SetStreamer(cStreamdevStreamer* Streamer) { delete m_Streamer; m_Streamer = Streamer; }
|
||||
|
||||
/* Return the streamer */
|
||||
cStreamdevStreamer *Streamer() const { return m_Streamer; }
|
||||
|
||||
static const cChannel *ChannelFromString(const char *String, int *Apid = NULL, int *Dpid = NULL);
|
||||
|
||||
public:
|
||||
/* If you derive, specify a short string such as HTTP for Protocol, which
|
||||
will be displayed in error messages */
|
||||
cServerConnection(const char *Protocol);
|
||||
cServerConnection(const char *Protocol, int Type = SOCK_STREAM);
|
||||
virtual ~cServerConnection();
|
||||
|
||||
/* If true, any client IP will be accepted */
|
||||
virtual bool CanAuthenticate(void) { return false; }
|
||||
|
||||
/* Gets called if the client has been accepted by the core */
|
||||
virtual void Welcome(void) { }
|
||||
|
||||
@@ -76,14 +100,19 @@ public:
|
||||
/* Will make the socket close after sending all queued output data */
|
||||
void DeferClose(void) { m_DeferClose = true; }
|
||||
|
||||
/* Will retrieve an unused device for transmitting data. Use the returned
|
||||
cDevice in a following call to StartTransfer */
|
||||
cDevice *GetDevice(const cChannel *Channel, int Priority);
|
||||
/* Close the socket */
|
||||
virtual bool Close(void);
|
||||
|
||||
virtual void Flushed(void) {}
|
||||
|
||||
virtual void Detach(void) = 0;
|
||||
virtual void Attach(void) = 0;
|
||||
/* This connections protocol name */
|
||||
virtual const char* Protocol(void) const { return m_Protocol; }
|
||||
|
||||
/* Text description of stream */
|
||||
virtual cString ToText(char Delimiter = ' ') const;
|
||||
|
||||
/* std::map with additional information */
|
||||
const tStrStrMap& Headers(void) const { return m_Headers; }
|
||||
};
|
||||
|
||||
inline bool cServerConnection::HasData(void) const
|
||||
|
||||
@@ -1,29 +1,48 @@
|
||||
/*
|
||||
* $Id: connectionHTTP.c,v 1.13 2008/03/28 15:11:40 schmirl Exp $
|
||||
* $Id: connectionHTTP.c,v 1.21 2010/08/03 10:46:41 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <vdr/thread.h>
|
||||
#include <vdr/recording.h>
|
||||
|
||||
#include "server/connectionHTTP.h"
|
||||
#include "server/menuHTTP.h"
|
||||
#include "server/server.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_ChannelList(NULL)
|
||||
m_Channel(NULL),
|
||||
m_RecPlayer(NULL),
|
||||
m_ReplayPos(0),
|
||||
m_ReplayFakeRange(false),
|
||||
m_MenuList(NULL)
|
||||
{
|
||||
Dprintf("constructor hsRequest\n");
|
||||
m_Apid[0] = m_Apid[1] = 0;
|
||||
m_Dpid[0] = m_Dpid[1] = 0;
|
||||
}
|
||||
|
||||
cConnectionHTTP::~cConnectionHTTP()
|
||||
{
|
||||
delete m_LiveStreamer;
|
||||
SetStreamer(NULL);
|
||||
delete m_RecPlayer;
|
||||
delete m_MenuList;
|
||||
}
|
||||
|
||||
bool cConnectionHTTP::CanAuthenticate(void)
|
||||
{
|
||||
return opt_auth != NULL;
|
||||
}
|
||||
|
||||
bool cConnectionHTTP::Command(char *Cmd)
|
||||
@@ -31,23 +50,84 @@ bool cConnectionHTTP::Command(char *Cmd)
|
||||
Dprintf("command %s\n", Cmd);
|
||||
switch (m_Status) {
|
||||
case hsRequest:
|
||||
Dprintf("Request\n");
|
||||
m_Request = Cmd;
|
||||
m_Status = hsHeaders;
|
||||
return true;
|
||||
// parse METHOD PATH[?QUERY] VERSION
|
||||
{
|
||||
char *p, *q, *v;
|
||||
p = strchr(Cmd, ' ');
|
||||
if (p) {
|
||||
*p = 0;
|
||||
v = strchr(++p, ' ');
|
||||
if (v) {
|
||||
*v = 0;
|
||||
SetHeader("REQUEST_METHOD", Cmd);
|
||||
q = strchr(p, '?');
|
||||
if (q) {
|
||||
*q = 0;
|
||||
SetHeader("QUERY_STRING", q + 1);
|
||||
while (q++) {
|
||||
char *n = strchr(q, '&');
|
||||
if (n)
|
||||
*n = 0;
|
||||
char *e = strchr(q, '=');
|
||||
if (e)
|
||||
*e++ = 0;
|
||||
else
|
||||
e = n ? n : v;
|
||||
m_Params.insert(tStrStr(q, e));
|
||||
q = n;
|
||||
}
|
||||
}
|
||||
else
|
||||
SetHeader("QUERY_STRING", "");
|
||||
SetHeader("PATH_INFO", p);
|
||||
m_Status = hsHeaders;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
case hsHeaders:
|
||||
if (*Cmd == '\0') {
|
||||
m_Status = hsBody;
|
||||
return ProcessRequest();
|
||||
}
|
||||
if (strncasecmp(Cmd, "Host:", 5) == 0) {
|
||||
Dprintf("Host-Header\n");
|
||||
m_Host = (std::string) skipspace(Cmd + 5);
|
||||
else if (isspace(*Cmd)) {
|
||||
; //TODO: multi-line header
|
||||
}
|
||||
else {
|
||||
// convert header name to CGI conventions:
|
||||
// uppercase, '-' replaced with '_', prefix "HTTP_"
|
||||
char *p;
|
||||
for (p = Cmd; *p != 0 && *p != ':'; p++) {
|
||||
if (*p == '-')
|
||||
*p = '_';
|
||||
else
|
||||
*p = toupper(*p);
|
||||
}
|
||||
if (*p == ':') {
|
||||
*p = 0;
|
||||
p = skipspace(++p);
|
||||
// don't disclose Authorization header
|
||||
if (strcmp(Cmd, "AUTHORIZATION") == 0) {
|
||||
char *q;
|
||||
for (q = p; *q != 0 && *q != ' '; q++)
|
||||
*q = toupper(*q);
|
||||
if (p != q) {
|
||||
*q = 0;
|
||||
SetHeader("AUTH_TYPE", p);
|
||||
m_Authorization = (std::string) skipspace(++q);
|
||||
}
|
||||
}
|
||||
else
|
||||
SetHeader(Cmd, p, "HTTP_");
|
||||
}
|
||||
}
|
||||
Dprintf("header\n");
|
||||
return true;
|
||||
default:
|
||||
// skip additional blank lines
|
||||
if (*Cmd == '\0')
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
return false; // ??? shouldn't happen
|
||||
@@ -55,214 +135,532 @@ bool cConnectionHTTP::Command(char *Cmd)
|
||||
|
||||
bool cConnectionHTTP::ProcessRequest(void)
|
||||
{
|
||||
// keys for Headers() hash
|
||||
const static std::string AUTH_TYPE("AUTH_TYPE");
|
||||
const static std::string REQUEST_METHOD("REQUEST_METHOD");
|
||||
const static std::string PATH_INFO("PATH_INFO");
|
||||
|
||||
Dprintf("process\n");
|
||||
if (m_Request.substr(0, 4) == "GET " && CmdGET(m_Request.substr(4))) {
|
||||
switch (m_Job) {
|
||||
case hjListing:
|
||||
if (m_ChannelList)
|
||||
return Respond("%s", true, m_ChannelList->HttpHeader().c_str());
|
||||
break;
|
||||
if (!StreamdevHosts.Acceptable(RemoteIpAddr())) {
|
||||
bool authOk = opt_auth && !m_Authorization.empty();
|
||||
if (authOk) {
|
||||
tStrStrMap::const_iterator it = Headers().find(AUTH_TYPE);
|
||||
|
||||
case hjTransfer:
|
||||
if (m_Channel == NULL) {
|
||||
DeferClose();
|
||||
return Respond("HTTP/1.0 404 not found");
|
||||
if (it == Headers().end()) {
|
||||
// no authorization header present
|
||||
authOk = false;
|
||||
}
|
||||
|
||||
m_LiveStreamer = new cStreamdevLiveStreamer(0, m_StreamerParameter);
|
||||
cDevice *device = GetDevice(m_Channel, 0);
|
||||
if (device != NULL) {
|
||||
device->SwitchChannel(m_Channel, false);
|
||||
if (m_LiveStreamer->SetChannel(m_Channel, m_StreamType, m_Apid)) {
|
||||
m_LiveStreamer->SetDevice(device);
|
||||
if (!SetDSCP())
|
||||
LOG_ERROR_STR("unable to set DSCP sockopt");
|
||||
if (m_StreamType == stES && (m_Apid != 0 || ISRADIO(m_Channel))) {
|
||||
return Respond("HTTP/1.0 200 OK")
|
||||
&& Respond("Content-Type: audio/mpeg")
|
||||
&& Respond("icy-name: %s", true, m_Channel->Name())
|
||||
&& Respond("");
|
||||
} else {
|
||||
return Respond("HTTP/1.0 200 OK")
|
||||
&& Respond("Content-Type: video/mpeg")
|
||||
&& Respond("");
|
||||
}
|
||||
}
|
||||
else if (it->second.compare("BASIC") == 0) {
|
||||
// basic auth
|
||||
authOk &= m_Authorization.compare(opt_auth) == 0;
|
||||
}
|
||||
DELETENULL(m_LiveStreamer);
|
||||
DeferClose();
|
||||
return Respond("HTTP/1.0 409 Channel not available")
|
||||
&& Respond("");
|
||||
else {
|
||||
// unsupported auth type
|
||||
authOk = false;
|
||||
}
|
||||
}
|
||||
if (!authOk) {
|
||||
isyslog("streamdev-server: HTTP authorization required");
|
||||
return HttpResponse(401, true, NULL, "WWW-authenticate: basic Realm=\"Streamdev-Server\"");
|
||||
}
|
||||
}
|
||||
|
||||
DeferClose();
|
||||
return Respond("HTTP/1.0 400 Bad Request")
|
||||
&& Respond("");
|
||||
tStrStrMap::const_iterator it;
|
||||
it = m_Params.find("apid");
|
||||
if (it != m_Params.end())
|
||||
m_Apid[0] = atoi(it->second.c_str());
|
||||
it = m_Params.find("dpid");
|
||||
if (it != m_Params.end())
|
||||
m_Dpid[0] = atoi(it->second.c_str());
|
||||
|
||||
tStrStrMap::const_iterator it_method = Headers().find(REQUEST_METHOD);
|
||||
tStrStrMap::const_iterator it_pathinfo = Headers().find(PATH_INFO);
|
||||
if (it_method == Headers().end() || it_pathinfo == Headers().end()) {
|
||||
// should never happen
|
||||
esyslog("streamdev-server connectionHTTP: Missing method or pathinfo");
|
||||
} else if (it_method->second.compare("GET") == 0 && ProcessURI(it_pathinfo->second)) {
|
||||
if (m_MenuList)
|
||||
return Respond("%s", true, m_MenuList->HttpHeader().c_str());
|
||||
else if (m_Channel != NULL) {
|
||||
if (cStreamdevLiveStreamer::ProvidesChannel(m_Channel, StreamdevServerSetup.HTTPPriority)) {
|
||||
cStreamdevLiveStreamer* liveStreamer = new cStreamdevLiveStreamer(this, m_Channel, StreamdevServerSetup.HTTPPriority, m_StreamType, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL);
|
||||
if (liveStreamer->GetDevice()) {
|
||||
SetStreamer(liveStreamer);
|
||||
if (!SetDSCP())
|
||||
LOG_ERROR_STR("unable to set DSCP sockopt");
|
||||
if (m_StreamType == stEXT) {
|
||||
return Respond("HTTP/1.0 200 OK");
|
||||
} else if (m_StreamType == stES && (m_Apid[0] || m_Dpid[0] || ISRADIO(m_Channel))) {
|
||||
return HttpResponse(200, false, "audio/mpeg", "icy-name: %s", m_Channel->Name());
|
||||
} else if (ISRADIO(m_Channel)) {
|
||||
return HttpResponse(200, false, "audio/mpeg");
|
||||
} else {
|
||||
return HttpResponse(200, false, "video/mpeg");
|
||||
}
|
||||
}
|
||||
SetStreamer(NULL);
|
||||
delete liveStreamer;
|
||||
}
|
||||
return HttpResponse(503, true);
|
||||
}
|
||||
else if (m_RecPlayer != NULL) {
|
||||
Dprintf("GET recording\n");
|
||||
bool isPes = m_RecPlayer->getCurrentRecording()->IsPesRecording();
|
||||
// no remuxing for old PES recordings
|
||||
if (isPes && m_StreamType != stPES)
|
||||
return HttpResponse(503, true);
|
||||
|
||||
int64_t from, to;
|
||||
bool hasRange = ParseRange(from, to);
|
||||
|
||||
cStreamdevRecStreamer* recStreamer;
|
||||
if (from == 0 && hasRange && m_ReplayFakeRange) {
|
||||
recStreamer = new cStreamdevRecStreamer(this, m_RecPlayer, m_StreamType, (int64_t) 0L, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL);
|
||||
from += m_ReplayPos;
|
||||
if (to >= 0)
|
||||
to += m_ReplayPos;
|
||||
}
|
||||
else
|
||||
recStreamer = new cStreamdevRecStreamer(this, m_RecPlayer, m_StreamType, m_ReplayPos, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL);
|
||||
SetStreamer(recStreamer);
|
||||
|
||||
if (m_StreamType == stEXT)
|
||||
return Respond("HTTP/1.0 200 OK");
|
||||
else if (m_StreamType == stES && (m_Apid[0] || m_Dpid[0]))
|
||||
return HttpResponse(200, false, "audio/mpeg");
|
||||
|
||||
const char* contentType = (isPes || m_RecPlayer->getPatPmtData()->Vpid()) ? "video/mpeg" : "audio/mpeg";
|
||||
// range not supported when remuxing
|
||||
if (m_StreamType != stTS && !isPes)
|
||||
return HttpResponse(200, false, contentType);
|
||||
|
||||
uint64_t total = recStreamer->GetLength();
|
||||
if (hasRange) {
|
||||
int64_t length = recStreamer->SetRange(from, to);
|
||||
Dprintf("range response: %lld-%lld/%lld, len %lld\n", (long long)from, (long long)to, (long long)total, (long long)length);
|
||||
if (length < 0L)
|
||||
return HttpResponse(416, true, contentType, "Accept-Ranges: bytes\r\nContent-Range: bytes */%llu", (unsigned long long) total);
|
||||
else
|
||||
return HttpResponse(206, false, contentType, "Accept-Ranges: bytes\r\nContent-Range: bytes %lld-%lld/%llu\r\nContent-Length: %lld", (long long) from, (long long) to, (unsigned long long) total, (long long) length);
|
||||
}
|
||||
else
|
||||
return HttpResponse(200, false, contentType, "Accept-Ranges: bytes");
|
||||
}
|
||||
else {
|
||||
return HttpResponse(404, true);
|
||||
}
|
||||
} else if (it_method->second.compare("HEAD") == 0 && ProcessURI(it_pathinfo->second)) {
|
||||
if (m_MenuList) {
|
||||
DeferClose();
|
||||
return Respond("%s", true, m_MenuList->HttpHeader().c_str());
|
||||
}
|
||||
else if (m_Channel != NULL) {
|
||||
if (cStreamdevLiveStreamer::ProvidesChannel(m_Channel, StreamdevServerSetup.HTTPPriority)) {
|
||||
if (m_StreamType == stEXT) {
|
||||
cStreamdevLiveStreamer *liveStreamer = new cStreamdevLiveStreamer(this, m_Channel, IDLEPRIORITY, m_StreamType, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL);
|
||||
SetStreamer(liveStreamer);
|
||||
return Respond("HTTP/1.0 200 OK");
|
||||
} else if (m_StreamType == stES && (m_Apid[0] || m_Dpid[0] || ISRADIO(m_Channel))) {
|
||||
return HttpResponse(200, true, "audio/mpeg", "icy-name: %s", m_Channel->Name());
|
||||
} else if (ISRADIO(m_Channel)) {
|
||||
return HttpResponse(200, true, "audio/mpeg");
|
||||
} else {
|
||||
return HttpResponse(200, true, "video/mpeg");
|
||||
}
|
||||
}
|
||||
return HttpResponse(503, true);
|
||||
}
|
||||
else if (m_RecPlayer != NULL) {
|
||||
Dprintf("HEAD recording\n");
|
||||
bool isPes = m_RecPlayer->getCurrentRecording()->IsPesRecording();
|
||||
// no remuxing for old PES recordings
|
||||
if (isPes && m_StreamType != stPES)
|
||||
return HttpResponse(503, true);
|
||||
|
||||
int64_t from, to;
|
||||
bool hasRange = ParseRange(from, to);
|
||||
|
||||
cStreamdevRecStreamer* recStreamer;
|
||||
if (from == 0 && hasRange && m_ReplayFakeRange) {
|
||||
recStreamer = new cStreamdevRecStreamer(this, m_RecPlayer, m_StreamType, m_ReplayPos, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL);
|
||||
from += m_ReplayPos;
|
||||
if (to >= 0)
|
||||
to += m_ReplayPos;
|
||||
}
|
||||
else
|
||||
recStreamer = new cStreamdevRecStreamer(this, m_RecPlayer, m_StreamType, m_ReplayPos, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL);
|
||||
SetStreamer(recStreamer);
|
||||
|
||||
if (m_StreamType == stEXT)
|
||||
return Respond("HTTP/1.0 200 OK");
|
||||
else if (m_StreamType == stES && (m_Apid[0] || m_Dpid[0]))
|
||||
return HttpResponse(200, true, "audio/mpeg");
|
||||
|
||||
const char* contentType = (isPes || m_RecPlayer->getPatPmtData()->Vpid()) ? "video/mpeg" : "audio/mpeg";
|
||||
// range not supported when remuxing
|
||||
if (m_StreamType != stTS && !isPes)
|
||||
return HttpResponse(200, false, contentType);
|
||||
|
||||
uint64_t total = recStreamer->GetLength();
|
||||
if (hasRange) {
|
||||
int64_t length = recStreamer->SetRange(from, to);
|
||||
if (length < 0L)
|
||||
return HttpResponse(416, true, contentType, "Accept-Ranges: bytes\r\nContent-Range: bytes */%llu", (unsigned long long) total);
|
||||
else
|
||||
return HttpResponse(206, true, contentType, "Accept-Ranges: bytes\r\nContent-Range: bytes %lld-%lld/%llu\r\nContent-Length: %lld", (long long) from, (long long) to, (unsigned long long) total, (long long) length);
|
||||
}
|
||||
else
|
||||
return HttpResponse(200, true, contentType, "Accept-Ranges: bytes");
|
||||
}
|
||||
else {
|
||||
return HttpResponse(404, true);
|
||||
}
|
||||
}
|
||||
|
||||
return HttpResponse(400, true);
|
||||
}
|
||||
|
||||
static const char *AAA[] = {
|
||||
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
|
||||
};
|
||||
static const char *MMM[] = {
|
||||
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
||||
};
|
||||
|
||||
bool cConnectionHTTP::HttpResponse(int Code, bool Last, const char* ContentType, const char* Headers, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, Headers);
|
||||
#if APIVERSNUM >= 10728
|
||||
cString headers = cString::vsprintf(Headers, ap);
|
||||
#else
|
||||
cString headers = cString::sprintf(Headers, ap);
|
||||
#endif
|
||||
va_end(ap);
|
||||
|
||||
bool rc;
|
||||
if (Last)
|
||||
DeferClose();
|
||||
switch (Code)
|
||||
{
|
||||
case 200: rc = Respond("HTTP/1.1 200 OK"); break;
|
||||
case 206: rc = Respond("HTTP/1.1 206 Partial Content"); break;
|
||||
case 400: rc = Respond("HTTP/1.1 400 Bad Request"); break;
|
||||
case 401: rc = Respond("HTTP/1.1 401 Authorization Required"); break;
|
||||
case 404: rc = Respond("HTTP/1.1 404 Not Found"); break;
|
||||
case 416: rc = Respond("HTTP/1.1 416 Requested range not satisfiable"); break;
|
||||
case 503: rc = Respond("HTTP/1.1 503 Service Unavailable"); break;
|
||||
default: rc = Respond("HTTP/1.1 500 Internal Server Error");
|
||||
}
|
||||
if (rc && ContentType)
|
||||
rc = Respond("Content-Type: %s", true, ContentType);
|
||||
|
||||
if (rc)
|
||||
rc = Respond("Connection: close")
|
||||
&& Respond("Pragma: no-cache")
|
||||
&& Respond("Cache-Control: no-cache")
|
||||
&& Respond("Server: VDR-%s / streamdev-server-%s", true, VDRVERSION, VERSION);
|
||||
|
||||
time_t t = time(NULL);
|
||||
struct tm *gmt = gmtime(&t);
|
||||
if (rc && gmt) {
|
||||
char buf[] = "Date: AAA, DD MMM YYYY HH:MM:SS GMT";
|
||||
if (snprintf(buf, sizeof(buf), "Date: %s, %.2d %s %.4d %.2d:%.2d:%.2d GMT", AAA[gmt->tm_wday], gmt->tm_mday, MMM[gmt->tm_mon], gmt->tm_year + 1900, gmt->tm_hour, gmt->tm_min, gmt->tm_sec) == sizeof(buf) - 1)
|
||||
rc = Respond(buf);
|
||||
}
|
||||
|
||||
if (rc && strlen(Headers) > 0)
|
||||
rc = Respond(headers);
|
||||
|
||||
tStrStrMap::iterator it = m_Params.begin();
|
||||
while (rc && it != m_Params.end()) {
|
||||
static const char DLNA_POSTFIX[] = ".dlna.org";
|
||||
if (it->first.rfind(DLNA_POSTFIX) + sizeof(DLNA_POSTFIX) - 1 == it->first.length())
|
||||
rc = Respond("%s: %s", true, it->first.c_str(), it->second.c_str());
|
||||
++it;
|
||||
}
|
||||
return rc && Respond("");
|
||||
}
|
||||
|
||||
bool cConnectionHTTP::ParseRange(int64_t &From, int64_t &To) const
|
||||
{
|
||||
const static std::string RANGE("HTTP_RANGE");
|
||||
From = To = 0L;
|
||||
tStrStrMap::const_iterator it = Headers().find(RANGE);
|
||||
if (it != Headers().end()) {
|
||||
size_t b = it->second.find("bytes=");
|
||||
if (b != std::string::npos) {
|
||||
char* e = NULL;
|
||||
const char* r = it->second.c_str() + b + sizeof("bytes=") - 1;
|
||||
if (strchr(r, ',') != NULL)
|
||||
esyslog("streamdev-server cConnectionHTTP::GetRange: Multi-ranges not supported");
|
||||
From = strtol(r, &e, 10);
|
||||
if (r != e) {
|
||||
if (From < 0L) {
|
||||
To = -1L;
|
||||
return *e == 0 || *e == ',';
|
||||
}
|
||||
else if (*e == '-') {
|
||||
r = e + 1;
|
||||
if (*r == 0 || *e == ',') {
|
||||
To = -1L;
|
||||
return true;
|
||||
}
|
||||
To = strtol(r, &e, 10);
|
||||
return r != e && To >= From &&
|
||||
(*e == 0 || *e == ',');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cConnectionHTTP::Flushed(void)
|
||||
{
|
||||
std::string line;
|
||||
|
||||
if (m_Status != hsBody)
|
||||
return;
|
||||
|
||||
switch (m_Job) {
|
||||
case hjListing:
|
||||
if (m_ChannelList) {
|
||||
if (m_ChannelList->HasNext()) {
|
||||
if (!Respond("%s", true, m_ChannelList->Next().c_str()))
|
||||
DeferClose();
|
||||
}
|
||||
else {
|
||||
DELETENULL(m_ChannelList);
|
||||
m_Status = hsFinished;
|
||||
if (m_MenuList) {
|
||||
if (m_MenuList->HasNext()) {
|
||||
if (!Respond("%s", true, m_MenuList->Next().c_str()))
|
||||
DeferClose();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// should never be reached
|
||||
esyslog("streamdev-server cConnectionHTTP::Flushed(): no channel list");
|
||||
m_Status = hsFinished;
|
||||
break;
|
||||
|
||||
case hjTransfer:
|
||||
else {
|
||||
DELETENULL(m_MenuList);
|
||||
m_Status = hsFinished;
|
||||
DeferClose();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (Streamer()) {
|
||||
Dprintf("streamer start\n");
|
||||
m_LiveStreamer->Start(this);
|
||||
Streamer()->Start(this);
|
||||
m_Status = hsFinished;
|
||||
}
|
||||
else {
|
||||
// should never be reached
|
||||
esyslog("streamdev-server cConnectionHTTP::Flushed(): no job to do");
|
||||
m_Status = hsFinished;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool cConnectionHTTP::CmdGET(const std::string &Opts)
|
||||
cMenuList* cConnectionHTTP::MenuListFromString(const std::string& Path, const std::string& Filebase, const std::string& Fileext) const
|
||||
{
|
||||
const char *ptr, *sp, *pp, *fp, *xp, *qp, *ep;
|
||||
const cChannel *chan;
|
||||
int apid = 0;
|
||||
std::string groupTarget;
|
||||
cItemIterator *iterator = NULL;
|
||||
|
||||
ptr = Opts.c_str();
|
||||
const static std::string GROUP("group");
|
||||
if (Filebase.compare("tree") == 0) {
|
||||
tStrStrMap::const_iterator it = m_Params.find(GROUP);
|
||||
iterator = new cListTree(it == m_Params.end() ? NULL : it->second.c_str());
|
||||
groupTarget = Filebase + Fileext;
|
||||
} else if (Filebase.compare("groups") == 0) {
|
||||
iterator = new cListGroups();
|
||||
groupTarget = (std::string) "group" + Fileext;
|
||||
} else if (Filebase.compare("group") == 0) {
|
||||
tStrStrMap::const_iterator it = m_Params.find(GROUP);
|
||||
iterator = new cListGroup(it == m_Params.end() ? NULL : it->second.c_str());
|
||||
} else if (Filebase.compare("channels") == 0) {
|
||||
iterator = new cListChannels();
|
||||
} else if (Filebase.compare("all") == 0 ||
|
||||
(Filebase.empty() && Fileext.empty())) {
|
||||
iterator = new cListAll();
|
||||
} else if (Filebase.compare("recordings") == 0) {
|
||||
iterator = new cRecordingsIterator(m_StreamType);
|
||||
}
|
||||
|
||||
// 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;
|
||||
if (iterator) {
|
||||
// assemble base url: http://host/path/
|
||||
std::string base;
|
||||
const static std::string HOST("HTTP_HOST");
|
||||
tStrStrMap::const_iterator it = Headers().find(HOST);
|
||||
if (it != Headers().end())
|
||||
base = "http://" + it->second + "/";
|
||||
else
|
||||
base = (std::string) "http://" + LocalIp() + ":" +
|
||||
(const char*) itoa(StreamdevServerSetup.HTTPServerPort) + "/";
|
||||
base += Path;
|
||||
|
||||
if (Filebase.empty() || Fileext.compare(".htm") == 0 || Fileext.compare(".html") == 0) {
|
||||
std::string self = Filebase + Fileext;
|
||||
std::string rss = Filebase + ".rss";
|
||||
tStrStrMap::const_iterator it = Headers().find("QUERY_STRING");
|
||||
if (it != Headers().end() && !it->second.empty()) {
|
||||
self += '?' + it->second;
|
||||
rss += '?' + it->second;
|
||||
}
|
||||
return new cHtmlMenuList(iterator, m_StreamType, self.c_str(), rss.c_str(), groupTarget.c_str());
|
||||
} else if (Fileext.compare(".m3u") == 0) {
|
||||
return new cM3uMenuList(iterator, base.c_str());
|
||||
} else if (Fileext.compare(".rss") == 0) {
|
||||
std::string html = Filebase + ".html";
|
||||
tStrStrMap::const_iterator it = Headers().find("QUERY_STRING");
|
||||
if (it != Headers().end() && !it->second.empty()) {
|
||||
html += '?' + it->second;
|
||||
}
|
||||
return new cRssMenuList(iterator, base.c_str(), html.c_str());
|
||||
} else {
|
||||
delete iterator;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
RecPlayer* cConnectionHTTP::RecPlayerFromString(const char *FileBase, const char *FileExt)
|
||||
{
|
||||
RecPlayer *recPlayer = NULL;
|
||||
|
||||
if (strcasecmp(FileExt, ".rec") != 0)
|
||||
return NULL;
|
||||
|
||||
char *p = NULL;
|
||||
unsigned long l = strtoul(FileBase, &p, 0);
|
||||
if (p != FileBase && l > 0L) {
|
||||
if (*p == ':') {
|
||||
// get recording by dev:inode
|
||||
ino_t inode = (ino_t) strtoull(p + 1, &p, 0);
|
||||
if (*p == 0 && inode > 0) {
|
||||
struct stat st;
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_RECORDINGS_READ;
|
||||
for (const cRecording *rec = Recordings->First(); rec; rec = Recordings->Next(rec)) {
|
||||
#else
|
||||
cThreadLock RecordingsLock(&Recordings);
|
||||
for (cRecording *rec = Recordings.First(); rec; rec = Recordings.Next(rec)) {
|
||||
#endif
|
||||
if (stat(rec->FileName(), &st) == 0 && st.st_dev == (dev_t) l && st.st_ino == inode)
|
||||
recPlayer = new RecPlayer(rec->FileName());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (*p == 0) {
|
||||
// get recording by index
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_RECORDINGS_READ;
|
||||
const cRecording *rec = Recordings->Get((int) l - 1);
|
||||
#else
|
||||
cThreadLock RecordingsLock(&Recordings);
|
||||
cRecording *rec = Recordings.Get((int) l - 1);
|
||||
#endif
|
||||
if (rec)
|
||||
recPlayer = new RecPlayer(rec->FileName());
|
||||
}
|
||||
|
||||
if (recPlayer) {
|
||||
const char *pos = NULL;
|
||||
tStrStrMap::const_iterator it = m_Params.begin();
|
||||
while (it != m_Params.end()) {
|
||||
if (it->first == "pos") {
|
||||
pos = it->second.c_str();
|
||||
break;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
if (pos) {
|
||||
// With prefix "full_" we try to fool players
|
||||
// by replying with a content range starting
|
||||
// at the requested position instead of 0.
|
||||
// This is a heavy violation of standards.
|
||||
// Use at your own risk!
|
||||
if (strncasecmp(pos, "full_", 5) == 0) {
|
||||
m_ReplayFakeRange = true;
|
||||
pos += 5;
|
||||
}
|
||||
if (strncasecmp(pos, "resume", 6) == 0) {
|
||||
int id = pos[6] == '.' ? atoi(pos + 7) : 0;
|
||||
m_ReplayPos = recPlayer->positionFromResume(id);
|
||||
}
|
||||
else if (strncasecmp(pos, "mark.", 5) == 0) {
|
||||
int index = atoi(pos + 5);
|
||||
m_ReplayPos = recPlayer->positionFromMark(index);
|
||||
}
|
||||
else if (strncasecmp(pos, "time.", 5) == 0) {
|
||||
int seconds = atoi(pos + 5);
|
||||
m_ReplayPos = recPlayer->positionFromTime(seconds);
|
||||
}
|
||||
else if (strncasecmp(pos, "frame.", 6) == 0) {
|
||||
int frame = atoi(pos + 6);
|
||||
m_ReplayPos = recPlayer->positionFromFrameNumber(frame);
|
||||
}
|
||||
else {
|
||||
m_ReplayPos = atol(pos);
|
||||
if (m_ReplayPos > 0L && m_ReplayPos < 100L)
|
||||
m_ReplayPos = recPlayer->positionFromPercent((int) m_ReplayPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return recPlayer;
|
||||
}
|
||||
|
||||
bool cConnectionHTTP::ProcessURI(const std::string& PathInfo)
|
||||
{
|
||||
std::string filespec, fileext;
|
||||
size_t file_pos = PathInfo.rfind('/');
|
||||
|
||||
if (file_pos != std::string::npos) {
|
||||
size_t ext_pos = PathInfo.rfind('.');
|
||||
if (ext_pos != std::string::npos) {
|
||||
// file extension including leading .
|
||||
fileext = PathInfo.substr(ext_pos);
|
||||
const char *ext = fileext.c_str();
|
||||
// ignore dummy file extensions
|
||||
if (strcasecmp(ext, ".ts") == 0 ||
|
||||
strcasecmp(ext, ".vdr") == 0 ||
|
||||
strcasecmp(ext, ".vob") == 0) {
|
||||
size_t ext_end = ext_pos;
|
||||
if (ext_pos > 0)
|
||||
ext_pos = PathInfo.rfind('.', ext_pos - 1);
|
||||
if (ext_pos == std::string::npos)
|
||||
ext_pos = ext_end;
|
||||
fileext = PathInfo.substr(ext_pos, ext_end - ext_pos);
|
||||
}
|
||||
}
|
||||
// file basename with leading / stripped off
|
||||
filespec = PathInfo.substr(file_pos + 1, ext_pos - file_pos - 1);
|
||||
}
|
||||
if (fileext.length() > 5) {
|
||||
//probably not an extension
|
||||
filespec += fileext;
|
||||
fileext.clear();
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
std::string type = PathInfo.substr(1, PathInfo.find_first_of("/;", 1) - 1);
|
||||
const char* pType = type.c_str();
|
||||
if (strcasecmp(pType, "PS") == 0) {
|
||||
m_StreamType = stPS;
|
||||
} else if (strcasecmp(pType, "PES") == 0) {
|
||||
if (strcasecmp(pType, "PES") == 0) {
|
||||
m_StreamType = stPES;
|
||||
#ifdef STREAMDEV_PS
|
||||
} else if (strcasecmp(pType, "PS") == 0) {
|
||||
m_StreamType = stPS;
|
||||
#endif
|
||||
} else if (strcasecmp(pType, "TS") == 0) {
|
||||
m_StreamType = stTS;
|
||||
} else if (strcasecmp(pType, "ES") == 0) {
|
||||
m_StreamType = stES;
|
||||
} else if (strcasecmp(pType, "Extern") == 0) {
|
||||
m_StreamType = stExtern;
|
||||
} else if (strcasecmp(pType, "EXT") == 0) {
|
||||
m_StreamType = stEXT;
|
||||
}
|
||||
|
||||
std::string groupTarget;
|
||||
cChannelIterator *iterator = NULL;
|
||||
Dprintf("before channelfromstring: type(%s) filespec(%s) fileext(%s)\n", type.c_str(), filespec.c_str(), fileext.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();
|
||||
}
|
||||
|
||||
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;
|
||||
if ((m_MenuList = MenuListFromString(PathInfo.substr(1, file_pos), filespec.c_str(), fileext.c_str())) != NULL) {
|
||||
Dprintf("Channel list requested\n");
|
||||
return true;
|
||||
} else if ((m_RecPlayer = RecPlayerFromString(filespec.c_str(), fileext.c_str())) != NULL) {
|
||||
Dprintf("Recording %s found\n", m_RecPlayer->getCurrentRecording()->Name());
|
||||
return true;
|
||||
} else if ((m_Channel = ChannelFromString(filespec.c_str(), &m_Apid[0], &m_Dpid[0])) != NULL) {
|
||||
Dprintf("Channel found. Apid/Dpid is %d/%d\n", m_Apid[0], m_Dpid[0]);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
|
||||
Dprintf("after channelfromstring\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
cString cConnectionHTTP::ToText(char Delimiter) const
|
||||
{
|
||||
cString str = cServerConnection::ToText(Delimiter);
|
||||
return Streamer() ? cString::sprintf("%s%c%s", *str, Delimiter, *Streamer()->ToText()) : str;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: connectionHTTP.h,v 1.5 2008/03/28 15:11:40 schmirl Exp $
|
||||
* $Id: connectionHTTP.h,v 1.7 2010/07/19 13:49:31 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SERVERS_CONNECTIONHTTP_H
|
||||
@@ -7,12 +7,13 @@
|
||||
|
||||
#include "connection.h"
|
||||
#include "server/livestreamer.h"
|
||||
#include "server/recstreamer.h"
|
||||
|
||||
#include <map>
|
||||
#include <tools/select.h>
|
||||
|
||||
class cChannel;
|
||||
class cStreamdevLiveStreamer;
|
||||
class cChannelList;
|
||||
class cMenuList;
|
||||
|
||||
class cConnectionHTTP: public cServerConnection {
|
||||
private:
|
||||
@@ -23,25 +24,34 @@ private:
|
||||
hsFinished,
|
||||
};
|
||||
|
||||
enum eHTTPJob {
|
||||
hjTransfer,
|
||||
hjListing,
|
||||
};
|
||||
|
||||
std::string m_Request;
|
||||
std::string m_Host;
|
||||
//std::map<std::string,std::string> m_Headers; TODO: later?
|
||||
std::string m_Authorization;
|
||||
eHTTPStatus m_Status;
|
||||
eHTTPJob m_Job;
|
||||
// job: transfer
|
||||
cStreamdevLiveStreamer *m_LiveStreamer;
|
||||
std::string m_StreamerParameter;
|
||||
const cChannel *m_Channel;
|
||||
int m_Apid;
|
||||
tStrStrMap m_Params;
|
||||
eStreamType m_StreamType;
|
||||
// job: transfer
|
||||
const cChannel *m_Channel;
|
||||
int m_Apid[2];
|
||||
int m_Dpid[2];
|
||||
// job: replay
|
||||
RecPlayer *m_RecPlayer;
|
||||
int64_t m_ReplayPos;
|
||||
bool m_ReplayFakeRange;
|
||||
// job: listing
|
||||
cChannelList *m_ChannelList;
|
||||
cMenuList *m_MenuList;
|
||||
|
||||
cMenuList* MenuListFromString(const std::string &PathInfo, const std::string &Filebase, const std::string &Fileext) const;
|
||||
RecPlayer* RecPlayerFromString(const char* FileBase, const char* FileExt);
|
||||
|
||||
bool ProcessURI(const std::string &PathInfo);
|
||||
bool HttpResponse(int Code, bool Last, const char* ContentType = NULL, const char* Headers = "", ...);
|
||||
//__attribute__ ((format (printf, 5, 6)));
|
||||
/**
|
||||
* Extract byte range from HTTP Range header. Returns false if no valid
|
||||
* range is found. The contents of From and To are undefined in this
|
||||
* case. From may be negative in which case To is undefined.
|
||||
* TODO: support for multiple ranges.
|
||||
*/
|
||||
bool ParseRange(int64_t &From, int64_t &To) const;
|
||||
protected:
|
||||
bool ProcessRequest(void);
|
||||
|
||||
@@ -49,11 +59,11 @@ public:
|
||||
cConnectionHTTP(void);
|
||||
virtual ~cConnectionHTTP();
|
||||
|
||||
virtual void Attach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Attach(); }
|
||||
virtual void Detach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Detach(); }
|
||||
virtual cString ToText(char Delimiter = ' ') const;
|
||||
|
||||
virtual bool CanAuthenticate(void);
|
||||
|
||||
virtual bool Command(char *Cmd);
|
||||
bool CmdGET(const std::string &Opts);
|
||||
|
||||
virtual bool Abort(void) const;
|
||||
virtual void Flushed(void);
|
||||
@@ -61,7 +71,7 @@ public:
|
||||
|
||||
inline bool cConnectionHTTP::Abort(void) const
|
||||
{
|
||||
return m_LiveStreamer && m_LiveStreamer->Abort();
|
||||
return !IsOpen() || (Streamer() && Streamer()->Abort());
|
||||
}
|
||||
|
||||
#endif // VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H
|
||||
|
||||
77
server/connectionIGMP.c
Normal file
77
server/connectionIGMP.c
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* $Id: connectionIGMP.c,v 1.3 2010/08/03 10:46:41 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include "server/connectionIGMP.h"
|
||||
#include "server/server.h"
|
||||
#include "server/setup.h"
|
||||
#include <vdr/channels.h>
|
||||
|
||||
cConnectionIGMP::cConnectionIGMP(const char* Name, int ClientPort, eStreamType StreamType) :
|
||||
cServerConnection(Name, SOCK_DGRAM),
|
||||
m_ClientPort(ClientPort),
|
||||
m_StreamType(StreamType),
|
||||
m_Channel(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
cConnectionIGMP::~cConnectionIGMP()
|
||||
{
|
||||
}
|
||||
|
||||
#if APIVERSNUM >= 20300
|
||||
bool cConnectionIGMP::SetChannel(const cChannel *Channel, in_addr_t Dst)
|
||||
#else
|
||||
bool cConnectionIGMP::SetChannel(cChannel *Channel, in_addr_t Dst)
|
||||
#endif
|
||||
{
|
||||
if (Channel) {
|
||||
m_Channel = Channel;
|
||||
struct in_addr ip;
|
||||
ip.s_addr = Dst;
|
||||
if (Connect(inet_ntoa(ip), m_ClientPort))
|
||||
return true;
|
||||
else
|
||||
esyslog("streamdev-server IGMP: Connect failed: %m");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
esyslog("streamdev-server IGMP: Channel not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
void cConnectionIGMP::Welcome()
|
||||
{
|
||||
if (cStreamdevLiveStreamer::ProvidesChannel(m_Channel, StreamdevServerSetup.IGMPPriority)) {
|
||||
cStreamdevLiveStreamer * liveStreamer = new cStreamdevLiveStreamer(this, m_Channel, StreamdevServerSetup.IGMPPriority, m_StreamType);
|
||||
if (liveStreamer->GetDevice()) {
|
||||
SetStreamer(liveStreamer);
|
||||
if (!SetDSCP())
|
||||
LOG_ERROR_STR("unable to set DSCP sockopt");
|
||||
Dprintf("streamer start\n");
|
||||
liveStreamer->Start(this);
|
||||
}
|
||||
else {
|
||||
SetStreamer(NULL);
|
||||
delete liveStreamer;
|
||||
esyslog("streamdev-server IGMP: SetChannel failed");
|
||||
}
|
||||
}
|
||||
else
|
||||
esyslog("streamdev-server IGMP: SwitchDevice failed");
|
||||
}
|
||||
|
||||
bool cConnectionIGMP::Close()
|
||||
{
|
||||
if (Streamer())
|
||||
Streamer()->Stop();
|
||||
return cServerConnection::Close();
|
||||
}
|
||||
|
||||
cString cConnectionIGMP::ToText(char Delimiter) const
|
||||
{
|
||||
cString str = cServerConnection::ToText(Delimiter);
|
||||
return Streamer() ? cString::sprintf("%s%c%s", *str, Delimiter, *Streamer()->ToText()) : str;
|
||||
}
|
||||
53
server/connectionIGMP.h
Normal file
53
server/connectionIGMP.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* $Id: connectionIGMP.h,v 1.1 2009/02/13 10:39:22 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SERVERS_CONNECTIONIGMP_H
|
||||
#define VDR_STREAMDEV_SERVERS_CONNECTIONIGMP_H
|
||||
|
||||
#include "connection.h"
|
||||
#include "server/livestreamer.h"
|
||||
|
||||
#include <tools/select.h>
|
||||
|
||||
#define MULTICAST_PRIV_MIN ((uint32_t) 0xefff0000)
|
||||
#define MULTICAST_PRIV_MAX ((uint32_t) 0xeffffeff)
|
||||
|
||||
class cStreamdevLiveStreamer;
|
||||
|
||||
class cConnectionIGMP: public cServerConnection {
|
||||
private:
|
||||
int m_ClientPort;
|
||||
eStreamType m_StreamType;
|
||||
#if APIVERSNUM >= 20300
|
||||
const cChannel *m_Channel;
|
||||
#else
|
||||
cChannel *m_Channel;
|
||||
#endif
|
||||
|
||||
public:
|
||||
cConnectionIGMP(const char* Name, int ClientPort, eStreamType StreamType);
|
||||
virtual ~cConnectionIGMP();
|
||||
|
||||
#if APIVERSNUM >= 20300
|
||||
bool SetChannel(const cChannel *Channel, in_addr_t Dst);
|
||||
#else
|
||||
bool SetChannel(cChannel *Channel, in_addr_t Dst);
|
||||
#endif
|
||||
virtual void Welcome(void);
|
||||
virtual cString ToText(char Delimiter = ' ') const;
|
||||
|
||||
/* Not used here */
|
||||
virtual bool Command(char *Cmd) { return false; }
|
||||
|
||||
virtual bool Close(void);
|
||||
|
||||
virtual bool Abort(void) const;
|
||||
};
|
||||
|
||||
inline bool cConnectionIGMP::Abort(void) const
|
||||
{
|
||||
return !IsOpen() || !Streamer() || Streamer()->Abort();
|
||||
}
|
||||
|
||||
#endif // VDR_STREAMDEV_SERVERS_CONNECTIONIGMP_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,35 +2,45 @@
|
||||
#define VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H
|
||||
|
||||
#include "server/connection.h"
|
||||
#include "server/recplayer.h"
|
||||
|
||||
class cTBSocket;
|
||||
class cStreamdevLiveStreamer;
|
||||
class cStreamdevFilterStreamer;
|
||||
class cLSTEHandler;
|
||||
class cLSTCHandler;
|
||||
class cLSTTHandler;
|
||||
class cLSTRHandler;
|
||||
|
||||
class cConnectionVTP: public cServerConnection {
|
||||
friend class cLSTEHandler;
|
||||
// if your compiler doesn't understand the following statement
|
||||
// (e.g. gcc 2.x), simply remove it and try again ;-)
|
||||
#if !defined __GNUC__ || __GNUC__ >= 3
|
||||
using cServerConnection::Respond;
|
||||
#endif
|
||||
|
||||
private:
|
||||
cTBSocket *m_LiveSocket;
|
||||
cStreamdevLiveStreamer *m_LiveStreamer;
|
||||
cTBSocket *m_FilterSocket;
|
||||
cStreamdevFilterStreamer *m_FilterStreamer;
|
||||
cTBSocket *m_RecSocket;
|
||||
cTBSocket *m_DataSocket;
|
||||
|
||||
char *m_LastCommand;
|
||||
eStreamType m_StreamType;
|
||||
unsigned int m_ClientVersion;
|
||||
bool m_FiltersSupport;
|
||||
bool m_LoopPrevention;
|
||||
RecPlayer *m_RecPlayer;
|
||||
|
||||
// Priority is only known in PROV command
|
||||
// Store in here for later use in TUNE call
|
||||
const cChannel *m_TuneChannel;
|
||||
int m_TunePriority;
|
||||
|
||||
// Members adopted for SVDRP
|
||||
cRecordings Recordings;
|
||||
cLSTEHandler *m_LSTEHandler;
|
||||
cLSTCHandler *m_LSTCHandler;
|
||||
cLSTTHandler *m_LSTTHandler;
|
||||
cLSTRHandler *m_LSTRHandler;
|
||||
|
||||
protected:
|
||||
template<class cHandler>
|
||||
@@ -43,15 +53,22 @@ public:
|
||||
virtual void Welcome(void);
|
||||
virtual void Reject(void);
|
||||
|
||||
virtual cString ToText(char Delimiter = ' ') const;
|
||||
|
||||
virtual bool Abort(void) const;
|
||||
virtual void Detach(void);
|
||||
virtual void Attach(void);
|
||||
|
||||
virtual bool Command(char *Cmd);
|
||||
bool CmdCAPS(char *Opts);
|
||||
bool CmdVERS(char *Opts);
|
||||
bool CmdPROV(char *Opts);
|
||||
bool CmdPORT(char *Opts);
|
||||
bool CmdREAD(char *Opts);
|
||||
bool CmdTUNE(char *Opts);
|
||||
bool CmdPLAY(char *Opts);
|
||||
bool CmdPRIO(char *Opts);
|
||||
bool CmdSGNL(char *Opts);
|
||||
bool CmdADDP(char *Opts);
|
||||
bool CmdDELP(char *Opts);
|
||||
bool CmdADDF(char *Opts);
|
||||
@@ -64,14 +81,20 @@ public:
|
||||
bool CmdLSTE(char *Opts);
|
||||
bool CmdLSTC(char *Opts);
|
||||
bool CmdLSTT(char *Opts);
|
||||
bool CmdLSTR(char *Opts);
|
||||
|
||||
// Commands adopted from SVDRP
|
||||
bool CmdSTAT(const char *Option);
|
||||
bool CmdMODT(const char *Option);
|
||||
bool CmdNEWT(const char *Option);
|
||||
bool CmdDELT(const char *Option);
|
||||
|
||||
//bool CmdLSTR(char *Opts);
|
||||
//bool CmdDELR(char *Opts);
|
||||
bool CmdNEXT(const char *Option);
|
||||
bool CmdNEWC(const char *Option);
|
||||
bool CmdMODC(const char *Option);
|
||||
bool CmdMOVC(const char *Option);
|
||||
bool CmdDELC(const char *Option);
|
||||
bool CmdDELR(const char *Option);
|
||||
bool CmdRENR(const char *Option);
|
||||
|
||||
bool Respond(int Code, const char *Message, ...)
|
||||
__attribute__ ((format (printf, 3, 4)));
|
||||
|
||||
@@ -1,22 +1,53 @@
|
||||
/*
|
||||
* $Id: livefilter.c,v 1.5 2008/04/07 14:27:31 schmirl Exp $
|
||||
* $Id: livefilter.c,v 1.7 2009/02/13 13:02:40 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include <vdr/filter.h>
|
||||
|
||||
#include "server/livefilter.h"
|
||||
#include "server/streamer.h"
|
||||
#include "common.h"
|
||||
|
||||
#ifndef TS_SIZE
|
||||
# define TS_SIZE 188
|
||||
#endif
|
||||
#ifndef TS_SYNC_BYTE
|
||||
# define TS_SYNC_BYTE 0x47
|
||||
#endif
|
||||
|
||||
cStreamdevLiveFilter::cStreamdevLiveFilter(cStreamdevStreamer *Streamer) {
|
||||
#define FILTERBUFSIZE (1000 * TS_SIZE)
|
||||
|
||||
// --- cStreamdevLiveFilter -------------------------------------------------
|
||||
|
||||
class cStreamdevLiveFilter: public cFilter {
|
||||
private:
|
||||
cStreamdevFilterStreamer *m_Streamer;
|
||||
bool m_On;
|
||||
|
||||
protected:
|
||||
virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
|
||||
virtual void SetStatus(bool On);
|
||||
|
||||
public:
|
||||
cStreamdevLiveFilter(cStreamdevFilterStreamer *Streamer);
|
||||
|
||||
virtual bool IsAttached(void) const { return m_On; };
|
||||
|
||||
void Set(u_short Pid, u_char Tid, u_char Mask) {
|
||||
cFilter::Set(Pid, Tid, Mask);
|
||||
}
|
||||
void Del(u_short Pid, u_char Tid, u_char Mask) {
|
||||
cFilter::Del(Pid, Tid, Mask);
|
||||
}
|
||||
};
|
||||
|
||||
cStreamdevLiveFilter::cStreamdevLiveFilter(cStreamdevFilterStreamer *Streamer) {
|
||||
m_On = false;
|
||||
m_Streamer = Streamer;
|
||||
}
|
||||
|
||||
void cStreamdevLiveFilter::SetStatus(bool On)
|
||||
{
|
||||
m_On = On;
|
||||
cFilter::SetStatus(On);
|
||||
}
|
||||
|
||||
void cStreamdevLiveFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
|
||||
{
|
||||
uchar buffer[TS_SIZE];
|
||||
@@ -29,13 +60,92 @@ void cStreamdevLiveFilter::Process(u_short Pid, u_char Tid, const u_char *Data,
|
||||
buffer[1] = ((Pid >> 8) & 0x3f) | (pos==0 ? 0x40 : 0); /* bit 6: payload unit start indicator (PUSI) */
|
||||
buffer[2] = Pid & 0xff;
|
||||
buffer[3] = Tid;
|
||||
// this makes it a proprietary stream
|
||||
buffer[4] = (uchar)chunk;
|
||||
memcpy(buffer + 5, Data + pos, chunk);
|
||||
length -= chunk;
|
||||
pos += chunk;
|
||||
|
||||
int p = m_Streamer->Put(buffer, TS_SIZE);
|
||||
if (p != TS_SIZE)
|
||||
m_Streamer->ReportOverflow(TS_SIZE - p);
|
||||
m_Streamer->Receive(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// --- cStreamdevFilterStreamer -------------------------------------------------
|
||||
|
||||
cStreamdevFilterStreamer::cStreamdevFilterStreamer():
|
||||
cStreamdevStreamer("streamdev-filterstreaming"),
|
||||
m_Device(NULL),
|
||||
m_Filter(NULL)/*,
|
||||
m_Channel(NULL)*/
|
||||
{
|
||||
m_ReceiveBuffer = new cStreamdevBuffer(FILTERBUFSIZE, TS_SIZE);
|
||||
m_ReceiveBuffer->SetTimeouts(0, 500);
|
||||
}
|
||||
|
||||
cStreamdevFilterStreamer::~cStreamdevFilterStreamer()
|
||||
{
|
||||
Dprintf("Desctructing Filter streamer\n");
|
||||
Detach();
|
||||
m_Device = NULL;
|
||||
DELETENULL(m_Filter);
|
||||
Stop();
|
||||
delete m_ReceiveBuffer;
|
||||
}
|
||||
|
||||
void cStreamdevFilterStreamer::Receive(uchar *Data)
|
||||
{
|
||||
int p = m_ReceiveBuffer->PutTS(Data, TS_SIZE);
|
||||
if (p != TS_SIZE)
|
||||
m_ReceiveBuffer->ReportOverflow(TS_SIZE - p);
|
||||
}
|
||||
|
||||
void cStreamdevFilterStreamer::Attach(void)
|
||||
{
|
||||
Dprintf("cStreamdevFilterStreamer::Attach()\n");
|
||||
LOCK_THREAD;
|
||||
if(m_Device && m_Filter)
|
||||
m_Device->AttachFilter(m_Filter);
|
||||
}
|
||||
|
||||
void cStreamdevFilterStreamer::Detach(void)
|
||||
{
|
||||
Dprintf("cStreamdevFilterStreamer::Detach()\n");
|
||||
LOCK_THREAD;
|
||||
if(m_Device && m_Filter)
|
||||
m_Device->Detach(m_Filter);
|
||||
}
|
||||
|
||||
void cStreamdevFilterStreamer::SetDevice(cDevice *Device)
|
||||
{
|
||||
Dprintf("cStreamdevFilterStreamer::SetDevice()\n");
|
||||
LOCK_THREAD;
|
||||
Detach();
|
||||
m_Device = Device;
|
||||
Attach();
|
||||
}
|
||||
|
||||
bool cStreamdevFilterStreamer::IsReceiving(void) const
|
||||
{
|
||||
return m_Filter && m_Filter->IsAttached();
|
||||
}
|
||||
|
||||
bool cStreamdevFilterStreamer::SetFilter(u_short Pid, u_char Tid, u_char Mask, bool On)
|
||||
{
|
||||
Dprintf("cStreamdevFilterStreamer::SetFilter(%u,0x%x,0x%x,%s)\n", Pid, Tid, Mask, On?"On":"Off");
|
||||
|
||||
if(!m_Device)
|
||||
return false;
|
||||
|
||||
if (On) {
|
||||
if (m_Filter == NULL) {
|
||||
m_Filter = new cStreamdevLiveFilter(this);
|
||||
Dprintf("attaching filter to device\n");
|
||||
Attach();
|
||||
}
|
||||
m_Filter->Set(Pid, Tid, Mask);
|
||||
} else if (m_Filter != NULL)
|
||||
m_Filter->Del(Pid, Tid, Mask);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,28 +5,33 @@
|
||||
#ifndef VDR_STREAMEV_LIVEFILTER_H
|
||||
#define VDR_STREAMEV_LIVEFILTER_H
|
||||
|
||||
#include <vdr/config.h>
|
||||
#include "server/streamer.h"
|
||||
|
||||
#include <vdr/filter.h>
|
||||
class cDevice;
|
||||
class cStreamdevLiveFilter;
|
||||
|
||||
class cStreamdevStreamer;
|
||||
|
||||
class cStreamdevLiveFilter: public cFilter {
|
||||
class cStreamdevFilterStreamer: public cStreamdevStreamer {
|
||||
private:
|
||||
cStreamdevStreamer *m_Streamer;
|
||||
cDevice *m_Device;
|
||||
cStreamdevLiveFilter *m_Filter;
|
||||
cStreamdevBuffer *m_ReceiveBuffer;
|
||||
|
||||
protected:
|
||||
virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
|
||||
virtual uchar* GetFromReceiver(int &Count) { return m_ReceiveBuffer->Get(Count); }
|
||||
virtual void DelFromReceiver(int Count) { m_ReceiveBuffer->Del(Count); }
|
||||
|
||||
public:
|
||||
cStreamdevLiveFilter(cStreamdevStreamer *Streamer);
|
||||
cStreamdevFilterStreamer();
|
||||
virtual ~cStreamdevFilterStreamer();
|
||||
|
||||
void Set(u_short Pid, u_char Tid, u_char Mask) {
|
||||
cFilter::Set(Pid, Tid, Mask);
|
||||
}
|
||||
void Del(u_short Pid, u_char Tid, u_char Mask) {
|
||||
cFilter::Del(Pid, Tid, Mask);
|
||||
}
|
||||
void SetDevice(cDevice *Device);
|
||||
bool SetFilter(u_short Pid, u_char Tid, u_char Mask, bool On);
|
||||
|
||||
virtual bool IsReceiving(void) const;
|
||||
void Receive(uchar *Data);
|
||||
|
||||
virtual void Attach(void);
|
||||
virtual void Detach(void);
|
||||
};
|
||||
|
||||
#endif // VDR_STREAMEV_LIVEFILTER_H
|
||||
|
||||
@@ -3,16 +3,24 @@
|
||||
#include <libsi/section.h>
|
||||
#include <libsi/descriptor.h>
|
||||
|
||||
#include <vdr/ringbuffer.h>
|
||||
|
||||
#include "server/livestreamer.h"
|
||||
#include "server/livefilter.h"
|
||||
#include "remux/ts2ps.h"
|
||||
#include "remux/ts2pes.h"
|
||||
#include "remux/ts2es.h"
|
||||
#include "remux/extern.h"
|
||||
|
||||
#include <vdr/transfer.h>
|
||||
|
||||
#include "server/livestreamer.h"
|
||||
#include "server/setup.h"
|
||||
#include "common.h"
|
||||
|
||||
#define TSPATREPACKER
|
||||
using namespace Streamdev;
|
||||
|
||||
// device occupied timeout to prevent VDR main loop to immediately switch back
|
||||
// when streamdev switched the live TV channel.
|
||||
// Note that there is still a gap between the GetDevice() and SetOccupied()
|
||||
// calls where the VDR main loop could strike
|
||||
#define STREAMDEVTUNETIMEOUT 5
|
||||
|
||||
// --- cStreamdevLiveReceiver -------------------------------------------------
|
||||
|
||||
@@ -20,32 +28,27 @@ class cStreamdevLiveReceiver: public cReceiver {
|
||||
friend class cStreamdevStreamer;
|
||||
|
||||
private:
|
||||
cStreamdevStreamer *m_Streamer;
|
||||
cStreamdevLiveStreamer *m_Streamer;
|
||||
|
||||
protected:
|
||||
virtual void Activate(bool On);
|
||||
#if APIVERSNUM >= 20300
|
||||
virtual void Receive(const uchar *Data, int Length);
|
||||
#else
|
||||
virtual void Receive(uchar *Data, int Length);
|
||||
#endif
|
||||
|
||||
public:
|
||||
#if VDRVERSNUM < 10500
|
||||
cStreamdevLiveReceiver(cStreamdevStreamer *Streamer, int Ca, int Priority, const int *Pids);
|
||||
#else
|
||||
cStreamdevLiveReceiver(cStreamdevStreamer *Streamer, tChannelID ChannelID, int Priority, const int *Pids);
|
||||
#endif
|
||||
cStreamdevLiveReceiver(cStreamdevLiveStreamer *Streamer, const cChannel *Channel, int Priority, const int *Pids);
|
||||
virtual ~cStreamdevLiveReceiver();
|
||||
};
|
||||
|
||||
#if VDRVERSNUM < 10500
|
||||
cStreamdevLiveReceiver::cStreamdevLiveReceiver(cStreamdevStreamer *Streamer, int Ca,
|
||||
int Priority, const int *Pids):
|
||||
cReceiver(Ca, Priority, 0, Pids),
|
||||
#else
|
||||
cStreamdevLiveReceiver::cStreamdevLiveReceiver(cStreamdevStreamer *Streamer, tChannelID ChannelID,
|
||||
int Priority, const int *Pids):
|
||||
cReceiver(ChannelID, Priority, 0, Pids),
|
||||
#endif
|
||||
cStreamdevLiveReceiver::cStreamdevLiveReceiver(cStreamdevLiveStreamer *Streamer, const cChannel *Channel, int Priority, const int *Pids):
|
||||
cReceiver(Channel, Priority),
|
||||
m_Streamer(Streamer)
|
||||
{
|
||||
// clears all PIDs but channel remains set
|
||||
SetPids(NULL);
|
||||
AddPids(Pids);
|
||||
}
|
||||
|
||||
cStreamdevLiveReceiver::~cStreamdevLiveReceiver()
|
||||
@@ -54,16 +57,12 @@ cStreamdevLiveReceiver::~cStreamdevLiveReceiver()
|
||||
Detach();
|
||||
}
|
||||
|
||||
#if APIVERSNUM >= 20300
|
||||
void cStreamdevLiveReceiver::Receive(const uchar *Data, int Length) {
|
||||
#else
|
||||
void cStreamdevLiveReceiver::Receive(uchar *Data, int Length) {
|
||||
int p = m_Streamer->Receive(Data, Length);
|
||||
if (p != Length)
|
||||
m_Streamer->ReportOverflow(Length - p);
|
||||
}
|
||||
|
||||
inline void cStreamdevLiveReceiver::Activate(bool On)
|
||||
{
|
||||
Dprintf("LiveReceiver->Activate(%d)\n", On);
|
||||
m_Streamer->Activate(On);
|
||||
#endif
|
||||
m_Streamer->Receive(Data, Length);
|
||||
}
|
||||
|
||||
// --- cStreamdevPatFilter ----------------------------------------------------
|
||||
@@ -73,6 +72,8 @@ private:
|
||||
int pmtPid;
|
||||
int pmtSid;
|
||||
int pmtVersion;
|
||||
uchar tspat_buf[TS_SIZE];
|
||||
cStreamdevBuffer siBuffer;
|
||||
|
||||
const cChannel *m_Channel;
|
||||
cStreamdevLiveStreamer *m_Streamer;
|
||||
@@ -82,11 +83,13 @@ private:
|
||||
int GetPid(SI::PMT::Stream& stream);
|
||||
public:
|
||||
cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const cChannel *Channel);
|
||||
uchar* Get(int &Count) { return siBuffer.Get(Count); }
|
||||
void Del(int Count) { return siBuffer.Del(Count); }
|
||||
};
|
||||
|
||||
cStreamdevPatFilter::cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const cChannel *Channel)
|
||||
cStreamdevPatFilter::cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const cChannel *Channel): siBuffer(10 * TS_SIZE, TS_SIZE)
|
||||
{
|
||||
Dprintf("cStreamdevPatFilter(\"%s\")", Channel->Name());
|
||||
Dprintf("cStreamdevPatFilter(\"%s\")\n", Channel->Name());
|
||||
assert(Streamer);
|
||||
m_Channel = Channel;
|
||||
m_Streamer = Streamer;
|
||||
@@ -94,6 +97,29 @@ cStreamdevPatFilter::cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const
|
||||
pmtSid = 0;
|
||||
pmtVersion = -1;
|
||||
Set(0x00, 0x00); // PAT
|
||||
// initialize PAT buffer. Only some values are dynamic (see comments)
|
||||
memset(tspat_buf, 0xff, TS_SIZE);
|
||||
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, DYNAMIC: Continuity counter
|
||||
tspat_buf[4] = 0x0; // SI pointer field
|
||||
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] = 0; // DYNAMIC: Transport stream ID (bits 8-15)
|
||||
tspat_buf[9] = 0; // DYNAMIC: Transport stream ID (bits 0-7)
|
||||
tspat_buf[10] = 0xc0; // Reserved, DYNAMIC: Version number, DYNAMIC: Current next indicator
|
||||
tspat_buf[11] = 0x0; // Section number
|
||||
tspat_buf[12] = 0x0; // Last section number
|
||||
tspat_buf[13] = 0; // DYNAMIC: Program number (bits 8-15)
|
||||
tspat_buf[14] = 0; // DYNAMIC: Program number (bits 0-7)
|
||||
tspat_buf[15] = 0xe0; // Reserved, DYNAMIC: Network ID (bits 8-12)
|
||||
tspat_buf[16] = 0; // DYNAMIC: Network ID (bits 0-7)
|
||||
tspat_buf[17] = 0; // DYNAMIC: Checksum
|
||||
tspat_buf[18] = 0; // DYNAMIC: Checksum
|
||||
tspat_buf[19] = 0; // DYNAMIC: Checksum
|
||||
tspat_buf[20] = 0; // DYNAMIC: Checksum
|
||||
}
|
||||
|
||||
static const char * const psStreamTypes[] = {
|
||||
@@ -145,7 +171,7 @@ int cStreamdevPatFilter::GetPid(SI::PMT::Stream& stream)
|
||||
case 0x10: // ISO/IEC 14496-2 Visual (MPEG-4)
|
||||
case 0x11: // ISO/IEC 14496-3 Audio with LATM transport syntax
|
||||
case 0x1b: // ISO/IEC 14496-10 Video (MPEG-4 part 10/AVC, aka H.264)
|
||||
Dprintf("cStreamdevPatFilter PMT scanner adding PID %d (%s)",
|
||||
Dprintf("cStreamdevPatFilter PMT scanner adding PID %d (%s)\n",
|
||||
stream.getPid(), psStreamTypes[stream.getStreamType()]);
|
||||
return stream.getPid();
|
||||
case 0x05: // ISO/IEC 13818-1 private sections
|
||||
@@ -153,19 +179,23 @@ int cStreamdevPatFilter::GetPid(SI::PMT::Stream& stream)
|
||||
for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) {
|
||||
switch (d->getDescriptorTag()) {
|
||||
case SI::AC3DescriptorTag:
|
||||
Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s",
|
||||
case SI::EnhancedAC3DescriptorTag:
|
||||
Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s\n",
|
||||
stream.getPid(), psStreamTypes[stream.getStreamType()], "AC3");
|
||||
delete d;
|
||||
return stream.getPid();
|
||||
case SI::TeletextDescriptorTag:
|
||||
Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s",
|
||||
Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s\n",
|
||||
stream.getPid(), psStreamTypes[stream.getStreamType()], "Teletext");
|
||||
delete d;
|
||||
return stream.getPid();
|
||||
case SI::SubtitlingDescriptorTag:
|
||||
Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s",
|
||||
Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s\n",
|
||||
stream.getPid(), psStreamTypes[stream.getStreamType()], "DVBSUB");
|
||||
delete d;
|
||||
return stream.getPid();
|
||||
default:
|
||||
Dprintf("cStreamdevPatFilter PMT scanner: NOT adding PID %d (%s) %s",
|
||||
Dprintf("cStreamdevPatFilter PMT scanner: NOT adding PID %d (%s) %s\n",
|
||||
stream.getPid(), psStreamTypes[stream.getStreamType()], "UNKNOWN");
|
||||
break;
|
||||
}
|
||||
@@ -195,6 +225,7 @@ int cStreamdevPatFilter::GetPid(SI::PMT::Stream& stream)
|
||||
stream.getPid(), stream.getStreamType(),
|
||||
d->getLength(), rawdata[2], rawdata[3],
|
||||
rawdata[4], rawdata[5]);
|
||||
delete d;
|
||||
return stream.getPid();
|
||||
}
|
||||
}
|
||||
@@ -210,7 +241,7 @@ int cStreamdevPatFilter::GetPid(SI::PMT::Stream& stream)
|
||||
return stream.getPid();
|
||||
}
|
||||
}
|
||||
Dprintf("cStreamdevPatFilter PMT scanner: NOT adding PID %d (%s) %s",
|
||||
Dprintf("cStreamdevPatFilter PMT scanner: NOT adding PID %d (%s) %s\n",
|
||||
stream.getPid(), psStreamTypes[stream.getStreamType()<0x1c?stream.getStreamType():0], "UNKNOWN");
|
||||
break;
|
||||
}
|
||||
@@ -220,67 +251,60 @@ int cStreamdevPatFilter::GetPid(SI::PMT::Stream& stream)
|
||||
void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
|
||||
{
|
||||
if (Pid == 0x00) {
|
||||
if (Tid == 0x00 && !pmtPid) {
|
||||
if (Tid == 0x00) {
|
||||
SI::PAT pat(Data, false);
|
||||
if (!pat.CheckCRCAndParse())
|
||||
return;
|
||||
SI::PAT::Association assoc;
|
||||
for (SI::Loop::Iterator it; pat.associationLoop.getNext(assoc, it); ) {
|
||||
if (!assoc.isNITPid()) {
|
||||
const cChannel *Channel = Channels.GetByServiceID(Source(), Transponder(), assoc.getServiceId());
|
||||
if (Channel && (Channel == m_Channel)) {
|
||||
if (0 != (pmtPid = assoc.getPid())) {
|
||||
Dprintf("cStreamdevPatFilter: PMT pid for channel %s: %d", Channel->Name(), pmtPid);
|
||||
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);
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_CHANNELS_READ;
|
||||
const cChannel *Channel = Channels->GetByServiceID(Source(), Transponder(), assoc.getServiceId());
|
||||
#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);
|
||||
const cChannel *Channel = Channels.GetByServiceID(Source(), Transponder(), assoc.getServiceId());
|
||||
#endif
|
||||
} else
|
||||
isyslog("cStreamdevPatFilter: PAT size %d too large to fit in one TS", Length);
|
||||
m_Streamer->SetPids(pmtPid);
|
||||
Add(pmtPid, 0x02);
|
||||
pmtVersion = -1;
|
||||
if (Channel && (Channel == m_Channel)) {
|
||||
int prevPmtPid = pmtPid;
|
||||
if (0 != (pmtPid = assoc.getPid())) {
|
||||
Dprintf("cStreamdevPatFilter: PMT pid for channel %s: %d\n", Channel->Name(), pmtPid);
|
||||
pmtSid = assoc.getServiceId();
|
||||
// repack PAT to TS frame and send to client
|
||||
int ts_id;
|
||||
unsigned int crc, i, len;
|
||||
uint8_t *tmp;
|
||||
static uint8_t ccounter = 0;
|
||||
ccounter = (ccounter + 1) % 16;
|
||||
ts_id = Channel->Tid(); // Get transport stream id of the channel
|
||||
tspat_buf[3] = 0x10 | ccounter; // Set payload flag, Continuity counter
|
||||
tspat_buf[8] = (ts_id >> 8); // Transport stream ID (bits 8-15)
|
||||
tspat_buf[9] = (ts_id & 0xff); // Transport stream ID (bits 0-7)
|
||||
tspat_buf[10] = 0xc0 | ((pat.getVersionNumber() << 1) & 0x3e) |
|
||||
pat.getCurrentNextIndicator();// Version number, Current next indicator
|
||||
tspat_buf[13] = (pmtSid >> 8); // Program number (bits 8-15)
|
||||
tspat_buf[14] = (pmtSid & 0xff); // Program number (bits 0-7)
|
||||
tspat_buf[15] = 0xe0 | (pmtPid >> 8); // 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
|
||||
int written = siBuffer.PutTS(tspat_buf, TS_SIZE);
|
||||
if (written != TS_SIZE)
|
||||
siBuffer.ReportOverflow(TS_SIZE - written);
|
||||
if (pmtPid != prevPmtPid) {
|
||||
m_Streamer->SetPid(pmtPid, true);
|
||||
Add(pmtPid, 0x02);
|
||||
pmtVersion = -1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -295,8 +319,8 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
|
||||
return; // skip broken PMT records
|
||||
if (pmtVersion != -1) {
|
||||
if (pmtVersion != pmt.getVersionNumber()) {
|
||||
Dprintf("cStreamdevPatFilter: PMT version changed, detaching all pids");
|
||||
Del(pmtPid, 0x02);
|
||||
Dprintf("cStreamdevPatFilter: PMT version changed, detaching all pids\n");
|
||||
cFilter::Del(pmtPid, 0x02);
|
||||
pmtPid = 0; // this triggers PAT scan
|
||||
}
|
||||
return;
|
||||
@@ -308,9 +332,9 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
|
||||
pids[npids++] = pmtPid;
|
||||
#if 0
|
||||
pids[npids++] = 0x10; // pid 0x10, tid 0x40: NIT
|
||||
#endif
|
||||
pids[npids++] = 0x11; // pid 0x11, tid 0x42: SDT
|
||||
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)
|
||||
@@ -323,36 +347,36 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
|
||||
|
||||
// --- cStreamdevLiveStreamer -------------------------------------------------
|
||||
|
||||
cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority, std::string Parameter):
|
||||
cStreamdevStreamer("streamdev-livestreaming"),
|
||||
cStreamdevLiveStreamer::cStreamdevLiveStreamer(const cServerConnection *Connection, const cChannel *Channel, int Priority, eStreamType StreamType, const int* Apid, const int *Dpid) :
|
||||
cStreamdevStreamer("streamdev-livestreaming", Connection),
|
||||
m_Priority(Priority),
|
||||
m_Parameter(Parameter),
|
||||
m_NumPids(0),
|
||||
m_StreamType(stTSPIDS),
|
||||
m_Channel(NULL),
|
||||
m_Channel(Channel),
|
||||
m_Device(NULL),
|
||||
m_Receiver(NULL),
|
||||
m_PatFilter(NULL),
|
||||
m_PESRemux(NULL),
|
||||
m_ESRemux(NULL),
|
||||
m_PSRemux(NULL),
|
||||
m_ExtRemux(NULL)
|
||||
m_SwitchLive(false)
|
||||
{
|
||||
m_ReceiveBuffer = new cStreamdevBuffer(LIVEBUFSIZE, TS_SIZE *2, true, "streamdev-livestreamer"),
|
||||
m_ReceiveBuffer->SetTimeouts(0, 100);
|
||||
if (Priority == IDLEPRIORITY) {
|
||||
SetChannel(StreamType, Apid, Dpid);
|
||||
}
|
||||
else {
|
||||
m_Device = SwitchDevice(Channel, Priority);
|
||||
if (m_Device)
|
||||
SetChannel(StreamType, Apid, Dpid);
|
||||
memcpy(m_Caids,Channel->Caids(),sizeof(m_Caids));
|
||||
}
|
||||
}
|
||||
|
||||
cStreamdevLiveStreamer::~cStreamdevLiveStreamer()
|
||||
{
|
||||
Dprintf("Desctructing Live streamer\n");
|
||||
Stop();
|
||||
if(m_PatFilter) {
|
||||
Detach();
|
||||
DELETENULL(m_PatFilter);
|
||||
}
|
||||
DELETENULL(m_PatFilter);
|
||||
DELETENULL(m_Receiver);
|
||||
delete m_PESRemux;
|
||||
delete m_ESRemux;
|
||||
delete m_PSRemux;
|
||||
delete m_ExtRemux;
|
||||
delete m_ReceiveBuffer;
|
||||
}
|
||||
|
||||
bool cStreamdevLiveStreamer::HasPid(int Pid)
|
||||
@@ -429,148 +453,152 @@ bool cStreamdevLiveStreamer::SetPids(int Pid, const int *Pids1, const int *Pids2
|
||||
return true;
|
||||
}
|
||||
|
||||
void cStreamdevLiveStreamer::StartReceiver(void)
|
||||
void cStreamdevLiveStreamer::SetPriority(int Priority)
|
||||
{
|
||||
DELETENULL(m_Receiver);
|
||||
if (m_NumPids > 0) {
|
||||
Dprintf("Creating Receiver to respect changed pids\n");
|
||||
#if VDRVERSNUM < 10500
|
||||
m_Receiver = new cStreamdevLiveReceiver(this, m_Channel->Ca(), m_Priority, m_Pids);
|
||||
#else
|
||||
m_Receiver = new cStreamdevLiveReceiver(this, m_Channel->GetChannelID(), m_Priority, m_Pids);
|
||||
m_Priority = Priority;
|
||||
#if VDRVERSNUM >= 20104
|
||||
cThreadLock ThreadLock(m_Device);
|
||||
if (m_Receiver)
|
||||
m_Receiver->SetPriority(Priority);
|
||||
else
|
||||
#endif
|
||||
if (IsRunning() && m_Device != NULL) {
|
||||
Dprintf("Attaching new receiver\n");
|
||||
Attach();
|
||||
}
|
||||
StartReceiver();
|
||||
}
|
||||
|
||||
void cStreamdevLiveStreamer::GetSignal(int *DevNum, int *Strength, int *Quality) const
|
||||
{
|
||||
if (m_Device) {
|
||||
*DevNum = m_Device->DeviceNumber() + 1;
|
||||
*Strength = m_Device->SignalStrength();
|
||||
*Quality = m_Device->SignalQuality();
|
||||
}
|
||||
}
|
||||
|
||||
bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType StreamType, int Apid)
|
||||
cString cStreamdevLiveStreamer::ToText() const
|
||||
{
|
||||
if (m_Device && m_Channel) {
|
||||
return cString::sprintf("DVB%-2d %3d %s", m_Device->DeviceNumber() + 1, m_Channel->Number(), m_Channel->Name());
|
||||
}
|
||||
return cString("");
|
||||
}
|
||||
|
||||
bool cStreamdevLiveStreamer::IsReceiving(void) const
|
||||
{
|
||||
cThreadLock ThreadLock(m_Device);
|
||||
return m_Receiver && m_Receiver->IsAttached();
|
||||
}
|
||||
|
||||
void cStreamdevLiveStreamer::StartReceiver(bool Force)
|
||||
{
|
||||
if (m_NumPids > 0 || Force) {
|
||||
Dprintf("Creating Receiver to respect changed pids\n");
|
||||
cReceiver *current = m_Receiver;
|
||||
cThreadLock ThreadLock(m_Device);
|
||||
m_Receiver = new cStreamdevLiveReceiver(this, m_Channel, m_Priority, m_Pids);
|
||||
if (IsRunning())
|
||||
Attach();
|
||||
delete current;
|
||||
}
|
||||
else
|
||||
DELETENULL(m_Receiver);
|
||||
}
|
||||
|
||||
bool cStreamdevLiveStreamer::SetChannel(eStreamType StreamType, const int* Apid, const int *Dpid)
|
||||
{
|
||||
Dprintf("Initializing Remuxer for full channel transfer\n");
|
||||
//printf("ca pid: %d\n", Channel->Ca());
|
||||
m_Channel = Channel;
|
||||
m_StreamType = StreamType;
|
||||
|
||||
int apid[2] = { Apid, 0 };
|
||||
const int *Apids = Apid ? apid : m_Channel->Apids();
|
||||
const int *Dpids = Apid ? NULL : m_Channel->Dpids();
|
||||
const int *Apids = Apid ? Apid : m_Channel->Apids();
|
||||
const int *Dpids = Dpid ? Dpid : m_Channel->Dpids();
|
||||
|
||||
switch (m_StreamType) {
|
||||
switch (StreamType) {
|
||||
case stES:
|
||||
{
|
||||
int pid = ISRADIO(m_Channel) ? m_Channel->Apid(0) : m_Channel->Vpid();
|
||||
if (Apid != 0)
|
||||
pid = Apid;
|
||||
m_ESRemux = new cTS2ESRemux(pid);
|
||||
if (Apid && Apid[0])
|
||||
pid = Apid[0];
|
||||
else if (Dpid && Dpid[0])
|
||||
pid = Dpid[0];
|
||||
SetRemux(new cTS2ESRemux(pid));
|
||||
return SetPids(pid);
|
||||
}
|
||||
|
||||
case stPES:
|
||||
m_PESRemux = new cRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(),
|
||||
m_Channel->Spids(), false);
|
||||
SetRemux(new cTS2PESRemux(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids()));
|
||||
return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids());
|
||||
|
||||
#ifdef STREAMDEV_PS
|
||||
case stPS:
|
||||
m_PSRemux = new cTS2PSRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(),
|
||||
m_Channel->Spids());
|
||||
SetRemux(new cTS2PSRemux(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids()));
|
||||
return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids());
|
||||
#endif
|
||||
|
||||
case stEXT:
|
||||
SetRemux(new cExternRemux(Connection(), m_Channel, Apids, Dpids));
|
||||
// fall through
|
||||
case stTS:
|
||||
// This should never happen, but ...
|
||||
if (m_PatFilter) {
|
||||
Detach();
|
||||
DELETENULL(m_PatFilter);
|
||||
}
|
||||
// Set pids from cChannel
|
||||
SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids());
|
||||
if (m_Channel->Vpid() != m_Channel->Ppid())
|
||||
SetPid(m_Channel->Ppid(), true);
|
||||
// Set pids from PMT
|
||||
m_PatFilter = new cStreamdevPatFilter(this, m_Channel);
|
||||
return true;
|
||||
|
||||
case stExtern:
|
||||
m_ExtRemux = new cExternRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(),
|
||||
m_Channel->Spids(), m_Parameter);
|
||||
return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids());
|
||||
|
||||
case stTSPIDS:
|
||||
Dprintf("pid streaming mode\n");
|
||||
// No PIDs requested yet. Start receiver anyway to occupy device
|
||||
StartReceiver(true);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if APIVERSNUM >= 20300
|
||||
void cStreamdevLiveStreamer::Receive(const uchar *Data, int Length)
|
||||
#else
|
||||
void cStreamdevLiveStreamer::Receive(uchar *Data, int Length)
|
||||
#endif
|
||||
{
|
||||
int p = m_ReceiveBuffer->PutTS(Data, Length);
|
||||
if (p != Length)
|
||||
m_ReceiveBuffer->ReportOverflow(Length - p);
|
||||
}
|
||||
|
||||
void cStreamdevLiveStreamer::Action(void)
|
||||
{
|
||||
if (StreamdevServerSetup.LiveBufferMs) {
|
||||
// wait for first data block
|
||||
int count = 0;
|
||||
while (Running()) {
|
||||
if (m_ReceiveBuffer->Get(count) != NULL) {
|
||||
cCondWait::SleepMs(StreamdevServerSetup.LiveBufferMs);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
cStreamdevStreamer::Action();
|
||||
}
|
||||
|
||||
int cStreamdevLiveStreamer::Put(const uchar *Data, int Count)
|
||||
{
|
||||
switch (m_StreamType) {
|
||||
case stTS:
|
||||
case stTSPIDS:
|
||||
return cStreamdevStreamer::Put(Data, Count);
|
||||
|
||||
case stPES:
|
||||
return m_PESRemux->Put(Data, Count);
|
||||
|
||||
case stES:
|
||||
return m_ESRemux->Put(Data, Count);
|
||||
|
||||
case stPS:
|
||||
return m_PSRemux->Put(Data, Count);
|
||||
|
||||
case stExtern:
|
||||
return m_ExtRemux->Put(Data, Count);
|
||||
|
||||
default: // shouldn't happen???
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uchar *cStreamdevLiveStreamer::Get(int &Count)
|
||||
{
|
||||
switch (m_StreamType) {
|
||||
case stTS:
|
||||
case stTSPIDS:
|
||||
return cStreamdevStreamer::Get(Count);
|
||||
|
||||
case stPES:
|
||||
return m_PESRemux->Get(Count);
|
||||
|
||||
case stES:
|
||||
return m_ESRemux->Get(Count);
|
||||
|
||||
case stPS:
|
||||
return m_PSRemux->Get(Count);
|
||||
|
||||
case stExtern:
|
||||
return m_ExtRemux->Get(Count);
|
||||
|
||||
default: // shouldn't happen???
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void cStreamdevLiveStreamer::Del(int Count)
|
||||
{
|
||||
switch (m_StreamType) {
|
||||
case stTS:
|
||||
case stTSPIDS:
|
||||
cStreamdevStreamer::Del(Count);
|
||||
break;
|
||||
|
||||
case stPES:
|
||||
m_PESRemux->Del(Count);
|
||||
break;
|
||||
|
||||
case stES:
|
||||
m_ESRemux->Del(Count);
|
||||
break;
|
||||
|
||||
case stPS:
|
||||
m_PSRemux->Del(Count);
|
||||
break;
|
||||
|
||||
case stExtern:
|
||||
m_ExtRemux->Del(Count);
|
||||
break;
|
||||
// insert si data
|
||||
if (m_PatFilter) {
|
||||
int siCount;
|
||||
uchar *siData = m_PatFilter->Get(siCount);
|
||||
if (siData) {
|
||||
siCount = cStreamdevStreamer::Put(siData, siCount);
|
||||
if (siCount)
|
||||
m_PatFilter->Del(siCount);
|
||||
}
|
||||
}
|
||||
return cStreamdevStreamer::Put(Data, Count);
|
||||
}
|
||||
|
||||
void cStreamdevLiveStreamer::Attach(void)
|
||||
@@ -578,7 +606,8 @@ void cStreamdevLiveStreamer::Attach(void)
|
||||
Dprintf("cStreamdevLiveStreamer::Attach()\n");
|
||||
if (m_Device) {
|
||||
if (m_Receiver) {
|
||||
m_Device->Detach(m_Receiver);
|
||||
if (m_Receiver->IsAttached())
|
||||
m_Device->Detach(m_Receiver);
|
||||
m_Device->AttachReceiver(m_Receiver);
|
||||
}
|
||||
if (m_PatFilter) {
|
||||
@@ -599,6 +628,101 @@ void cStreamdevLiveStreamer::Detach(void)
|
||||
}
|
||||
}
|
||||
|
||||
bool cStreamdevLiveStreamer::UsedByLiveTV(cDevice *device)
|
||||
{
|
||||
return device == cTransferControl::ReceiverDevice() ||
|
||||
(device->IsPrimaryDevice() && device->HasDecoder() && !device->Replaying());
|
||||
}
|
||||
|
||||
cDevice *cStreamdevLiveStreamer::SwitchDevice(const cChannel *Channel, int Priority)
|
||||
{
|
||||
cDevice *device = cDevice::GetDevice(Channel, Priority, false);
|
||||
if (!device) {
|
||||
dsyslog("streamdev: GetDevice failed for channel %d (%s) at priority %d (PrimaryDevice=%d, ActualDevice=%d)", Channel->Number(), Channel->Name(), Priority, cDevice::PrimaryDevice()->CardIndex(), cDevice::ActualDevice()->CardIndex());
|
||||
}
|
||||
else if (!device->IsTunedToTransponder(Channel) && UsedByLiveTV(device)) {
|
||||
// make sure VDR main loop doesn't switch back
|
||||
device->SetOccupied(STREAMDEVTUNETIMEOUT);
|
||||
if (device->SwitchChannel(Channel, false)) {
|
||||
// switched away live TV
|
||||
m_SwitchLive = true;
|
||||
}
|
||||
else {
|
||||
dsyslog("streamdev: SwitchChannel (live) failed for channel %d (%s) at priority %d (PrimaryDevice=%d, ActualDevice=%d, device=%d)", Channel->Number(), Channel->Name(), Priority, cDevice::PrimaryDevice()->CardIndex(), cDevice::ActualDevice()->CardIndex(), device->CardIndex());
|
||||
device->SetOccupied(0);
|
||||
device = NULL;
|
||||
}
|
||||
}
|
||||
else if (!device->SwitchChannel(Channel, false)) {
|
||||
dsyslog("streamdev: SwitchChannel failed for channel %d (%s) at priority %d (PrimaryDevice=%d, ActualDevice=%d, device=%d)", Channel->Number(), Channel->Name(), Priority, cDevice::PrimaryDevice()->CardIndex(), cDevice::ActualDevice()->CardIndex(), device->CardIndex());
|
||||
device = NULL;
|
||||
}
|
||||
return device;
|
||||
}
|
||||
|
||||
bool cStreamdevLiveStreamer::ProvidesChannel(const cChannel *Channel, int Priority)
|
||||
{
|
||||
cDevice *device = cDevice::GetDevice(Channel, Priority, false, true);
|
||||
if (!device)
|
||||
dsyslog("streamdev: No device provides channel %d (%s) at priority %d", Channel->Number(), Channel->Name(), Priority);
|
||||
return device;
|
||||
}
|
||||
|
||||
void cStreamdevLiveStreamer::ChannelChange(const cChannel *Channel)
|
||||
{
|
||||
if (Running() && m_Device && m_Channel == Channel) {
|
||||
// Check whether the Caids actually changed
|
||||
// If not, no need to re-tune, probably just an Audio PID update
|
||||
if (!memcmp(m_Caids, Channel->Caids(), sizeof(m_Caids))) {
|
||||
dsyslog("streamdev: channel %d (%s) changed, but caids remained the same, not re-tuning", Channel->Number(), Channel->Name());
|
||||
}
|
||||
else {
|
||||
Detach();
|
||||
if (m_Device->SwitchChannel(m_Channel, false)) {
|
||||
Attach();
|
||||
dsyslog("streamdev: channel %d (%s) changed, re-tuned", Channel->Number(), Channel->Name());
|
||||
memcpy(m_Caids, Channel->Caids(), sizeof(m_Caids));
|
||||
}
|
||||
else
|
||||
isyslog("streamdev: failed to re-tune after channel %d (%s) changed", Channel->Number(), Channel->Name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cStreamdevLiveStreamer::MainThreadHook()
|
||||
{
|
||||
if (!m_SwitchLive && Running() && m_Device && !m_Device->IsTunedToTransponder(m_Channel) && !IsReceiving()) {
|
||||
cDevice *dev = SwitchDevice(m_Channel, m_Priority);
|
||||
if (dev) {
|
||||
dsyslog("streamdev: Lost channel %d (%s) on device %d. Continuing on device %d.", m_Channel->Number(), m_Channel->Name(), m_Device->CardIndex(), dev->CardIndex());
|
||||
m_Device = dev;
|
||||
StartReceiver();
|
||||
}
|
||||
else {
|
||||
isyslog("streamdev: Lost channel %d (%s) on device %d.", m_Channel->Number(), m_Channel->Name(), m_Device->CardIndex());
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
if (m_SwitchLive) {
|
||||
// switched away live TV. Try previous channel on other device first
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_CHANNELS_READ;
|
||||
if (!Channels->SwitchTo(cDevice::CurrentChannel())) {
|
||||
// switch to streamdev channel otherwise
|
||||
Channels->SwitchTo(m_Channel->Number());
|
||||
#else
|
||||
if (!Channels.SwitchTo(cDevice::CurrentChannel())) {
|
||||
// switch to streamdev channel otherwise
|
||||
Channels.SwitchTo(m_Channel->Number());
|
||||
#endif
|
||||
Skins.Message(mtInfo, tr("Streaming active"));
|
||||
}
|
||||
if (m_Device)
|
||||
m_Device->SetOccupied(0);
|
||||
m_SwitchLive = false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string cStreamdevLiveStreamer::Report(void)
|
||||
{
|
||||
std::string result;
|
||||
@@ -615,107 +739,3 @@ std::string cStreamdevLiveStreamer::Report(void)
|
||||
result += "\n";
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- cStreamdevFilterStreamer -------------------------------------------------
|
||||
|
||||
cStreamdevFilterStreamer::cStreamdevFilterStreamer():
|
||||
cStreamdevStreamer("streamdev-filterstreaming"),
|
||||
m_Device(NULL),
|
||||
m_Filter(NULL)/*,
|
||||
m_Channel(NULL)*/
|
||||
{
|
||||
}
|
||||
|
||||
cStreamdevFilterStreamer::~cStreamdevFilterStreamer()
|
||||
{
|
||||
Dprintf("Desctructing Filter streamer\n");
|
||||
Detach();
|
||||
m_Device = NULL;
|
||||
DELETENULL(m_Filter);
|
||||
Stop();
|
||||
}
|
||||
|
||||
void cStreamdevFilterStreamer::Attach(void)
|
||||
{
|
||||
Dprintf("cStreamdevFilterStreamer::Attach()\n");
|
||||
LOCK_THREAD;
|
||||
if(m_Device && m_Filter)
|
||||
m_Device->AttachFilter(m_Filter);
|
||||
}
|
||||
|
||||
void cStreamdevFilterStreamer::Detach(void)
|
||||
{
|
||||
Dprintf("cStreamdevFilterStreamer::Detach()\n");
|
||||
LOCK_THREAD;
|
||||
if(m_Device && m_Filter)
|
||||
m_Device->Detach(m_Filter);
|
||||
}
|
||||
|
||||
#if 0
|
||||
void cStreamdevFilterStreamer::SetChannel(const cChannel *Channel)
|
||||
{
|
||||
LOCK_THREAD;
|
||||
Dprintf("cStreamdevFilterStreamer::SetChannel(%s : %s)", Channel?Channel->Name():"<null>",
|
||||
Channel ? *Channel->GetChannelID().ToString() : "");
|
||||
m_Channel = Channel;
|
||||
}
|
||||
#endif
|
||||
|
||||
void cStreamdevFilterStreamer::SetDevice(cDevice *Device)
|
||||
{
|
||||
Dprintf("cStreamdevFilterStreamer::SetDevice()\n");
|
||||
LOCK_THREAD;
|
||||
if(Device != m_Device) {
|
||||
Detach();
|
||||
m_Device = Device;
|
||||
//m_Channel = NULL;
|
||||
Attach();
|
||||
}
|
||||
}
|
||||
|
||||
bool cStreamdevFilterStreamer::SetFilter(u_short Pid, u_char Tid, u_char Mask, bool On)
|
||||
{
|
||||
Dprintf("cStreamdevFilterStreamer::SetFilter(%u,0x%x,0x%x,%s)\n", Pid, Tid, Mask, On?"On":"Off");
|
||||
|
||||
if(!m_Device)
|
||||
return false;
|
||||
|
||||
if (On) {
|
||||
if (m_Filter == NULL) {
|
||||
m_Filter = new cStreamdevLiveFilter(this);
|
||||
Dprintf("attaching filter to device\n");
|
||||
Attach();
|
||||
}
|
||||
m_Filter->Set(Pid, Tid, Mask);
|
||||
} else if (m_Filter != NULL)
|
||||
m_Filter->Del(Pid, Tid, Mask);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if 0
|
||||
void cStreamdevFilterStreamer::ChannelSwitch(const cDevice *Device, int ChannelNumber) {
|
||||
LOCK_THREAD;
|
||||
if(Device == m_Device) {
|
||||
if(ChannelNumber > 0) {
|
||||
cChannel *ch = Channels.GetByNumber(ChannelNumber);
|
||||
if(ch != NULL) {
|
||||
if(m_Filter != NULL &&
|
||||
m_Channel != NULL &&
|
||||
(! TRANSPONDER(ch, m_Channel))) {
|
||||
|
||||
isyslog("***** LiveFilterStreamer: transponder changed ! %s",
|
||||
*ch->GetChannelID().ToString());
|
||||
|
||||
uchar buffer[TS_SIZE] = {TS_SYNC_BYTE, 0xff, 0xff, 0xff, 0x7f, 0};
|
||||
strcpy((char*)(buffer + 5), ch->GetChannelID().ToString());
|
||||
int p = Put(buffer, TS_SIZE);
|
||||
if (p != TS_SIZE)
|
||||
ReportOverflow(TS_SIZE - p);
|
||||
}
|
||||
m_Channel = ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -2,85 +2,88 @@
|
||||
#define VDR_STREAMDEV_LIVESTREAMER_H
|
||||
|
||||
#include <vdr/config.h>
|
||||
#include <vdr/status.h>
|
||||
#include <vdr/receiver.h>
|
||||
|
||||
#include "server/streamer.h"
|
||||
#include "server/streamdev-server.h"
|
||||
#include "common.h"
|
||||
|
||||
class cTS2PSRemux;
|
||||
class cTS2ESRemux;
|
||||
class cExternRemux;
|
||||
class cRemux;
|
||||
#define LIVEBUFSIZE (20000 * TS_SIZE)
|
||||
|
||||
class cStreamdevPatFilter;
|
||||
class cStreamdevLiveReceiver;
|
||||
|
||||
// --- cStreamdevLiveStreamer -------------------------------------------------
|
||||
|
||||
class cStreamdevLiveStreamer: public cStreamdevStreamer {
|
||||
class cStreamdevLiveStreamer: public cStreamdevStreamer, public cMainThreadHookSubscriber
|
||||
#if VDRVERSNUM >= 20104
|
||||
, public cStatus
|
||||
#endif
|
||||
{
|
||||
private:
|
||||
int m_Priority;
|
||||
std::string m_Parameter;
|
||||
int m_Pids[MAXRECEIVEPIDS + 1];
|
||||
int m_NumPids;
|
||||
eStreamType m_StreamType;
|
||||
int m_Caids[MAXCAIDS + 1];
|
||||
const cChannel *m_Channel;
|
||||
cDevice *m_Device;
|
||||
cStreamdevLiveReceiver *m_Receiver;
|
||||
cStreamdevBuffer *m_ReceiveBuffer;
|
||||
cStreamdevPatFilter *m_PatFilter;
|
||||
cRemux *m_PESRemux;
|
||||
cTS2ESRemux *m_ESRemux;
|
||||
cTS2PSRemux *m_PSRemux;
|
||||
cExternRemux *m_ExtRemux;
|
||||
bool m_SwitchLive;
|
||||
|
||||
void StartReceiver(void);
|
||||
void StartReceiver(bool Force = false);
|
||||
bool HasPid(int Pid);
|
||||
|
||||
public:
|
||||
cStreamdevLiveStreamer(int Priority, std::string Parameter = "");
|
||||
virtual ~cStreamdevLiveStreamer();
|
||||
/* Test if device is in use as the transfer mode receiver device
|
||||
or a FF card, displaying live TV from internal tuner */
|
||||
static bool UsedByLiveTV(cDevice *device);
|
||||
|
||||
void SetDevice(cDevice *Device) { m_Device = Device; }
|
||||
bool SetPid(int Pid, bool On);
|
||||
bool SetPids(int Pid, const int *Pids1 = NULL, const int *Pids2 = NULL, const int *Pids3 = NULL);
|
||||
bool SetChannel(const cChannel *Channel, eStreamType StreamType, int Apid = 0);
|
||||
/* Find a suitable device and tune it to the requested channel. */
|
||||
cDevice *SwitchDevice(const cChannel *Channel, int Priority);
|
||||
|
||||
bool SetChannel(eStreamType StreamType, const int* Apid = NULL, const int* Dpid = NULL);
|
||||
|
||||
protected:
|
||||
virtual uchar* GetFromReceiver(int &Count) { return m_ReceiveBuffer->Get(Count); }
|
||||
virtual void DelFromReceiver(int Count) { m_ReceiveBuffer->Del(Count); }
|
||||
|
||||
virtual int Put(const uchar *Data, int Count);
|
||||
virtual uchar *Get(int &Count);
|
||||
virtual void Del(int Count);
|
||||
virtual void Action(void);
|
||||
|
||||
virtual void ChannelChange(const cChannel *Channel);
|
||||
|
||||
public:
|
||||
cStreamdevLiveStreamer(const cServerConnection *Connection, const cChannel *Channel, int Priority, eStreamType StreamType, const int* Apid = NULL, const int* Dpid = NULL);
|
||||
virtual ~cStreamdevLiveStreamer();
|
||||
|
||||
bool SetPid(int Pid, bool On);
|
||||
bool SetPids(int Pid, const int *Pids1 = NULL, const int *Pids2 = NULL, const int *Pids3 = NULL);
|
||||
void SetPriority(int Priority);
|
||||
void GetSignal(int *DevNum, int *Strength, int *Quality) const;
|
||||
virtual cString ToText() const;
|
||||
|
||||
#if APIVERSNUM >= 20300
|
||||
void Receive(const uchar *Data, int Length);
|
||||
#else
|
||||
void Receive(uchar *Data, int Length);
|
||||
#endif
|
||||
virtual bool IsReceiving(void) const;
|
||||
|
||||
virtual void Attach(void);
|
||||
virtual void Detach(void);
|
||||
|
||||
cDevice *GetDevice() const { return m_Device; }
|
||||
|
||||
/* Test if a call to GetDevice would return a usable device. */
|
||||
static bool ProvidesChannel(const cChannel *Channel, int Priority);
|
||||
|
||||
/* Do things which must be done in VDR's main loop */
|
||||
void MainThreadHook();
|
||||
|
||||
// Statistical purposes:
|
||||
virtual std::string Report(void);
|
||||
};
|
||||
|
||||
|
||||
// --- cStreamdevFilterStreamer -------------------------------------------------
|
||||
|
||||
//#include <vdr/status.h>
|
||||
|
||||
class cStreamdevLiveFilter;
|
||||
|
||||
class cStreamdevFilterStreamer: public cStreamdevStreamer /*, public cStatus*/ {
|
||||
private:
|
||||
cDevice *m_Device;
|
||||
cStreamdevLiveFilter *m_Filter;
|
||||
//const cChannel *m_Channel;
|
||||
|
||||
public:
|
||||
cStreamdevFilterStreamer();
|
||||
virtual ~cStreamdevFilterStreamer();
|
||||
|
||||
void SetDevice(cDevice *Device);
|
||||
//void SetChannel(const cChannel *Channel);
|
||||
bool SetFilter(u_short Pid, u_char Tid, u_char Mask, bool On);
|
||||
|
||||
virtual void Attach(void);
|
||||
virtual void Detach(void);
|
||||
|
||||
// cStatus message handlers
|
||||
//virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber);
|
||||
};
|
||||
|
||||
#endif // VDR_STREAMDEV_LIVESTREAMER_H
|
||||
|
||||
76
server/menu.c
Normal file
76
server/menu.c
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* $Id: menu.c,v 1.10 2010/07/19 13:49:31 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include <vdr/menuitems.h>
|
||||
#include <vdr/thread.h>
|
||||
#include <vdr/player.h>
|
||||
|
||||
#include "server/menu.h"
|
||||
#include "server/setup.h"
|
||||
#include "server/server.h"
|
||||
#include "server/suspend.h"
|
||||
|
||||
cStreamdevServerMenu::cStreamdevServerMenu(): cOsdMenu(tr("Streamdev Connections"), 4, 20) {
|
||||
cThreadLock lock;
|
||||
#if APIVERSNUM >= 20300
|
||||
cList<cServerConnection>& clients = cStreamdevServer::Clients(lock);
|
||||
#else
|
||||
const cList<cServerConnection>& clients = cStreamdevServer::Clients(lock);
|
||||
#endif
|
||||
for (cServerConnection *s = clients.First(); s; s = clients.Next(s))
|
||||
Add(new cOsdItem(s->ToText('\t')));
|
||||
SetHelpKeys();
|
||||
Display();
|
||||
}
|
||||
|
||||
cStreamdevServerMenu::~cStreamdevServerMenu() {
|
||||
}
|
||||
|
||||
void cStreamdevServerMenu::SetHelpKeys() {
|
||||
SetHelp(Count() ? tr("Disconnect") : NULL, NULL, NULL, tr("Suspend"));
|
||||
}
|
||||
|
||||
eOSState cStreamdevServerMenu::Disconnect() {
|
||||
cOsdItem *item = Get(Current());
|
||||
if (item) {
|
||||
cThreadLock lock;
|
||||
#if APIVERSNUM >= 20300
|
||||
cList<cServerConnection>& clients = cStreamdevServer::Clients(lock);
|
||||
#else
|
||||
const cList<cServerConnection>& clients = cStreamdevServer::Clients(lock);
|
||||
#endif
|
||||
const char *text = item->Text();
|
||||
for (cServerConnection *s = clients.First(); s; s = clients.Next(s)) {
|
||||
if (!strcmp(text, s->ToText('\t'))) {
|
||||
s->Close();
|
||||
Del(Current());
|
||||
SetHelpKeys();
|
||||
Display();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return osContinue;
|
||||
}
|
||||
|
||||
eOSState cStreamdevServerMenu::Suspend() {
|
||||
if (!cSuspendCtl::IsActive()) {
|
||||
cControl::Launch(new cSuspendCtl);
|
||||
return osBack;
|
||||
}
|
||||
return osContinue;
|
||||
}
|
||||
|
||||
eOSState cStreamdevServerMenu::ProcessKey(eKeys Key) {
|
||||
eOSState state = cOsdMenu::ProcessKey(Key);
|
||||
if (state == osUnknown) {
|
||||
switch (Key) {
|
||||
case kRed: return Disconnect();
|
||||
case kBlue: return Suspend();
|
||||
case kOk: return osBack;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
24
server/menu.h
Normal file
24
server/menu.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* $Id: menu.h,v 1.4 2010/07/19 13:49:31 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_MENU_H
|
||||
#define VDR_STREAMDEV_MENU_H
|
||||
|
||||
#include <vdr/osdbase.h>
|
||||
#include "connection.h"
|
||||
|
||||
class cStreamdevServerMenu: public cOsdMenu {
|
||||
private:
|
||||
void SetHelpKeys();
|
||||
eOSState Disconnect();
|
||||
eOSState Suspend();
|
||||
protected:
|
||||
virtual eOSState ProcessKey(eKeys Key);
|
||||
|
||||
public:
|
||||
cStreamdevServerMenu();
|
||||
virtual ~cStreamdevServerMenu();
|
||||
};
|
||||
|
||||
#endif // VDR_STREAMDEV_MENU_H
|
||||
@@ -1,123 +1,312 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <vdr/channels.h>
|
||||
#include "server/menuHTTP.h"
|
||||
|
||||
//**************************** cChannelIterator **************
|
||||
cChannelIterator::cChannelIterator(cChannel *First): channel(First)
|
||||
{}
|
||||
|
||||
const cChannel* cChannelIterator::Next()
|
||||
//**************************** cRecordingIterator **************
|
||||
#if APIVERSNUM >= 20300
|
||||
cRecordingsIterator::cRecordingsIterator(eStreamType StreamType)
|
||||
#else
|
||||
cRecordingsIterator::cRecordingsIterator(eStreamType StreamType): RecordingsLock(&Recordings)
|
||||
#endif
|
||||
{
|
||||
const cChannel *current = channel;
|
||||
channel = NextChannel(channel);
|
||||
streamType = StreamType;
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_RECORDINGS_READ;
|
||||
first = NextSuitable(Recordings->First());
|
||||
#else
|
||||
first = NextSuitable(Recordings.First());
|
||||
#endif
|
||||
current = NULL;
|
||||
}
|
||||
|
||||
const cRecording* cRecordingsIterator::NextSuitable(const cRecording *Recording)
|
||||
{
|
||||
while (Recording)
|
||||
{
|
||||
bool isPes = Recording->IsPesRecording();
|
||||
if (!isPes || (isPes && streamType == stPES))
|
||||
break;
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_RECORDINGS_READ;
|
||||
Recording = Recordings->Next(Recording);
|
||||
#else
|
||||
Recording = Recordings.Next(Recording);
|
||||
#endif
|
||||
}
|
||||
return Recording;
|
||||
}
|
||||
|
||||
bool cRecordingsIterator::Next()
|
||||
{
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_RECORDINGS_READ;
|
||||
#endif
|
||||
if (first)
|
||||
{
|
||||
current = first;
|
||||
first = NULL;
|
||||
}
|
||||
else
|
||||
#if APIVERSNUM >= 20300
|
||||
current = NextSuitable(Recordings->Next(current));
|
||||
#else
|
||||
current = NextSuitable(Recordings.Next(current));
|
||||
#endif
|
||||
return current;
|
||||
}
|
||||
|
||||
const cString cRecordingsIterator::ItemRessource() const
|
||||
{
|
||||
struct stat st;
|
||||
if (stat(current->FileName(), &st) == 0)
|
||||
return cString::sprintf("%lu:%llu.rec", (unsigned long) st.st_dev, (unsigned long long) st.st_ino);
|
||||
return "";
|
||||
}
|
||||
|
||||
//**************************** cChannelIterator **************
|
||||
cChannelIterator::cChannelIterator(const cChannel *First)
|
||||
{
|
||||
first = First;
|
||||
current = NULL;
|
||||
}
|
||||
|
||||
bool cChannelIterator::Next()
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
current = first;
|
||||
first = NULL;
|
||||
}
|
||||
else
|
||||
current = NextChannel(current);
|
||||
return current;
|
||||
}
|
||||
|
||||
const cString cChannelIterator::ItemId() const
|
||||
{
|
||||
if (current)
|
||||
{
|
||||
if (current->GroupSep())
|
||||
{
|
||||
int index = 0;
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_CHANNELS_READ;
|
||||
for (int curr = Channels->GetNextGroup(-1); curr >= 0; curr = Channels->GetNextGroup(curr))
|
||||
{
|
||||
if (Channels->Get(curr) == current)
|
||||
#else
|
||||
for (int curr = Channels.GetNextGroup(-1); curr >= 0; curr = Channels.GetNextGroup(curr))
|
||||
{
|
||||
if (Channels.Get(curr) == current)
|
||||
#endif
|
||||
return itoa(index);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return itoa(current->Number());
|
||||
}
|
||||
}
|
||||
return cString("-1");
|
||||
}
|
||||
|
||||
const cChannel* cChannelIterator::GetGroup(const char* GroupId)
|
||||
{
|
||||
int group = -1;
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_CHANNELS_READ;
|
||||
#endif
|
||||
if (GroupId)
|
||||
{
|
||||
int Index = atoi(GroupId);
|
||||
#if APIVERSNUM >= 20300
|
||||
group = Channels->GetNextGroup(-1);
|
||||
while (Index-- && group >= 0)
|
||||
group = Channels->GetNextGroup(group);
|
||||
}
|
||||
return group >= 0 ? Channels->Get(group) : NULL;
|
||||
#else
|
||||
group = Channels.GetNextGroup(-1);
|
||||
while (Index-- && group >= 0)
|
||||
group = Channels.GetNextGroup(group);
|
||||
}
|
||||
return group >= 0 ? Channels.Get(group) : NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
const cChannel* cChannelIterator::FirstChannel()
|
||||
{
|
||||
const cChannel *Channel;
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_CHANNELS_READ;
|
||||
Channel = Channels->First();
|
||||
#else
|
||||
Channel = Channels.First();
|
||||
#endif
|
||||
return Channel;
|
||||
}
|
||||
|
||||
const cChannel* cChannelIterator::NextNormal()
|
||||
{
|
||||
const cChannel *Channel;
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_CHANNELS_READ;
|
||||
Channel = Channels->Get(Channels->GetNextNormal(-1));
|
||||
#else
|
||||
Channel = Channels.Get(Channels.GetNextNormal(-1));
|
||||
#endif
|
||||
return Channel;
|
||||
}
|
||||
|
||||
const cChannel* cChannelIterator::NextGroup()
|
||||
{
|
||||
const cChannel *Channel;
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_CHANNELS_READ;
|
||||
Channel = Channels->Get(Channels->GetNextGroup(-1));
|
||||
#else
|
||||
Channel = Channels.Get(Channels.GetNextGroup(-1));
|
||||
#endif
|
||||
return Channel;
|
||||
}
|
||||
|
||||
//**************************** cListAll **************
|
||||
cListAll::cListAll(): cChannelIterator(Channels.First())
|
||||
cListAll::cListAll(): cChannelIterator(FirstChannel())
|
||||
{}
|
||||
|
||||
const cChannel* cListAll::NextChannel(const cChannel *Channel)
|
||||
{
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_CHANNELS_READ;
|
||||
if (Channel)
|
||||
Channel = Channels.Next(Channel);
|
||||
Channel = SkipFakeGroups(Channels->Next(Channel));
|
||||
#else
|
||||
if (Channel)
|
||||
Channel = SkipFakeGroups(Channels.Next(Channel));
|
||||
#endif
|
||||
return Channel;
|
||||
}
|
||||
|
||||
//**************************** cListChannels **************
|
||||
cListChannels::cListChannels(): cChannelIterator(Channels.Get(Channels.GetNextNormal(-1)))
|
||||
cListChannels::cListChannels(): cChannelIterator(NextNormal())
|
||||
{}
|
||||
|
||||
const cChannel* cListChannels::NextChannel(const cChannel *Channel)
|
||||
{
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_CHANNELS_READ;
|
||||
if (Channel)
|
||||
Channel = Channels->Get(Channels->GetNextNormal(Channel->Index()));
|
||||
#else
|
||||
if (Channel)
|
||||
Channel = Channels.Get(Channels.GetNextNormal(Channel->Index()));
|
||||
#endif
|
||||
return Channel;
|
||||
}
|
||||
|
||||
// ********************* cListGroups ****************
|
||||
cListGroups::cListGroups(): cChannelIterator(Channels.Get(Channels.GetNextGroup(-1)))
|
||||
cListGroups::cListGroups(): cChannelIterator(NextGroup())
|
||||
{}
|
||||
|
||||
const cChannel* cListGroups::NextChannel(const cChannel *Channel)
|
||||
{
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_CHANNELS_READ;
|
||||
if (Channel)
|
||||
Channel = Channels->Get(Channels->GetNextGroup(Channel->Index()));
|
||||
#else
|
||||
if (Channel)
|
||||
Channel = Channels.Get(Channels.GetNextGroup(Channel->Index()));
|
||||
#endif
|
||||
return Channel;
|
||||
}
|
||||
//
|
||||
// ********************* cListGroup ****************
|
||||
cListGroup::cListGroup(const cChannel *Group): cChannelIterator((Group && Group->GroupSep() && Channels.Next(Group) && !Channels.Next(Group)->GroupSep()) ? Channels.Next(Group) : NULL)
|
||||
cListGroup::cListGroup(const char *GroupId): cChannelIterator(GetNextChannelInGroup(GetGroup(GroupId)))
|
||||
{}
|
||||
|
||||
const cChannel* cListGroup::GetNextChannelInGroup(const cChannel *Channel)
|
||||
{
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_CHANNELS_READ;
|
||||
if (Channel)
|
||||
Channel = SkipFakeGroups(Channels->Next(Channel));
|
||||
#else
|
||||
if (Channel)
|
||||
Channel = SkipFakeGroups(Channels.Next(Channel));
|
||||
#endif
|
||||
return Channel && !Channel->GroupSep() ? Channel : NULL;
|
||||
}
|
||||
|
||||
const cChannel* cListGroup::NextChannel(const cChannel *Channel)
|
||||
{
|
||||
if (Channel)
|
||||
Channel = Channels.Next(Channel);
|
||||
return (Channel && !Channel->GroupSep()) ? Channel : NULL;
|
||||
return GetNextChannelInGroup(Channel);
|
||||
}
|
||||
//
|
||||
// ********************* cListTree ****************
|
||||
cListTree::cListTree(const cChannel *SelectedGroup): cChannelIterator(Channels.Get(Channels.GetNextGroup(-1)))
|
||||
cListTree::cListTree(const char *SelectedGroupId): cChannelIterator(NextGroup())
|
||||
{
|
||||
selectedGroup = SelectedGroup;
|
||||
selectedGroup = GetGroup(SelectedGroupId);
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_CHANNELS_READ;
|
||||
currentGroup = Channels->Get(Channels->GetNextGroup(-1));
|
||||
#else
|
||||
currentGroup = Channels.Get(Channels.GetNextGroup(-1));
|
||||
#endif
|
||||
}
|
||||
|
||||
const cChannel* cListTree::NextChannel(const cChannel *Channel)
|
||||
{
|
||||
if (currentGroup == selectedGroup)
|
||||
{
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_CHANNELS_READ;
|
||||
if (Channel)
|
||||
Channel = Channels.Next(Channel);
|
||||
Channel = SkipFakeGroups(Channels->Next(Channel));
|
||||
#else
|
||||
if (Channel)
|
||||
Channel = SkipFakeGroups(Channels.Next(Channel));
|
||||
#endif
|
||||
if (Channel && Channel->GroupSep())
|
||||
currentGroup = Channel;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_CHANNELS_READ;
|
||||
if (Channel)
|
||||
Channel = Channels->Get(Channels->GetNextGroup(Channel->Index()));
|
||||
#else
|
||||
if (Channel)
|
||||
Channel = Channels.Get(Channels.GetNextGroup(Channel->Index()));
|
||||
#endif
|
||||
currentGroup = Channel;
|
||||
}
|
||||
return Channel;
|
||||
}
|
||||
|
||||
// ******************** cChannelList ******************
|
||||
cChannelList::cChannelList(cChannelIterator *Iterator) : iterator(Iterator)
|
||||
// ******************** cMenuList ******************
|
||||
cMenuList::cMenuList(cItemIterator *Iterator) : iterator(Iterator)
|
||||
{}
|
||||
|
||||
cChannelList::~cChannelList()
|
||||
cMenuList::~cMenuList()
|
||||
{
|
||||
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;
|
||||
}
|
||||
// ******************** cHtmlMenuList ******************
|
||||
const char* cHtmlMenuList::menu =
|
||||
"[<a href=\"/\">Home</a> (<a href=\"all.html\" tvid=\"RED\">no script</a>)] "
|
||||
"[<a href=\"tree.html\" tvid=\"GREEN\">Tree View</a>] "
|
||||
"[<a href=\"groups.html\" tvid=\"YELLOW\">Groups</a> (<a href=\"groups.m3u\">Playlist</a> | <a href=\"groups.rss\">RSS</a>)] "
|
||||
"[<a href=\"channels.html\" tvid=\"BLUE\">Channels</a> (<a href=\"channels.m3u\">Playlist</a> | <a href=\"channels.rss\">RSS</a>)] "
|
||||
"[<a href=\"recordings.html\">Recordings</a> (<a href=\"recordings.m3u\">Playlist</a> | <a href=\"recordings.rss\">RSS</a>)] ";
|
||||
|
||||
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 =
|
||||
const char* cHtmlMenuList::css =
|
||||
"<style type=\"text/css\">\n"
|
||||
"<!--\n"
|
||||
"a:link, a:visited, a:hover, a:active, a:focus { color:#333399; }\n"
|
||||
@@ -133,7 +322,7 @@ const char* cHtmlChannelList::css =
|
||||
"-->\n"
|
||||
"</style>";
|
||||
|
||||
const char* cHtmlChannelList::js =
|
||||
const char* cHtmlMenuList::js =
|
||||
"<script language=\"JavaScript\">\n"
|
||||
"<!--\n"
|
||||
|
||||
@@ -194,43 +383,47 @@ const char* cHtmlChannelList::js =
|
||||
"</script>";
|
||||
|
||||
|
||||
std::string cHtmlChannelList::StreamTypeMenu()
|
||||
std::string cHtmlMenuList::StreamTypeMenu()
|
||||
{
|
||||
std::string typeMenu;
|
||||
typeMenu += (streamType == stTS ? (std::string) "[TS] " :
|
||||
(std::string) "[<a href=\"/TS/" + self + "\">TS</a>] ");
|
||||
#ifdef STREAMDEV_PS
|
||||
typeMenu += (streamType == stPS ? (std::string) "[PS] " :
|
||||
(std::string) "[<a href=\"/PS/" + self + "\">PS</a>] ");
|
||||
#endif
|
||||
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>] ");
|
||||
typeMenu += (streamType == stEXT ? (std::string) "[EXT] " :
|
||||
(std::string) "[<a href=\"/EXT/" + self + "\">EXT</a>] ");
|
||||
return typeMenu;
|
||||
}
|
||||
|
||||
cHtmlChannelList::cHtmlChannelList(cChannelIterator *Iterator, eStreamType StreamType, const char *Self, const char *GroupTarget): cChannelList(Iterator)
|
||||
cHtmlMenuList::cHtmlMenuList(cItemIterator *Iterator, eStreamType StreamType, const char *Self, const char *Rss, const char *GroupTarget): cMenuList(Iterator)
|
||||
{
|
||||
streamType = StreamType;
|
||||
self = strdup(Self);
|
||||
rss = strdup(Rss);
|
||||
groupTarget = (GroupTarget && *GroupTarget) ? strdup(GroupTarget) : NULL;
|
||||
htmlState = hsRoot;
|
||||
current = NULL;
|
||||
onItem = true;
|
||||
}
|
||||
|
||||
cHtmlChannelList::~cHtmlChannelList()
|
||||
cHtmlMenuList::~cHtmlMenuList()
|
||||
{
|
||||
free((void *) self);
|
||||
free((void *) rss);
|
||||
free((void *) groupTarget);
|
||||
}
|
||||
|
||||
bool cHtmlChannelList::HasNext()
|
||||
bool cHtmlMenuList::HasNext()
|
||||
{
|
||||
return htmlState != hsPageBottom;
|
||||
}
|
||||
|
||||
std::string cHtmlChannelList::Next()
|
||||
std::string cHtmlMenuList::Next()
|
||||
{
|
||||
switch (htmlState)
|
||||
{
|
||||
@@ -247,39 +440,39 @@ std::string cHtmlChannelList::Next()
|
||||
htmlState = hsPageTop;
|
||||
break;
|
||||
case hsPageTop:
|
||||
current = NextChannel();
|
||||
htmlState = current ? (current->GroupSep() ? hsGroupTop : hsPlainTop) : hsPageBottom;
|
||||
onItem = NextItem();
|
||||
htmlState = onItem ? (IsGroup() ? hsGroupTop : hsPlainTop) : hsPageBottom;
|
||||
break;
|
||||
case hsPlainTop:
|
||||
htmlState = hsPlainItem;
|
||||
break;
|
||||
case hsPlainItem:
|
||||
current = NextChannel();
|
||||
htmlState = current && !current->GroupSep() ? hsPlainItem : hsPlainBottom;
|
||||
onItem = NextItem();
|
||||
htmlState = onItem && !IsGroup() ? hsPlainItem : hsPlainBottom;
|
||||
break;
|
||||
case hsPlainBottom:
|
||||
htmlState = current ? hsGroupTop : hsPageBottom;
|
||||
htmlState = onItem ? hsGroupTop : hsPageBottom;
|
||||
break;
|
||||
case hsGroupTop:
|
||||
current = NextChannel();
|
||||
htmlState = current && !current->GroupSep() ? hsItemsTop : hsGroupBottom;
|
||||
onItem = NextItem();
|
||||
htmlState = onItem && !IsGroup() ? hsItemsTop : hsGroupBottom;
|
||||
break;
|
||||
case hsItemsTop:
|
||||
htmlState = hsItem;
|
||||
break;
|
||||
case hsItem:
|
||||
current = NextChannel();
|
||||
htmlState = current && !current->GroupSep() ? hsItem : hsItemsBottom;
|
||||
onItem = NextItem();
|
||||
htmlState = onItem && !IsGroup() ? hsItem : hsItemsBottom;
|
||||
break;
|
||||
case hsItemsBottom:
|
||||
htmlState = hsGroupBottom;
|
||||
break;
|
||||
case hsGroupBottom:
|
||||
htmlState = current ? hsGroupTop : hsPageBottom;
|
||||
htmlState = onItem ? hsGroupTop : hsPageBottom;
|
||||
break;
|
||||
case hsPageBottom:
|
||||
default:
|
||||
esyslog("streamdev-server cHtmlChannelList: invalid call to Next()");
|
||||
esyslog("streamdev-server cHtmlMenuList: invalid call to Next()");
|
||||
break;
|
||||
}
|
||||
switch (htmlState)
|
||||
@@ -304,86 +497,100 @@ std::string cHtmlChannelList::Next()
|
||||
}
|
||||
}
|
||||
|
||||
std::string cHtmlChannelList::HtmlHead()
|
||||
std::string cHtmlMenuList::HtmlHead()
|
||||
{
|
||||
return (std::string) "";
|
||||
return (std::string) "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"RSS\" href=\"" + rss + "\"/>";
|
||||
}
|
||||
|
||||
std::string cHtmlChannelList::PageTop()
|
||||
std::string cHtmlMenuList::PageTop()
|
||||
{
|
||||
return (std::string) "<div class=\"menu\"><div>" + menu + "</div><div>" + StreamTypeMenu() + "</div></div>";
|
||||
}
|
||||
|
||||
std::string cHtmlChannelList::PageBottom()
|
||||
std::string cHtmlMenuList::PageBottom()
|
||||
{
|
||||
return (std::string) "";
|
||||
}
|
||||
|
||||
std::string cHtmlChannelList::GroupTitle()
|
||||
std::string cHtmlMenuList::GroupTitle()
|
||||
{
|
||||
if (groupTarget)
|
||||
{
|
||||
return (std::string) "<a href=\"" + groupTarget + "?group=" +
|
||||
(const char*) itoa(cChannelList::GetGroupIndex(current)) +
|
||||
"\">" + current->Name() + "</a>";
|
||||
return (std::string) "<a href=\"" + groupTarget + "?group=" + (const char*) ItemId() + "\">" +
|
||||
ItemTitle() + "</a>";
|
||||
}
|
||||
else
|
||||
{
|
||||
return (std::string) current->Name();
|
||||
return (std::string) ItemTitle();
|
||||
}
|
||||
}
|
||||
|
||||
std::string cHtmlChannelList::ItemText()
|
||||
std::string cHtmlMenuList::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>";
|
||||
std::string suffix;
|
||||
|
||||
int count = 0;
|
||||
for (int i = 0; current->Apid(i) != 0; ++i, ++count)
|
||||
;
|
||||
for (int i = 0; current->Dpid(i) != 0; ++i, ++count)
|
||||
;
|
||||
switch (streamType) {
|
||||
case stTS: suffix = (std::string) ".ts"; break;
|
||||
#ifdef STREAMDEV_PS
|
||||
case stPS: suffix = (std::string) ".vob"; break;
|
||||
#endif
|
||||
// for Network Media Tank
|
||||
case stPES: suffix = (std::string) ".vdr"; break;
|
||||
default: suffix = "";
|
||||
}
|
||||
line += (std::string) "<li value=\"" + (const char*) ItemId() + "\">";
|
||||
line += (std::string) "<a href=\"" + (const char*) ItemRessource() + suffix + "\"";
|
||||
|
||||
if (count > 1)
|
||||
// for Network Media Tank
|
||||
line += (std::string) " vod ";
|
||||
if (strlen(ItemId()) < 4)
|
||||
line += (std::string) " tvid=\"" + (const char*) ItemId() + "\"";
|
||||
|
||||
line += (std::string) ">" + ItemTitle() + "</a>";
|
||||
|
||||
// TS always streams all PIDs
|
||||
if (streamType != stTS)
|
||||
{
|
||||
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>";
|
||||
}
|
||||
const char* lang;
|
||||
std::string pids;
|
||||
for (int i = 0; (lang = Alang(i)) != NULL; ++i, ++index) {
|
||||
pids += (std::string) " <a href=\"" + (const char*) ItemRessource() +
|
||||
"+" + (const char*)itoa(index) + suffix + "\" class=\"apid\" vod>" + (const char*) lang + "</a>";
|
||||
}
|
||||
for (int i = 0; (lang = Dlang(i)) != NULL; ++i, ++index) {
|
||||
pids += (std::string) " <a href=\"" + (const char*) ItemRessource() +
|
||||
"+" + (const char*)itoa(index) + suffix + "\" class=\"dpid\" vod>" + (const char*) lang + "</a>";
|
||||
}
|
||||
// always show audio PIDs for stES to select audio only
|
||||
if (index > 2 || streamType == stES)
|
||||
line += pids;
|
||||
}
|
||||
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
|
||||
// ******************** cM3uMenuList ******************
|
||||
cM3uMenuList::cM3uMenuList(cItemIterator *Iterator, const char* Base)
|
||||
: cMenuList(Iterator),
|
||||
m_IConv(cCharSetConv::SystemCharacterTable(), "UTF-8")
|
||||
{
|
||||
base = strdup(Base);
|
||||
m3uState = msFirst;
|
||||
}
|
||||
|
||||
cM3uChannelList::~cM3uChannelList()
|
||||
cM3uMenuList::~cM3uMenuList()
|
||||
{
|
||||
free(base);
|
||||
}
|
||||
|
||||
bool cM3uChannelList::HasNext()
|
||||
bool cM3uMenuList::HasNext()
|
||||
{
|
||||
return m3uState != msLast;
|
||||
}
|
||||
|
||||
std::string cM3uChannelList::Next()
|
||||
std::string cM3uMenuList::Next()
|
||||
{
|
||||
if (m3uState == msFirst)
|
||||
{
|
||||
@@ -391,30 +598,83 @@ std::string cM3uChannelList::Next()
|
||||
return "#EXTM3U";
|
||||
}
|
||||
|
||||
const cChannel *channel = NextChannel();
|
||||
if (!channel)
|
||||
if (!NextItem())
|
||||
{
|
||||
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
|
||||
std::string name = (std::string) m_IConv.Convert(ItemTitle());
|
||||
|
||||
if (channel->GroupSep())
|
||||
if (IsGroup())
|
||||
{
|
||||
return (std::string) "#EXTINF:0," + name + "\r\n" +
|
||||
base + "group.m3u?group=" +
|
||||
(const char*) itoa(cChannelList::GetGroupIndex(channel));
|
||||
return (std::string) "#EXTINF:-1," + name + "\r\n" +
|
||||
base + "group.m3u?group=" + (const char*) ItemId();
|
||||
}
|
||||
else
|
||||
{
|
||||
return (std::string) "#EXTINF:0," +
|
||||
(const char*) itoa(channel->Number()) + " " + name + "\r\n" +
|
||||
base + (std::string) channel->GetChannelID().ToString();
|
||||
return (std::string) "#EXTINF:-1," +
|
||||
(const char*) ItemId() + " " + name + "\r\n" +
|
||||
base + (const char*) ItemRessource();
|
||||
}
|
||||
}
|
||||
|
||||
// ******************** cRssMenuList ******************
|
||||
cRssMenuList::cRssMenuList(cItemIterator *Iterator, const char *Base, const char *Html)
|
||||
: cMenuList(Iterator),
|
||||
m_IConv(cCharSetConv::SystemCharacterTable(), "UTF-8")
|
||||
{
|
||||
base = strdup(Base);
|
||||
html = strdup(Html);
|
||||
rssState = msFirst;
|
||||
}
|
||||
|
||||
cRssMenuList::~cRssMenuList()
|
||||
{
|
||||
free(base);
|
||||
free(html);
|
||||
}
|
||||
|
||||
bool cRssMenuList::HasNext()
|
||||
{
|
||||
return rssState != msLast;
|
||||
}
|
||||
|
||||
std::string cRssMenuList::Next()
|
||||
{
|
||||
std::string type_ext;
|
||||
|
||||
if (rssState == msFirst)
|
||||
{
|
||||
rssState = msContinue;
|
||||
return (std::string) "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rss version=\"2.0\">\n\t<channel>\n"
|
||||
"\t\t<title>VDR</title>\n"
|
||||
"\t\t<link>" + base + html + "</link>\n"
|
||||
"\t\t<description>VDR channel list</description>\n"
|
||||
;
|
||||
}
|
||||
|
||||
if (!NextItem())
|
||||
{
|
||||
rssState = msLast;
|
||||
return "\t</channel>\n</rss>\n";
|
||||
}
|
||||
|
||||
std::string name = (std::string) m_IConv.Convert(ItemTitle());
|
||||
|
||||
if (IsGroup())
|
||||
{
|
||||
return (std::string) "\t\t<item>\n\t\t\t<title>" +
|
||||
name + "</title>\n\t\t\t<link>" +
|
||||
base + "group.rss?group=" + (const char*) ItemId() + "</link>\n\t\t</item>\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
return (std::string) "\t\t<item>\n\t\t\t<title>" +
|
||||
(const char*) ItemId() + " " + name + "</title>\n\t\t\t<link>" +
|
||||
base + (const char*) ItemRessource() + "</link>\n\t\t\t<enclosure url=\"" +
|
||||
base + (const char*) ItemRessource() + "\" type=\"video/mpeg\" />\n\t\t</item>\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3,19 +3,67 @@
|
||||
|
||||
#include <string>
|
||||
#include "../common.h"
|
||||
#include <vdr/recording.h>
|
||||
|
||||
class cChannel;
|
||||
|
||||
// ******************** cChannelIterator ******************
|
||||
class cChannelIterator
|
||||
// ******************** cItemIterator ******************
|
||||
class cItemIterator
|
||||
{
|
||||
public:
|
||||
virtual bool Next() = 0;
|
||||
virtual bool IsGroup() const = 0;
|
||||
virtual const cString ItemId() const = 0;
|
||||
virtual const char* ItemTitle() const = 0;
|
||||
virtual const cString ItemRessource() const = 0;
|
||||
virtual const char* Alang(int i) const = 0;
|
||||
virtual const char* Dlang(int i) const = 0;
|
||||
virtual ~cItemIterator() {};
|
||||
};
|
||||
|
||||
class cRecordingsIterator: public cItemIterator
|
||||
{
|
||||
private:
|
||||
const cChannel *channel;
|
||||
eStreamType streamType;
|
||||
const cRecording *first;
|
||||
const cRecording *current;
|
||||
cThreadLock RecordingsLock;
|
||||
protected:
|
||||
virtual const cChannel* NextChannel(const cChannel *Channel) = 0;
|
||||
virtual const cRecording* NextSuitable(const cRecording *Recording);
|
||||
public:
|
||||
const cChannel* Next();
|
||||
cChannelIterator(cChannel *First);
|
||||
virtual bool Next();
|
||||
virtual bool IsGroup() const { return false; }
|
||||
virtual const cString ItemId() const { return current ? itoa(current->Index() + 1) : "0"; }
|
||||
virtual const char* ItemTitle() const { return current ? current->Title() : ""; }
|
||||
virtual const cString ItemRessource() const;
|
||||
virtual const char* Alang(int i) const { return NULL; }
|
||||
virtual const char* Dlang(int i) const { return NULL; }
|
||||
cRecordingsIterator(eStreamType StreamType);
|
||||
virtual ~cRecordingsIterator() {};
|
||||
};
|
||||
|
||||
class cChannelIterator: public cItemIterator
|
||||
{
|
||||
private:
|
||||
const cChannel *first;
|
||||
const cChannel *current;
|
||||
protected:
|
||||
virtual const cChannel* FirstChannel();
|
||||
virtual const cChannel* NextNormal();
|
||||
virtual const cChannel* NextGroup();
|
||||
virtual const cChannel* NextChannel(const cChannel *Channel) = 0;
|
||||
static inline const cChannel* SkipFakeGroups(const cChannel *Channel);
|
||||
// Helper which returns the group by its index
|
||||
static const cChannel* GetGroup(const char* GroupId);
|
||||
public:
|
||||
virtual bool Next();
|
||||
virtual bool IsGroup() const { return current && current->GroupSep(); }
|
||||
virtual const cString ItemId() const;
|
||||
virtual const char* ItemTitle() const { return current ? current->Name() : ""; }
|
||||
virtual const cString ItemRessource() const { return (current ? current->GetChannelID() : tChannelID::InvalidID).ToString(); }
|
||||
virtual const char* Alang(int i) const { return current && current->Apid(i) ? current->Alang(i) : NULL; }
|
||||
virtual const char* Dlang(int i) const { return current && current->Dpid(i) ? current->Dlang(i) : NULL; }
|
||||
cChannelIterator(const cChannel *First);
|
||||
virtual ~cChannelIterator() {};
|
||||
};
|
||||
|
||||
@@ -48,10 +96,12 @@ class cListGroups: public cChannelIterator
|
||||
|
||||
class cListGroup: public cChannelIterator
|
||||
{
|
||||
private:
|
||||
static const cChannel* GetNextChannelInGroup(const cChannel *Channel);
|
||||
protected:
|
||||
virtual const cChannel* NextChannel(const cChannel *Channel);
|
||||
public:
|
||||
cListGroup(const cChannel *Group);
|
||||
cListGroup(const char *GroupId);
|
||||
virtual ~cListGroup() {};
|
||||
};
|
||||
|
||||
@@ -63,31 +113,32 @@ class cListTree: public cChannelIterator
|
||||
protected:
|
||||
virtual const cChannel* NextChannel(const cChannel *Channel);
|
||||
public:
|
||||
cListTree(const cChannel *SelectedGroup);
|
||||
cListTree(const char *SelectedGroupId);
|
||||
virtual ~cListTree() {};
|
||||
};
|
||||
|
||||
// ******************** cChannelList ******************
|
||||
class cChannelList
|
||||
// ******************** cMenuList ******************
|
||||
class cMenuList
|
||||
{
|
||||
private:
|
||||
cChannelIterator *iterator;
|
||||
cItemIterator *iterator;
|
||||
protected:
|
||||
const cChannel* NextChannel() { return iterator->Next(); }
|
||||
bool NextItem() { return iterator->Next(); }
|
||||
bool IsGroup() { return iterator->IsGroup(); }
|
||||
const cString ItemId() { return iterator->ItemId(); }
|
||||
const char* ItemTitle() { return iterator->ItemTitle(); }
|
||||
const cString ItemRessource() { return iterator->ItemRessource(); }
|
||||
const char* Alang(int i) { return iterator->Alang(i); }
|
||||
const char* Dlang(int i) { return iterator->Dlang(i); }
|
||||
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();
|
||||
cMenuList(cItemIterator *Iterator);
|
||||
virtual ~cMenuList();
|
||||
};
|
||||
|
||||
class cHtmlChannelList: public cChannelList
|
||||
class cHtmlMenuList: public cMenuList
|
||||
{
|
||||
private:
|
||||
static const char* menu;
|
||||
@@ -101,9 +152,10 @@ class cHtmlChannelList: public cChannelList
|
||||
hsItemsTop, hsItem, hsItemsBottom
|
||||
};
|
||||
eHtmlState htmlState;
|
||||
const cChannel *current;
|
||||
bool onItem;
|
||||
eStreamType streamType;
|
||||
const char* self;
|
||||
const char* rss;
|
||||
const char* groupTarget;
|
||||
|
||||
std::string StreamTypeMenu();
|
||||
@@ -113,28 +165,62 @@ class cHtmlChannelList: public cChannelList
|
||||
std::string ItemText();
|
||||
std::string PageBottom();
|
||||
public:
|
||||
virtual std::string HttpHeader() { return cChannelList::HttpHeader() + "Content-type: text/html\r\n\r\n"; }
|
||||
virtual std::string HttpHeader() {
|
||||
return cMenuList::HttpHeader()
|
||||
+ "Content-type: text/html; charset="
|
||||
+ (cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8")
|
||||
+ "\r\n";
|
||||
}
|
||||
virtual bool HasNext();
|
||||
virtual std::string Next();
|
||||
cHtmlChannelList(cChannelIterator *Iterator, eStreamType StreamType, const char *Self, const char *GroupTarget);
|
||||
virtual ~cHtmlChannelList();
|
||||
cHtmlMenuList(cItemIterator *Iterator, eStreamType StreamType, const char *Self, const char *Rss, const char *GroupTarget);
|
||||
virtual ~cHtmlMenuList();
|
||||
};
|
||||
|
||||
class cM3uChannelList: public cChannelList
|
||||
class cM3uMenuList: public cMenuList
|
||||
{
|
||||
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 std::string HttpHeader() { return cMenuList::HttpHeader() + "Content-type: audio/x-mpegurl; charset=UTF-8\r\n"; };
|
||||
virtual bool HasNext();
|
||||
virtual std::string Next();
|
||||
cM3uChannelList(cChannelIterator *Iterator, const char* Base);
|
||||
virtual ~cM3uChannelList();
|
||||
cM3uMenuList(cItemIterator *Iterator, const char* Base);
|
||||
virtual ~cM3uMenuList();
|
||||
};
|
||||
|
||||
class cRssMenuList: public cMenuList
|
||||
{
|
||||
private:
|
||||
char *base;
|
||||
char *html;
|
||||
enum eRssState { msFirst, msContinue, msLast };
|
||||
eRssState rssState;
|
||||
cCharSetConv m_IConv;
|
||||
public:
|
||||
virtual std::string HttpHeader() { return cMenuList::HttpHeader() + "Content-type: application/rss+xml\r\n"; };
|
||||
virtual bool HasNext();
|
||||
virtual std::string Next();
|
||||
|
||||
cRssMenuList(cItemIterator *Iterator, const char *Base, const char *Html);
|
||||
virtual ~cRssMenuList();
|
||||
};
|
||||
|
||||
inline const cChannel* cChannelIterator::SkipFakeGroups(const cChannel* Group)
|
||||
{
|
||||
#if APIVERSNUM >= 20300
|
||||
LOCK_CHANNELS_READ;
|
||||
#endif
|
||||
while (Group && Group->GroupSep() && !*Group->Name())
|
||||
#if APIVERSNUM >= 20300
|
||||
Group = Channels->Next(Group);
|
||||
#else
|
||||
Group = Channels.Next(Group);
|
||||
#endif
|
||||
return Group;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
95
server/po/de_DE.po
Normal file
95
server/po/de_DE.po
Normal file
@@ -0,0 +1,95 @@
|
||||
# VDR streamdev plugin language source file.
|
||||
# Copyright (C) 2008 streamdev development team. See http://streamdev.vdr-developer.org
|
||||
# This file is distributed under the same license as the VDR streamdev package.
|
||||
# Frank Schmirler <vdrdev@schmirler.de>, 2008
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: streamdev\n"
|
||||
"Report-Msgid-Bugs-To: <vdrdev@schmirler.de>\n"
|
||||
"POT-Creation-Date: 2014-05-05 22:46+0200\n"
|
||||
"PO-Revision-Date: 2008-03-30 02:11+0200\n"
|
||||
"Last-Translator: Frank Schmirler <vdrdev@schmirler.de>\n"
|
||||
"Language-Team: German <vdr@linuxtv.org>\n"
|
||||
"Language: de\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=ISO-8859-15\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "Streaming active"
|
||||
msgstr "Streamen im Gange"
|
||||
|
||||
msgid "Streamdev Connections"
|
||||
msgstr "Streamdev Verbindungen"
|
||||
|
||||
msgid "Disconnect"
|
||||
msgstr "Trennen"
|
||||
|
||||
msgid "Suspend"
|
||||
msgstr "Pausieren"
|
||||
|
||||
msgid "Common Settings"
|
||||
msgstr "Allgemeines"
|
||||
|
||||
msgid "Hide Mainmenu Entry"
|
||||
msgstr "Hauptmenüeintrag verstecken"
|
||||
|
||||
msgid "Start with Live TV suspended"
|
||||
msgstr "Live-TV beim Start pausieren"
|
||||
|
||||
msgid "Maximum Number of Clients"
|
||||
msgstr "Maximalanzahl an Clients"
|
||||
|
||||
msgid "Live TV buffer delay (ms)"
|
||||
msgstr "Live-TV Pufferdauer (ms)"
|
||||
|
||||
msgid "VDR-to-VDR Server"
|
||||
msgstr "VDR-zu-VDR Server"
|
||||
|
||||
msgid "Start VDR-to-VDR Server"
|
||||
msgstr "VDR-zu-VDR Server starten"
|
||||
|
||||
msgid "VDR-to-VDR Server Port"
|
||||
msgstr "Port des VDR-zu-VDR Servers"
|
||||
|
||||
msgid "Bind to IP"
|
||||
msgstr "Binde an IP"
|
||||
|
||||
msgid "Legacy Client Priority"
|
||||
msgstr "Priorität für alte Clients"
|
||||
|
||||
msgid "Client may suspend"
|
||||
msgstr "Client darf pausieren"
|
||||
|
||||
msgid "Loop Prevention"
|
||||
msgstr "Schleifen verhindern"
|
||||
|
||||
msgid "HTTP Server"
|
||||
msgstr "HTTP Server"
|
||||
|
||||
msgid "Start HTTP Server"
|
||||
msgstr "HTTP Server starten"
|
||||
|
||||
msgid "HTTP Server Port"
|
||||
msgstr "Port des HTTP Servers"
|
||||
|
||||
msgid "Priority"
|
||||
msgstr "Priorität"
|
||||
|
||||
msgid "HTTP Streamtype"
|
||||
msgstr "HTTP Streamtyp"
|
||||
|
||||
msgid "Multicast Streaming Server"
|
||||
msgstr "Multicast Streaming Server"
|
||||
|
||||
msgid "Start IGMP Server"
|
||||
msgstr "IGMP Server starten"
|
||||
|
||||
msgid "Multicast Client Port"
|
||||
msgstr "Port des Multicast Clients"
|
||||
|
||||
msgid "Multicast Streamtype"
|
||||
msgstr "Multicast Streamtyp"
|
||||
|
||||
msgid "VDR Streaming Server"
|
||||
msgstr "VDR Streaming Server"
|
||||
95
server/po/es_ES.po
Normal file
95
server/po/es_ES.po
Normal file
@@ -0,0 +1,95 @@
|
||||
# VDR streamdev plugin language source file.
|
||||
# Copyright (C) 2008 streamdev development team. See http://streamdev.vdr-developer.org
|
||||
# This file is distributed under the same license as the VDR streamdev package.
|
||||
# Javier Bradineras <jbradi@hotmail.com>, 2011
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: streamdev 0.5.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdrdev@schmirler.de>\n"
|
||||
"POT-Creation-Date: 2014-05-05 22:46+0200\n"
|
||||
"PO-Revision-Date: 2010-06-19 03:58+0100\n"
|
||||
"Last-Translator: Javier Bradineras <jbradi@hotmail.com>\n"
|
||||
"Language-Team: Spanish <vdr@linuxtv.org>\n"
|
||||
"Language: es\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=ISO-8859-15\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "Streaming active"
|
||||
msgstr "Trasmisión activa"
|
||||
|
||||
msgid "Streamdev Connections"
|
||||
msgstr ""
|
||||
|
||||
msgid "Disconnect"
|
||||
msgstr ""
|
||||
|
||||
msgid "Suspend"
|
||||
msgstr "Suspender"
|
||||
|
||||
msgid "Common Settings"
|
||||
msgstr "Configuración común"
|
||||
|
||||
msgid "Hide Mainmenu Entry"
|
||||
msgstr ""
|
||||
|
||||
msgid "Start with Live TV suspended"
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum Number of Clients"
|
||||
msgstr "Numero máximo de clientes"
|
||||
|
||||
msgid "Live TV buffer delay (ms)"
|
||||
msgstr ""
|
||||
|
||||
msgid "VDR-to-VDR Server"
|
||||
msgstr "Servidor VDR-a-VDR"
|
||||
|
||||
msgid "Start VDR-to-VDR Server"
|
||||
msgstr "Iniciar Servidor VDR-a-VDR"
|
||||
|
||||
msgid "VDR-to-VDR Server Port"
|
||||
msgstr "Puerto del Servidor VDR-a-VDR"
|
||||
|
||||
msgid "Bind to IP"
|
||||
msgstr "IP asociada"
|
||||
|
||||
msgid "Legacy Client Priority"
|
||||
msgstr ""
|
||||
|
||||
msgid "Client may suspend"
|
||||
msgstr "Permitir suspender al cliente"
|
||||
|
||||
msgid "Loop Prevention"
|
||||
msgstr ""
|
||||
|
||||
msgid "HTTP Server"
|
||||
msgstr "Servidor HTTP"
|
||||
|
||||
msgid "Start HTTP Server"
|
||||
msgstr "Iniciar Servidor HTTP"
|
||||
|
||||
msgid "HTTP Server Port"
|
||||
msgstr "Puerto del Servidor HTTP"
|
||||
|
||||
msgid "Priority"
|
||||
msgstr ""
|
||||
|
||||
msgid "HTTP Streamtype"
|
||||
msgstr "Tipo de flujo HTTP"
|
||||
|
||||
msgid "Multicast Streaming Server"
|
||||
msgstr "Servidor de transmisión Multicast"
|
||||
|
||||
msgid "Start IGMP Server"
|
||||
msgstr "Iniciar Servidor IGMP"
|
||||
|
||||
msgid "Multicast Client Port"
|
||||
msgstr "Puerto del Cliente Multicast"
|
||||
|
||||
msgid "Multicast Streamtype"
|
||||
msgstr "Tipo de flujo Multicast"
|
||||
|
||||
msgid "VDR Streaming Server"
|
||||
msgstr "Servidor de transmisiones del VDR"
|
||||
95
server/po/fi_FI.po
Normal file
95
server/po/fi_FI.po
Normal file
@@ -0,0 +1,95 @@
|
||||
# VDR streamdev plugin language source file.
|
||||
# Copyright (C) 2008 streamdev development team. See http://streamdev.vdr-developer.org
|
||||
# This file is distributed under the same license as the VDR streamdev package.
|
||||
# Rolf Ahrenberg, 2008-
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: streamdev 0.5.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdrdev@schmirler.de>\n"
|
||||
"POT-Creation-Date: 2014-05-05 22:46+0200\n"
|
||||
"PO-Revision-Date: 2008-03-30 02:11+0200\n"
|
||||
"Last-Translator: Rolf Ahrenberg\n"
|
||||
"Language-Team: Finnish <vdr@linuxtv.org>\n"
|
||||
"Language: fi\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "Streaming active"
|
||||
msgstr "Suoratoistopalvelin aktiivinen"
|
||||
|
||||
msgid "Streamdev Connections"
|
||||
msgstr "Suoratoistoyhteydet"
|
||||
|
||||
msgid "Disconnect"
|
||||
msgstr "Katkaise"
|
||||
|
||||
msgid "Suspend"
|
||||
msgstr "Pysäytä"
|
||||
|
||||
msgid "Common Settings"
|
||||
msgstr "Yleiset asetukset"
|
||||
|
||||
msgid "Hide Mainmenu Entry"
|
||||
msgstr "Piilota valinta päävalikosta"
|
||||
|
||||
msgid "Start with Live TV suspended"
|
||||
msgstr "Käynnistä Live-katselu pysäytettynä"
|
||||
|
||||
msgid "Maximum Number of Clients"
|
||||
msgstr "Suurin sallittu asiakkaiden määrä"
|
||||
|
||||
msgid "Live TV buffer delay (ms)"
|
||||
msgstr ""
|
||||
|
||||
msgid "VDR-to-VDR Server"
|
||||
msgstr "VDR-palvelin"
|
||||
|
||||
msgid "Start VDR-to-VDR Server"
|
||||
msgstr "Käynnistä VDR-palvelin"
|
||||
|
||||
msgid "VDR-to-VDR Server Port"
|
||||
msgstr "VDR-palvelimen portti"
|
||||
|
||||
msgid "Bind to IP"
|
||||
msgstr "Sido osoitteeseen"
|
||||
|
||||
msgid "Legacy Client Priority"
|
||||
msgstr "Legacy-asiakkaan prioriteetti"
|
||||
|
||||
msgid "Client may suspend"
|
||||
msgstr "Asiakas saa pysäyttää palvelimen"
|
||||
|
||||
msgid "Loop Prevention"
|
||||
msgstr "Estä asiakaslaitesilmukat"
|
||||
|
||||
msgid "HTTP Server"
|
||||
msgstr "HTTP-palvelin"
|
||||
|
||||
msgid "Start HTTP Server"
|
||||
msgstr "Käynnistä HTTP-palvelin"
|
||||
|
||||
msgid "HTTP Server Port"
|
||||
msgstr "HTTP-palvelimen portti"
|
||||
|
||||
msgid "Priority"
|
||||
msgstr "Prioriteetti"
|
||||
|
||||
msgid "HTTP Streamtype"
|
||||
msgstr "HTTP-lähetysmuoto"
|
||||
|
||||
msgid "Multicast Streaming Server"
|
||||
msgstr "Multicast-suoratoistopalvelin"
|
||||
|
||||
msgid "Start IGMP Server"
|
||||
msgstr "Käynnistä IGMP-palvelin"
|
||||
|
||||
msgid "Multicast Client Port"
|
||||
msgstr "Multicast-portti"
|
||||
|
||||
msgid "Multicast Streamtype"
|
||||
msgstr "Multicast-lähetysmuoto"
|
||||
|
||||
msgid "VDR Streaming Server"
|
||||
msgstr "VDR-suoratoistopalvelin"
|
||||
95
server/po/fr_FR.po
Normal file
95
server/po/fr_FR.po
Normal file
@@ -0,0 +1,95 @@
|
||||
# VDR streamdev plugin language source file.
|
||||
# Copyright (C) 2008 streamdev development team. See http://streamdev.vdr-developer.org
|
||||
# This file is distributed under the same license as the VDR streamdev package.
|
||||
# Frank Schmirler <vdrdev@schmirler.de>, 2008
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: streamdev 0.5.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdrdev@schmirler.de>\n"
|
||||
"POT-Creation-Date: 2014-05-05 22:46+0200\n"
|
||||
"PO-Revision-Date: 2008-03-30 02:11+0200\n"
|
||||
"Last-Translator: micky979 <micky979@free.fr>\n"
|
||||
"Language-Team: French <vdr@linuxtv.org>\n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=ISO-8859-15\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "Streaming active"
|
||||
msgstr "Streaming actif"
|
||||
|
||||
msgid "Streamdev Connections"
|
||||
msgstr ""
|
||||
|
||||
msgid "Disconnect"
|
||||
msgstr ""
|
||||
|
||||
msgid "Suspend"
|
||||
msgstr "Suspendre"
|
||||
|
||||
msgid "Common Settings"
|
||||
msgstr "Paramètres communs"
|
||||
|
||||
msgid "Hide Mainmenu Entry"
|
||||
msgstr ""
|
||||
|
||||
msgid "Start with Live TV suspended"
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum Number of Clients"
|
||||
msgstr "Nombre maximun de clients"
|
||||
|
||||
msgid "Live TV buffer delay (ms)"
|
||||
msgstr ""
|
||||
|
||||
msgid "VDR-to-VDR Server"
|
||||
msgstr "VDR-to-VDR Serveur"
|
||||
|
||||
msgid "Start VDR-to-VDR Server"
|
||||
msgstr "Démarrer le serveur VDR-to-VDR"
|
||||
|
||||
msgid "VDR-to-VDR Server Port"
|
||||
msgstr "Port du serveur VDR-to-VDR"
|
||||
|
||||
msgid "Bind to IP"
|
||||
msgstr "Attacher aux IP"
|
||||
|
||||
msgid "Legacy Client Priority"
|
||||
msgstr ""
|
||||
|
||||
msgid "Client may suspend"
|
||||
msgstr "Le client peut suspendre"
|
||||
|
||||
msgid "Loop Prevention"
|
||||
msgstr ""
|
||||
|
||||
msgid "HTTP Server"
|
||||
msgstr "Serveur HTTP"
|
||||
|
||||
msgid "Start HTTP Server"
|
||||
msgstr "Démarrer le serveur HTTP"
|
||||
|
||||
msgid "HTTP Server Port"
|
||||
msgstr "Port du serveur HTTP"
|
||||
|
||||
msgid "Priority"
|
||||
msgstr ""
|
||||
|
||||
msgid "HTTP Streamtype"
|
||||
msgstr "Type de Streaming HTTP"
|
||||
|
||||
msgid "Multicast Streaming Server"
|
||||
msgstr ""
|
||||
|
||||
msgid "Start IGMP Server"
|
||||
msgstr ""
|
||||
|
||||
msgid "Multicast Client Port"
|
||||
msgstr ""
|
||||
|
||||
msgid "Multicast Streamtype"
|
||||
msgstr ""
|
||||
|
||||
msgid "VDR Streaming Server"
|
||||
msgstr "Serveur de streaming VDR"
|
||||
97
server/po/it_IT.po
Normal file
97
server/po/it_IT.po
Normal file
@@ -0,0 +1,97 @@
|
||||
# VDR streamdev plugin language source file.
|
||||
# Copyright (C) 2008 streamdev development team. See http://streamdev.vdr-developer.org
|
||||
# This file is distributed under the same license as the VDR streamdev package.
|
||||
# Alberto Carraro <bertocar@tin.it>, 2001
|
||||
# Antonio Ospite <ospite@studenti.unina.it>, 2003
|
||||
# Sean Carlos <seanc@libero.it>, 2005
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: streamdev 0.5.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdrdev@schmirler.de>\n"
|
||||
"POT-Creation-Date: 2014-05-05 22:46+0200\n"
|
||||
"PO-Revision-Date: 2012-06-12 19:57+0100\n"
|
||||
"Last-Translator: Diego Pierotto <vdr-italian@tiscali.it>\n"
|
||||
"Language-Team: Italian <vdr@linuxtv.org>\n"
|
||||
"Language: it\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "Streaming active"
|
||||
msgstr "Trasmissione attiva"
|
||||
|
||||
msgid "Streamdev Connections"
|
||||
msgstr "Connessioni Streamdev"
|
||||
|
||||
msgid "Disconnect"
|
||||
msgstr "Disconnetti"
|
||||
|
||||
msgid "Suspend"
|
||||
msgstr "Sospendi"
|
||||
|
||||
msgid "Common Settings"
|
||||
msgstr "Impostazioni comuni"
|
||||
|
||||
msgid "Hide Mainmenu Entry"
|
||||
msgstr "Nascondi voce menu principale"
|
||||
|
||||
msgid "Start with Live TV suspended"
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum Number of Clients"
|
||||
msgstr "Numero massimo di Client"
|
||||
|
||||
msgid "Live TV buffer delay (ms)"
|
||||
msgstr ""
|
||||
|
||||
msgid "VDR-to-VDR Server"
|
||||
msgstr "Server VDR-a-VDR"
|
||||
|
||||
msgid "Start VDR-to-VDR Server"
|
||||
msgstr "Avvia Server VDR-a-VDR"
|
||||
|
||||
msgid "VDR-to-VDR Server Port"
|
||||
msgstr "Porta Server VDR-a-VDR"
|
||||
|
||||
msgid "Bind to IP"
|
||||
msgstr "IP associati"
|
||||
|
||||
msgid "Legacy Client Priority"
|
||||
msgstr "Priorità nativa client"
|
||||
|
||||
msgid "Client may suspend"
|
||||
msgstr "Permetti sospensione al Client"
|
||||
|
||||
msgid "Loop Prevention"
|
||||
msgstr "Evita ciclo"
|
||||
|
||||
msgid "HTTP Server"
|
||||
msgstr "Server HTTP"
|
||||
|
||||
msgid "Start HTTP Server"
|
||||
msgstr "Avvia Server HTTP"
|
||||
|
||||
msgid "HTTP Server Port"
|
||||
msgstr "Porta Server HTTP"
|
||||
|
||||
msgid "Priority"
|
||||
msgstr "Priorità"
|
||||
|
||||
msgid "HTTP Streamtype"
|
||||
msgstr "Tipo flusso HTTP"
|
||||
|
||||
msgid "Multicast Streaming Server"
|
||||
msgstr "Server trasmissione Multicast"
|
||||
|
||||
msgid "Start IGMP Server"
|
||||
msgstr "Avvia Server IGMP"
|
||||
|
||||
msgid "Multicast Client Port"
|
||||
msgstr "Porta Client Multicast"
|
||||
|
||||
msgid "Multicast Streamtype"
|
||||
msgstr "Tipo flusso Multicast"
|
||||
|
||||
msgid "VDR Streaming Server"
|
||||
msgstr "Server trasmissione VDR"
|
||||
95
server/po/lt_LT.po
Normal file
95
server/po/lt_LT.po
Normal file
@@ -0,0 +1,95 @@
|
||||
# VDR streamdev plugin language source file.
|
||||
# Copyright (C) 2008 streamdev development team. See http://streamdev.vdr-developer.org
|
||||
# This file is distributed under the same license as the VDR streamdev package.
|
||||
# Frank Schmirler <vdrdev@schmirler.de>, 2008
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: streamdev 0.5.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdrdev@schmirler.de>\n"
|
||||
"POT-Creation-Date: 2014-05-05 22:46+0200\n"
|
||||
"PO-Revision-Date: 2009-11-26 21:57+0200\n"
|
||||
"Last-Translator: Valdemaras Pipiras <varas@ambernet.lt>\n"
|
||||
"Language-Team: Lithuanian <vdr@linuxtv.org>\n"
|
||||
"Language: lt\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "Streaming active"
|
||||
msgstr "Transliavimas vyksta"
|
||||
|
||||
msgid "Streamdev Connections"
|
||||
msgstr ""
|
||||
|
||||
msgid "Disconnect"
|
||||
msgstr ""
|
||||
|
||||
msgid "Suspend"
|
||||
msgstr "Pristabdyti"
|
||||
|
||||
msgid "Common Settings"
|
||||
msgstr "Bendri nustatymai"
|
||||
|
||||
msgid "Hide Mainmenu Entry"
|
||||
msgstr ""
|
||||
|
||||
msgid "Start with Live TV suspended"
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum Number of Clients"
|
||||
msgstr "Maksimalus klientų skaičius"
|
||||
|
||||
msgid "Live TV buffer delay (ms)"
|
||||
msgstr ""
|
||||
|
||||
msgid "VDR-to-VDR Server"
|
||||
msgstr "VDR-su-VDR Serveris"
|
||||
|
||||
msgid "Start VDR-to-VDR Server"
|
||||
msgstr "Paleisti VDR-su-VDR serverį"
|
||||
|
||||
msgid "VDR-to-VDR Server Port"
|
||||
msgstr "VDR-su-VDR Serverio portas"
|
||||
|
||||
msgid "Bind to IP"
|
||||
msgstr "Pririšti IP"
|
||||
|
||||
msgid "Legacy Client Priority"
|
||||
msgstr ""
|
||||
|
||||
msgid "Client may suspend"
|
||||
msgstr "Klientas gali pristabdyti"
|
||||
|
||||
msgid "Loop Prevention"
|
||||
msgstr ""
|
||||
|
||||
msgid "HTTP Server"
|
||||
msgstr "HTTP Serveris"
|
||||
|
||||
msgid "Start HTTP Server"
|
||||
msgstr "Paleisti HTTP serverį"
|
||||
|
||||
msgid "HTTP Server Port"
|
||||
msgstr "HTTP serverio portas"
|
||||
|
||||
msgid "Priority"
|
||||
msgstr ""
|
||||
|
||||
msgid "HTTP Streamtype"
|
||||
msgstr "HTTP transliavimo tipas"
|
||||
|
||||
msgid "Multicast Streaming Server"
|
||||
msgstr "Multicast transliavimo serveris"
|
||||
|
||||
msgid "Start IGMP Server"
|
||||
msgstr "Paleisti IGMP serverį"
|
||||
|
||||
msgid "Multicast Client Port"
|
||||
msgstr "Multicast kliento portas"
|
||||
|
||||
msgid "Multicast Streamtype"
|
||||
msgstr "Multicast transliavimo tipas"
|
||||
|
||||
msgid "VDR Streaming Server"
|
||||
msgstr "VDR transliavimo serveris"
|
||||
96
server/po/pl_PL.po
Normal file
96
server/po/pl_PL.po
Normal file
@@ -0,0 +1,96 @@
|
||||
# VDR streamdev plugin language source file.
|
||||
# Copyright (C) 2008 streamdev development team. See
|
||||
# This file is distributed under the same license as the VDR streamdev package.
|
||||
# Tomasz Maciej Nowak, 2014
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: vdr-streamdev-server 0.6.1-git\n"
|
||||
"Report-Msgid-Bugs-To: <vdrdev@schmirler.de>\n"
|
||||
"POT-Creation-Date: 2014-11-20 14:11+0100\n"
|
||||
"PO-Revision-Date: 2014-11-24 18:02+0100\n"
|
||||
"Last-Translator: Tomasz Maciej Nowak <tomek_n@o2.pl>\n"
|
||||
"Language-Team: Polish <vdr@linuxtv.org>\n"
|
||||
"Language: pl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=iso-8859-2\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 1.6.10\n"
|
||||
|
||||
msgid "Streaming active"
|
||||
msgstr "Strumieniowanie aktywne"
|
||||
|
||||
msgid "Streamdev Connections"
|
||||
msgstr "Po³±czenia Streamdev"
|
||||
|
||||
msgid "Disconnect"
|
||||
msgstr "Roz³±cz"
|
||||
|
||||
msgid "Suspend"
|
||||
msgstr "Wstrzymaj"
|
||||
|
||||
msgid "Common Settings"
|
||||
msgstr "Ustawienia ogólne"
|
||||
|
||||
msgid "Hide Mainmenu Entry"
|
||||
msgstr "Ukryj pozycjê w g³ównym menu"
|
||||
|
||||
msgid "Start with Live TV suspended"
|
||||
msgstr "Uruchom ze wstrzyman± telewizj±"
|
||||
|
||||
msgid "Maximum Number of Clients"
|
||||
msgstr "Maksymalna liczba klientów"
|
||||
|
||||
msgid "Live TV buffer delay (ms)"
|
||||
msgstr "Bufor opó¼nieñ telewizji (ms)"
|
||||
|
||||
msgid "VDR-to-VDR Server"
|
||||
msgstr "Serwer VDR do VDR"
|
||||
|
||||
msgid "Start VDR-to-VDR Server"
|
||||
msgstr "Uruchom serwer VDR do VDR"
|
||||
|
||||
msgid "VDR-to-VDR Server Port"
|
||||
msgstr "Port serwera VDR do VDR"
|
||||
|
||||
msgid "Bind to IP"
|
||||
msgstr "Przypisz do adresu"
|
||||
|
||||
msgid "Legacy Client Priority"
|
||||
msgstr "Priorytet dla starego klienta"
|
||||
|
||||
msgid "Client may suspend"
|
||||
msgstr "Klient mo¿e wstrzymaæ"
|
||||
|
||||
msgid "Loop Prevention"
|
||||
msgstr "Zapobieganie pêtli"
|
||||
|
||||
msgid "HTTP Server"
|
||||
msgstr "Serwer HTTP"
|
||||
|
||||
msgid "Start HTTP Server"
|
||||
msgstr "Uruchom serwer HTTP"
|
||||
|
||||
msgid "HTTP Server Port"
|
||||
msgstr "Port serwera HTTP"
|
||||
|
||||
msgid "Priority"
|
||||
msgstr "Priorytet"
|
||||
|
||||
msgid "HTTP Streamtype"
|
||||
msgstr "Typ strumienia HTTP"
|
||||
|
||||
msgid "Multicast Streaming Server"
|
||||
msgstr "Serwer strumienia Multicast"
|
||||
|
||||
msgid "Start IGMP Server"
|
||||
msgstr "Uruchom serwer Multicast"
|
||||
|
||||
msgid "Multicast Client Port"
|
||||
msgstr "Port klienta Multicast"
|
||||
|
||||
msgid "Multicast Streamtype"
|
||||
msgstr "Typ strumienia Multicast"
|
||||
|
||||
msgid "VDR Streaming Server"
|
||||
msgstr "Serwer strumieniuj±cy VDR"
|
||||
95
server/po/ru_RU.po
Normal file
95
server/po/ru_RU.po
Normal file
@@ -0,0 +1,95 @@
|
||||
# VDR streamdev plugin language source file.
|
||||
# Copyright (C) 2008 streamdev development team. See http://streamdev.vdr-developer.org
|
||||
# This file is distributed under the same license as the VDR streamdev package.
|
||||
# Frank Schmirler <vdrdev@schmirler.de>, 2008
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: streamdev 0.5.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdrdev@schmirler.de>\n"
|
||||
"POT-Creation-Date: 2014-05-05 22:46+0200\n"
|
||||
"PO-Revision-Date: 2008-06-26 15:36+0100\n"
|
||||
"Last-Translator: Oleg Roitburd <oleg@roitburd.de>\n"
|
||||
"Language-Team: Russian <vdr@linuxtv.org>\n"
|
||||
"Language: ru\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=ISO-8859-5\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "Streaming active"
|
||||
msgstr "ÁâàØÜØÝÓ ÐÚâØÒÕÝ"
|
||||
|
||||
msgid "Streamdev Connections"
|
||||
msgstr ""
|
||||
|
||||
msgid "Disconnect"
|
||||
msgstr ""
|
||||
|
||||
msgid "Suspend"
|
||||
msgstr "¾áâÐÝÞÒÚÐ"
|
||||
|
||||
msgid "Common Settings"
|
||||
msgstr "½ÐáâàÞÙÚØ"
|
||||
|
||||
msgid "Hide Mainmenu Entry"
|
||||
msgstr ""
|
||||
|
||||
msgid "Start with Live TV suspended"
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum Number of Clients"
|
||||
msgstr "¼ÐÚá. ÚÞÛØçÕáâÒÞ ÚÛØÕÝâÞÒ"
|
||||
|
||||
msgid "Live TV buffer delay (ms)"
|
||||
msgstr ""
|
||||
|
||||
msgid "VDR-to-VDR Server"
|
||||
msgstr "VDR-to-VDR áÕàÒÕà"
|
||||
|
||||
msgid "Start VDR-to-VDR Server"
|
||||
msgstr "ÁâÐàâ VDR-to-VDR áÕàÒÕà"
|
||||
|
||||
msgid "VDR-to-VDR Server Port"
|
||||
msgstr "VDR-to-VDR ßÞàâ áÕàÒÕàÐ"
|
||||
|
||||
msgid "Bind to IP"
|
||||
msgstr "¿àØáÞÕÔØÝØâìáï Ú IP"
|
||||
|
||||
msgid "Legacy Client Priority"
|
||||
msgstr ""
|
||||
|
||||
msgid "Client may suspend"
|
||||
msgstr "ºÛØÕÝâ ÜÞÖÕâ ÞáâÐÝÐÒÛØÒÐâì"
|
||||
|
||||
msgid "Loop Prevention"
|
||||
msgstr ""
|
||||
|
||||
msgid "HTTP Server"
|
||||
msgstr "HTTP áÕàÒÕà"
|
||||
|
||||
msgid "Start HTTP Server"
|
||||
msgstr "ÁâÐàâ HTTP áÕàÒÕàÐ"
|
||||
|
||||
msgid "HTTP Server Port"
|
||||
msgstr "HTTP áÕàÒÕà ¿Þàâ"
|
||||
|
||||
msgid "Priority"
|
||||
msgstr ""
|
||||
|
||||
msgid "HTTP Streamtype"
|
||||
msgstr "ÂØß HTTP ßÞâÞÚÐ"
|
||||
|
||||
msgid "Multicast Streaming Server"
|
||||
msgstr ""
|
||||
|
||||
msgid "Start IGMP Server"
|
||||
msgstr ""
|
||||
|
||||
msgid "Multicast Client Port"
|
||||
msgstr ""
|
||||
|
||||
msgid "Multicast Streamtype"
|
||||
msgstr ""
|
||||
|
||||
msgid "VDR Streaming Server"
|
||||
msgstr "VDR Streaming áÕàÒÕà"
|
||||
97
server/po/sk_SK.po
Executable file
97
server/po/sk_SK.po
Executable file
@@ -0,0 +1,97 @@
|
||||
# VDR streamdev plugin language source file.
|
||||
# Copyright (C) 2009 streamdev development team. See http://streamdev.vdr-developer.org
|
||||
# This file is distributed under the same license as the VDR streamdev package.
|
||||
# Milan Hrala <hrala.milan@gmail.com>, 2009
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: streamdev_SK\n"
|
||||
"Report-Msgid-Bugs-To: <vdrdev@schmirler.de>\n"
|
||||
"POT-Creation-Date: 2014-05-05 22:46+0200\n"
|
||||
"PO-Revision-Date: 2013-11-22 23:39+0100\n"
|
||||
"Last-Translator: Milan Hrala <hrala.milan@gmail.com>\n"
|
||||
"Language-Team: Slovak <hrala.milan@gmail.com>\n"
|
||||
"Language: sk\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=iso-8859-2\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Poedit-Language: Slovak\n"
|
||||
"X-Poedit-Country: SLOVAKIA\n"
|
||||
|
||||
msgid "Streaming active"
|
||||
msgstr "Streamovanie aktívne"
|
||||
|
||||
msgid "Streamdev Connections"
|
||||
msgstr "Streamdev spojenia"
|
||||
|
||||
msgid "Disconnect"
|
||||
msgstr "Odpoji»"
|
||||
|
||||
msgid "Suspend"
|
||||
msgstr "Pozastavi»"
|
||||
|
||||
msgid "Common Settings"
|
||||
msgstr "V¹eobecné nastavenia"
|
||||
|
||||
msgid "Hide Mainmenu Entry"
|
||||
msgstr "Schova» v hlavnom menu"
|
||||
|
||||
msgid "Start with Live TV suspended"
|
||||
msgstr "Pozastavi» Live TV pri ¹tarte"
|
||||
|
||||
msgid "Maximum Number of Clients"
|
||||
msgstr "Maximály poèet klientov"
|
||||
|
||||
msgid "Live TV buffer delay (ms)"
|
||||
msgstr ""
|
||||
|
||||
msgid "VDR-to-VDR Server"
|
||||
msgstr "Prenos z VDR do VDR"
|
||||
|
||||
msgid "Start VDR-to-VDR Server"
|
||||
msgstr "Spusti» prenos z VDR do VDR"
|
||||
|
||||
msgid "VDR-to-VDR Server Port"
|
||||
msgstr "Port servera prenosu z VDR do VDR"
|
||||
|
||||
msgid "Bind to IP"
|
||||
msgstr "Viaza» na IP"
|
||||
|
||||
msgid "Legacy Client Priority"
|
||||
msgstr "Dodatoèná priorita klienta"
|
||||
|
||||
msgid "Client may suspend"
|
||||
msgstr "Klient mô¾e server pozastavi»"
|
||||
|
||||
msgid "Loop Prevention"
|
||||
msgstr "Prevencia sluèky"
|
||||
|
||||
msgid "HTTP Server"
|
||||
msgstr "HTTP server "
|
||||
|
||||
msgid "Start HTTP Server"
|
||||
msgstr "Spusti» HTTP Server"
|
||||
|
||||
msgid "HTTP Server Port"
|
||||
msgstr "Port HTTP servera"
|
||||
|
||||
msgid "Priority"
|
||||
msgstr "Priorita"
|
||||
|
||||
msgid "HTTP Streamtype"
|
||||
msgstr "Typ HTTP streamu"
|
||||
|
||||
msgid "Multicast Streaming Server"
|
||||
msgstr "Streamovanie Multicastového servera"
|
||||
|
||||
msgid "Start IGMP Server"
|
||||
msgstr "Spusti» IGMP Server"
|
||||
|
||||
msgid "Multicast Client Port"
|
||||
msgstr "Port Multicast klienta"
|
||||
|
||||
msgid "Multicast Streamtype"
|
||||
msgstr "Typ Multicast streamu"
|
||||
|
||||
msgid "VDR Streaming Server"
|
||||
msgstr "VDR server streamovania"
|
||||
319
server/recplayer.c
Normal file
319
server/recplayer.c
Normal file
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
Copyright 2004-2005 Chris Tallon
|
||||
|
||||
This file is part of VOMP.
|
||||
and adopted for streamdev to play recordings
|
||||
|
||||
VOMP is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
VOMP is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with VOMP; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "recplayer.h"
|
||||
|
||||
// for TSPLAY patch detection
|
||||
#include "vdr/device.h"
|
||||
|
||||
#undef _XOPEN_SOURCE
|
||||
#define _XOPEN_SOURCE 600
|
||||
#include <fcntl.h>
|
||||
|
||||
RecPlayer::RecPlayer(const char* FileName)
|
||||
{
|
||||
file = NULL;
|
||||
fileOpen = 0;
|
||||
lastPosition = 0;
|
||||
recording = new cRecording(FileName);
|
||||
for(int i = 1; i < 1000; i++) segments[i] = NULL;
|
||||
|
||||
// FIXME find out max file path / name lengths
|
||||
|
||||
indexFile = new cIndexFile(recording->FileName(), false, recording->IsPesRecording());
|
||||
if (!indexFile) esyslog("ERROR: Streamdev: Failed to create indexfile!");
|
||||
|
||||
scan();
|
||||
|
||||
parser = new cPatPmtParser();
|
||||
unsigned char buffer[2 * TS_SIZE];
|
||||
unsigned long l = getBlock(buffer, 0UL, sizeof(buffer));
|
||||
if (!l || !parser->ParsePatPmt(buffer, (int) l))
|
||||
esyslog("ERROR: Streamdev: Failed to parse PAT/PMT");
|
||||
}
|
||||
|
||||
void RecPlayer::scan()
|
||||
{
|
||||
if (file) fclose(file);
|
||||
totalLength = 0;
|
||||
fileOpen = 0;
|
||||
totalFrames = 0;
|
||||
|
||||
int i = 1;
|
||||
while(segments[i++]) delete segments[i];
|
||||
|
||||
char fileName[2048];
|
||||
for(i = 1; i < 1000; i++)
|
||||
{
|
||||
|
||||
snprintf(fileName, 2047, "%s/%05i.ts", recording->FileName(), i);
|
||||
file = fopen(fileName, "r");
|
||||
if (!file) {
|
||||
snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), i);
|
||||
file = fopen(fileName, "r");
|
||||
}
|
||||
if (!file) break;
|
||||
|
||||
segments[i] = new Segment();
|
||||
segments[i]->start = totalLength;
|
||||
fseek(file, 0, SEEK_END);
|
||||
totalLength += ftello(file);
|
||||
totalFrames = indexFile->Last();
|
||||
//log->log("RecPlayer", Log::DEBUG, "File %i found, totalLength now %llu, numFrames = %lu", i, totalLength, totalFrames);
|
||||
segments[i]->end = totalLength;
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
file = NULL;
|
||||
}
|
||||
|
||||
RecPlayer::~RecPlayer()
|
||||
{
|
||||
//log->log("RecPlayer", Log::DEBUG, "destructor");
|
||||
int i = 1;
|
||||
while(segments[i++]) delete segments[i];
|
||||
if (file) fclose(file);
|
||||
delete indexFile;
|
||||
delete recording;
|
||||
delete parser;
|
||||
}
|
||||
|
||||
int RecPlayer::openFile(int index)
|
||||
{
|
||||
if (file) fclose(file);
|
||||
|
||||
char fileName[2048];
|
||||
|
||||
snprintf(fileName, 2047, "%s/%05i.ts", recording->FileName(), index);
|
||||
isyslog("openFile called for index %i string:%s", index, fileName);
|
||||
|
||||
file = fopen(fileName, "r");
|
||||
if (file)
|
||||
{
|
||||
fileOpen = index;
|
||||
return 1;
|
||||
}
|
||||
|
||||
snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), index);
|
||||
isyslog("openFile called for index %i string:%s", index, fileName);
|
||||
//log->log("RecPlayer", Log::DEBUG, "openFile called for index %i string:%s", index, fileName);
|
||||
|
||||
file = fopen(fileName, "r");
|
||||
if (file)
|
||||
{
|
||||
fileOpen = index;
|
||||
return 1;
|
||||
}
|
||||
|
||||
//log->log("RecPlayer", Log::DEBUG, "file failed to open");
|
||||
fileOpen = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t RecPlayer::getLengthBytes()
|
||||
{
|
||||
return totalLength;
|
||||
}
|
||||
|
||||
uint32_t RecPlayer::getLengthFrames()
|
||||
{
|
||||
return totalFrames;
|
||||
}
|
||||
|
||||
unsigned long RecPlayer::getBlock(unsigned char* buffer, uint64_t position, unsigned long amount)
|
||||
{
|
||||
if ((amount > totalLength) || (amount > 500000))
|
||||
{
|
||||
//log->log("RecPlayer", Log::DEBUG, "Amount %lu requested and rejected", amount);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (position >= totalLength)
|
||||
{
|
||||
//log->log("RecPlayer", Log::DEBUG, "Client asked for data starting past end of recording!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((position + amount) > totalLength)
|
||||
{
|
||||
//log->log("RecPlayer", Log::DEBUG, "Client asked for some data past the end of recording, adjusting amount");
|
||||
amount = totalLength - position;
|
||||
}
|
||||
|
||||
// work out what block position is in
|
||||
int segmentNumber;
|
||||
for(segmentNumber = 1; segmentNumber < 1000; segmentNumber++)
|
||||
{
|
||||
if ((position >= segments[segmentNumber]->start) && (position < segments[segmentNumber]->end)) break;
|
||||
// position is in this block
|
||||
}
|
||||
|
||||
// we could be seeking around
|
||||
if (segmentNumber != fileOpen)
|
||||
{
|
||||
if (!openFile(segmentNumber)) return 0;
|
||||
}
|
||||
|
||||
uint64_t currentPosition = position;
|
||||
uint32_t yetToGet = amount;
|
||||
uint32_t got = 0;
|
||||
uint32_t getFromThisSegment = 0;
|
||||
uint64_t filePosition;
|
||||
|
||||
while(got < amount)
|
||||
{
|
||||
if (got)
|
||||
{
|
||||
// if(got) then we have already got some and we are back around
|
||||
// advance the file pointer to the next file
|
||||
if (!openFile(++segmentNumber)) return 0;
|
||||
}
|
||||
|
||||
// is the request completely in this block?
|
||||
if ((currentPosition + yetToGet) <= segments[segmentNumber]->end)
|
||||
getFromThisSegment = yetToGet;
|
||||
else
|
||||
getFromThisSegment = segments[segmentNumber]->end - currentPosition;
|
||||
|
||||
filePosition = currentPosition - segments[segmentNumber]->start;
|
||||
fseek(file, filePosition, SEEK_SET);
|
||||
if (fread(&buffer[got], getFromThisSegment, 1, file) != 1) return 0; // umm, big problem.
|
||||
|
||||
// Tell linux not to bother keeping the data in the FS cache
|
||||
posix_fadvise(fileno(file), filePosition, getFromThisSegment, POSIX_FADV_DONTNEED);
|
||||
|
||||
got += getFromThisSegment;
|
||||
currentPosition += getFromThisSegment;
|
||||
yetToGet -= getFromThisSegment;
|
||||
}
|
||||
|
||||
lastPosition = position;
|
||||
return got;
|
||||
}
|
||||
|
||||
uint64_t RecPlayer::getLastPosition()
|
||||
{
|
||||
return lastPosition;
|
||||
}
|
||||
|
||||
cRecording* RecPlayer::getCurrentRecording()
|
||||
{
|
||||
return recording;
|
||||
}
|
||||
|
||||
#if VDRVERSNUM < 10732
|
||||
#define ALIGNED_POS(x) (positionFromFrameNumber(indexFile->GetNextIFrame(x, 1)))
|
||||
#else
|
||||
#define ALIGNED_POS(x) (positionFromFrameNumber(indexFile->GetClosestIFrame(x)))
|
||||
#endif
|
||||
uint64_t RecPlayer::positionFromResume(int ResumeID)
|
||||
{
|
||||
int resumeBackup = Setup.ResumeID;
|
||||
Setup.ResumeID = ResumeID;
|
||||
cResumeFile resume(recording->FileName(), recording->IsPesRecording());
|
||||
Setup.ResumeID = resumeBackup;
|
||||
return ALIGNED_POS(resume.Read());
|
||||
}
|
||||
|
||||
uint64_t RecPlayer::positionFromMark(int MarkIndex)
|
||||
{
|
||||
cMarks marks;
|
||||
if (marks.Load(recording->FileName(), recording->FramesPerSecond(), recording->IsPesRecording()) && marks.Count()) {
|
||||
cMark *mark = marks.cConfig<cMark>::Get(MarkIndex);
|
||||
if (mark)
|
||||
return ALIGNED_POS(mark->Position());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t RecPlayer::positionFromTime(int Seconds)
|
||||
{
|
||||
return ALIGNED_POS(SecondsToFrames(Seconds, recording->FramesPerSecond()));
|
||||
}
|
||||
|
||||
uint64_t RecPlayer::positionFromPercent(int Percent)
|
||||
{
|
||||
return ALIGNED_POS(getLengthFrames() * Percent / 100L);
|
||||
}
|
||||
|
||||
uint64_t RecPlayer::positionFromFrameNumber(uint32_t frameNumber)
|
||||
{
|
||||
if (!indexFile) return 0;
|
||||
|
||||
uint16_t retFileNumber;
|
||||
off_t retFileOffset;
|
||||
|
||||
if (!indexFile->Get((int)frameNumber, &retFileNumber, &retFileOffset))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// log->log("RecPlayer", Log::DEBUG, "FN: %u FO: %i", retFileNumber, retFileOffset);
|
||||
if (!segments[retFileNumber]) return 0;
|
||||
uint64_t position = segments[retFileNumber]->start + retFileOffset;
|
||||
// log->log("RecPlayer", Log::DEBUG, "Pos: %llu", position);
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
uint32_t RecPlayer::frameNumberFromPosition(uint64_t position)
|
||||
{
|
||||
if (!indexFile) return 0;
|
||||
|
||||
if (position >= totalLength)
|
||||
{
|
||||
//log->log("RecPlayer", Log::DEBUG, "Client asked for data starting past end of recording!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t segmentNumber;
|
||||
for(segmentNumber = 1; segmentNumber < 255; segmentNumber++)
|
||||
{
|
||||
if ((position >= segments[segmentNumber]->start) && (position < segments[segmentNumber]->end)) break;
|
||||
// position is in this block
|
||||
}
|
||||
uint64_t askposition = position - segments[segmentNumber]->start;
|
||||
return indexFile->Get((int)segmentNumber, askposition);
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool RecPlayer::getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength)
|
||||
{
|
||||
// 0 = backwards
|
||||
// 1 = forwards
|
||||
|
||||
if (!indexFile) return false;
|
||||
|
||||
int iframeLength;
|
||||
int indexReturnFrameNumber;
|
||||
|
||||
indexReturnFrameNumber = (uint32_t)indexFile->GetNextIFrame(frameNumber, (direction==1 ? true : false), NULL, NULL, &iframeLength);
|
||||
//log->log("RecPlayer", Log::DEBUG, "GNIF input framenumber:%lu, direction=%lu, output:framenumber=%i, framelength=%i", frameNumber, direction, indexReturnFrameNumber, iframeLength);
|
||||
|
||||
if (indexReturnFrameNumber == -1) return false;
|
||||
|
||||
*rfilePosition = positionFromFrameNumber(indexReturnFrameNumber);
|
||||
*rframeNumber = (uint32_t)indexReturnFrameNumber;
|
||||
*rframeLength = (uint32_t)iframeLength;
|
||||
|
||||
return true;
|
||||
}
|
||||
70
server/recplayer.h
Normal file
70
server/recplayer.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
Copyright 2004-2005 Chris Tallon
|
||||
|
||||
This file is part of VOMP.
|
||||
|
||||
VOMP is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
VOMP is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with VOMP; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef RECPLAYER_H
|
||||
#define RECPLAYER_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <vdr/recording.h>
|
||||
#include <vdr/remux.h>
|
||||
|
||||
#include "server/streamer.h"
|
||||
|
||||
class Segment
|
||||
{
|
||||
public:
|
||||
uint64_t start;
|
||||
uint64_t end;
|
||||
};
|
||||
|
||||
class RecPlayer
|
||||
{
|
||||
public:
|
||||
RecPlayer(const char* FileName);
|
||||
~RecPlayer();
|
||||
uint64_t getLengthBytes();
|
||||
uint32_t getLengthFrames();
|
||||
unsigned long getBlock(unsigned char* buffer, uint64_t position, unsigned long amount);
|
||||
int openFile(int index);
|
||||
uint64_t getLastPosition();
|
||||
cRecording* getCurrentRecording();
|
||||
const cPatPmtParser* getPatPmtData() { return parser; }
|
||||
void scan();
|
||||
uint64_t positionFromResume(int ResumeID);
|
||||
uint64_t positionFromMark(int MarkIndex);
|
||||
uint64_t positionFromTime(int Seconds);
|
||||
uint64_t positionFromPercent(int Percent);
|
||||
uint64_t positionFromFrameNumber(uint32_t frameNumber);
|
||||
uint32_t frameNumberFromPosition(uint64_t position);
|
||||
bool getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength);
|
||||
|
||||
private:
|
||||
cRecording* recording;
|
||||
cIndexFile* indexFile;
|
||||
cPatPmtParser* parser;
|
||||
FILE* file;
|
||||
int fileOpen;
|
||||
Segment* segments[1000];
|
||||
uint64_t totalLength;
|
||||
uint64_t lastPosition;
|
||||
uint32_t totalFrames;
|
||||
};
|
||||
|
||||
#endif
|
||||
98
server/recstreamer.c
Normal file
98
server/recstreamer.c
Normal file
@@ -0,0 +1,98 @@
|
||||
#include "remux/ts2ps.h"
|
||||
#include "remux/ts2pes.h"
|
||||
#include "remux/ts2es.h"
|
||||
#include "remux/extern.h"
|
||||
|
||||
#include <vdr/ringbuffer.h>
|
||||
#include "server/recstreamer.h"
|
||||
#include "server/connection.h"
|
||||
#include "common.h"
|
||||
|
||||
using namespace Streamdev;
|
||||
|
||||
// --- cStreamdevRecStreamer -------------------------------------------------
|
||||
|
||||
cStreamdevRecStreamer::cStreamdevRecStreamer(const cServerConnection *Connection, RecPlayer *RecPlayer, eStreamType StreamType, int64_t StartOffset, const int *Apid, const int *Dpid):
|
||||
cStreamdevStreamer("streamdev-recstreaming", Connection),
|
||||
m_RecPlayer(RecPlayer),
|
||||
m_StartOffset(StartOffset),
|
||||
m_From(0L)
|
||||
{
|
||||
Dprintf("New rec streamer\n");
|
||||
m_To = (int64_t) m_RecPlayer->getLengthBytes() - StartOffset - 1;
|
||||
|
||||
const cPatPmtParser *parser = RecPlayer->getPatPmtData();
|
||||
const int *Apids = Apid ? Apid : parser->Apids();
|
||||
const int *Dpids = Dpid ? Dpid : parser->Dpids();
|
||||
switch (StreamType) {
|
||||
case stES:
|
||||
{
|
||||
int pid = parser->Vpid();
|
||||
if (Apid && Apid[0])
|
||||
pid = Apid[0];
|
||||
else if (Dpid && Dpid[0])
|
||||
pid = Dpid[0];
|
||||
SetRemux(new cTS2ESRemux(pid));
|
||||
}
|
||||
break;
|
||||
case stPES:
|
||||
if (!m_RecPlayer->getCurrentRecording()->IsPesRecording())
|
||||
SetRemux(new cTS2PESRemux(parser->Vpid(), Apids, Dpids, parser->Spids()));
|
||||
break;
|
||||
#ifdef STREAMDEV_PS
|
||||
case stPS:
|
||||
SetRemux(new cTS2PSRemux(parser->Vpid(), Apids, Dpids, parser->Spids()));
|
||||
break;
|
||||
#endif
|
||||
case stEXT:
|
||||
SetRemux(new cExternRemux(Connection, parser, Apids, Dpids));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cStreamdevRecStreamer::~cStreamdevRecStreamer()
|
||||
{
|
||||
Dprintf("Desctructing rec streamer\n");
|
||||
Stop();
|
||||
}
|
||||
|
||||
int64_t cStreamdevRecStreamer::SetRange(int64_t &From, int64_t &To)
|
||||
{
|
||||
int64_t l = (int64_t) GetLength();
|
||||
if (From < 0L) {
|
||||
From += l;
|
||||
if (From < 0L)
|
||||
From = 0L;
|
||||
To = l - 1;
|
||||
}
|
||||
else {
|
||||
if (To < 0L)
|
||||
To += l;
|
||||
else if (To >= l)
|
||||
To = l - 1;
|
||||
if (From > To) {
|
||||
// invalid range - return whole content
|
||||
From = 0L;
|
||||
To = l - 1;
|
||||
}
|
||||
}
|
||||
m_From = From;
|
||||
m_To = To;
|
||||
return m_To - m_From + 1;
|
||||
}
|
||||
|
||||
uchar* cStreamdevRecStreamer::GetFromReceiver(int &Count)
|
||||
{
|
||||
if (m_From <= m_To) {
|
||||
Count = (int) m_RecPlayer->getBlock(m_Buffer, m_StartOffset + m_From, sizeof(m_Buffer));
|
||||
return m_Buffer;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cString cStreamdevRecStreamer::ToText() const
|
||||
{
|
||||
return "REPLAY";
|
||||
}
|
||||
34
server/recstreamer.h
Normal file
34
server/recstreamer.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#ifndef VDR_STREAMDEV_RECSTREAMER_H
|
||||
#define VDR_STREAMDEV_RECSTREAMER_H
|
||||
|
||||
#include "common.h"
|
||||
#include "server/streamer.h"
|
||||
#include "server/recplayer.h"
|
||||
|
||||
#define RECBUFSIZE (174 * TS_SIZE)
|
||||
|
||||
// --- cStreamdevRecStreamer -------------------------------------------------
|
||||
|
||||
class cStreamdevRecStreamer: public cStreamdevStreamer {
|
||||
private:
|
||||
//Streamdev::cTSRemux *m_Remux;
|
||||
RecPlayer *m_RecPlayer;
|
||||
int64_t m_StartOffset;
|
||||
int64_t m_From;
|
||||
int64_t m_To;
|
||||
uchar m_Buffer[RECBUFSIZE];
|
||||
|
||||
protected:
|
||||
virtual uchar* GetFromReceiver(int &Count);
|
||||
virtual void DelFromReceiver(int Count) { m_From += Count; };
|
||||
|
||||
public:
|
||||
virtual bool IsReceiving(void) const { return m_From <= m_To; };
|
||||
uint64_t GetLength() { return m_RecPlayer->getLengthBytes() - m_StartOffset; }
|
||||
int64_t SetRange(int64_t &From, int64_t &To);
|
||||
virtual cString ToText() const;
|
||||
cStreamdevRecStreamer(const cServerConnection *Connection, RecPlayer *RecPlayer, eStreamType StreamType, int64_t StartOffset = 0L, const int *Apids = NULL, const int *Dpids = NULL);
|
||||
virtual ~cStreamdevRecStreamer();
|
||||
};
|
||||
|
||||
#endif // VDR_STREAMDEV_RECSTREAMER_H
|
||||
@@ -1,10 +1,11 @@
|
||||
/*
|
||||
* $Id: server.c,v 1.5 2007/04/02 10:32:34 schmirl Exp $
|
||||
* $Id: server.c,v 1.10 2009/02/13 10:39:22 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include "server/server.h"
|
||||
#include "server/componentVTP.h"
|
||||
#include "server/componentHTTP.h"
|
||||
#include "server/componentIGMP.h"
|
||||
#include "server/setup.h"
|
||||
|
||||
#include <vdr/tools.h>
|
||||
@@ -13,14 +14,15 @@
|
||||
#include <errno.h>
|
||||
|
||||
cSVDRPhosts StreamdevHosts;
|
||||
char *opt_auth = NULL;
|
||||
char *opt_remux = NULL;
|
||||
|
||||
cStreamdevServer *cStreamdevServer::m_Instance = NULL;
|
||||
cList<cServerComponent> cStreamdevServer::m_Servers;
|
||||
cList<cServerConnection> cStreamdevServer::m_Clients;
|
||||
|
||||
cStreamdevServer::cStreamdevServer(void):
|
||||
cThread("streamdev server"),
|
||||
m_Active(false)
|
||||
cThread("streamdev server")
|
||||
{
|
||||
Start();
|
||||
}
|
||||
@@ -35,6 +37,12 @@ void cStreamdevServer::Initialize(void)
|
||||
if (m_Instance == NULL) {
|
||||
if (StreamdevServerSetup.StartVTPServer) Register(new cComponentVTP);
|
||||
if (StreamdevServerSetup.StartHTTPServer) Register(new cComponentHTTP);
|
||||
if (StreamdevServerSetup.StartIGMPServer) {
|
||||
if (strcmp(StreamdevServerSetup.IGMPBindIP, "0.0.0.0") == 0)
|
||||
esyslog("streamdev-server: Not starting IGMP. IGMP must be bound to a local IP");
|
||||
else
|
||||
Register(new cComponentIGMP);
|
||||
}
|
||||
|
||||
m_Instance = new cStreamdevServer;
|
||||
}
|
||||
@@ -47,10 +55,8 @@ void cStreamdevServer::Destruct(void)
|
||||
|
||||
void cStreamdevServer::Stop(void)
|
||||
{
|
||||
if (m_Active) {
|
||||
m_Active = false;
|
||||
if (Running())
|
||||
Cancel(3);
|
||||
}
|
||||
}
|
||||
|
||||
void cStreamdevServer::Register(cServerComponent *Server)
|
||||
@@ -60,8 +66,6 @@ void cStreamdevServer::Register(cServerComponent *Server)
|
||||
|
||||
void cStreamdevServer::Action(void)
|
||||
{
|
||||
m_Active = true;
|
||||
|
||||
/* Initialize Server components, deleting those that failed */
|
||||
for (cServerComponent *c = m_Servers.First(); c;) {
|
||||
cServerComponent *next = m_Servers.Next(c);
|
||||
@@ -72,11 +76,11 @@ void cStreamdevServer::Action(void)
|
||||
|
||||
if (m_Servers.Count() == 0) {
|
||||
esyslog("ERROR: no streamdev server activated, exiting");
|
||||
m_Active = false;
|
||||
Cancel(-1);
|
||||
}
|
||||
|
||||
cTBSelect select;
|
||||
while (m_Active) {
|
||||
while (Running()) {
|
||||
select.Clear();
|
||||
|
||||
/* Ask all Server components to register to the selector */
|
||||
@@ -102,9 +106,9 @@ void cStreamdevServer::Action(void)
|
||||
sel = 0;
|
||||
}
|
||||
}
|
||||
} while (sel < 0 && errno == ETIMEDOUT && m_Active);
|
||||
} while (sel < 0 && errno == ETIMEDOUT && Running());
|
||||
|
||||
if (!m_Active)
|
||||
if (!Running())
|
||||
break;
|
||||
if (sel < 0) {
|
||||
esyslog("fatal error, server exiting: %m");
|
||||
@@ -115,13 +119,17 @@ void cStreamdevServer::Action(void)
|
||||
for (cServerComponent *c = m_Servers.First(); c; c = m_Servers.Next(c)){
|
||||
if (sel && select.CanRead(c->Socket())) {
|
||||
cServerConnection *client = c->Accept();
|
||||
if (!client)
|
||||
continue;
|
||||
Lock();
|
||||
m_Clients.Add(client);
|
||||
Unlock();
|
||||
|
||||
if (m_Clients.Count() > StreamdevServerSetup.MaxClients) {
|
||||
esyslog("streamdev: too many clients, rejecting %s:%d",
|
||||
client->RemoteIp().c_str(), client->RemotePort());
|
||||
client->Reject();
|
||||
} else if (!StreamdevHosts.Acceptable(client->RemoteIpAddr())) {
|
||||
} else if (!client->CanAuthenticate() && !StreamdevHosts.Acceptable(client->RemoteIpAddr())) {
|
||||
esyslog("streamdev: client %s:%d not allowed to connect",
|
||||
client->RemoteIp().c_str(), client->RemotePort());
|
||||
client->Reject();
|
||||
@@ -144,26 +152,37 @@ void cStreamdevServer::Action(void)
|
||||
|
||||
cServerConnection *next = m_Clients.Next(s);
|
||||
if (!result) {
|
||||
isyslog("streamdev: closing streamdev connection to %s:%d",
|
||||
s->RemoteIp().c_str(), s->RemotePort());
|
||||
s->Close();
|
||||
if (s->IsOpen())
|
||||
s->Close();
|
||||
Lock();
|
||||
m_Clients.Del(s);
|
||||
Unlock();
|
||||
}
|
||||
s = next;
|
||||
}
|
||||
}
|
||||
|
||||
Lock();
|
||||
while (m_Clients.Count() > 0) {
|
||||
cServerConnection *s = m_Clients.First();
|
||||
s->Close();
|
||||
m_Clients.Del(s);
|
||||
}
|
||||
Unlock();
|
||||
|
||||
while (m_Servers.Count() > 0) {
|
||||
cServerComponent *c = m_Servers.First();
|
||||
c->Destruct();
|
||||
m_Servers.Del(c);
|
||||
}
|
||||
|
||||
m_Active = false;
|
||||
}
|
||||
|
||||
#if APIVERSNUM >= 20300
|
||||
cList<cServerConnection>& cStreamdevServer::Clients(cThreadLock& Lock)
|
||||
#else
|
||||
const cList<cServerConnection>& cStreamdevServer::Clients(cThreadLock& Lock)
|
||||
#endif
|
||||
{
|
||||
Lock.Lock(m_Instance);
|
||||
return m_Clients;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: server.h,v 1.3 2008/04/07 14:50:33 schmirl Exp $
|
||||
* $Id: server.h,v 1.6 2008/10/22 11:59:32 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SERVER_H
|
||||
@@ -10,13 +10,14 @@
|
||||
#include "server/component.h"
|
||||
#include "server/connection.h"
|
||||
|
||||
#define EXTERNREMUXPATH (*AddDirectory(cPlugin::ConfigDirectory(PLUGIN_NAME_I18N), "externremux.sh"))
|
||||
#define DEFAULT_EXTERNREMUX (*AddDirectory(cPlugin::ConfigDirectory(PLUGIN_NAME_I18N), "externremux.sh"))
|
||||
#define STREAMDEVHOSTSPATH (*AddDirectory(cPlugin::ConfigDirectory(PLUGIN_NAME_I18N), "streamdevhosts.conf"))
|
||||
|
||||
extern char *opt_auth;
|
||||
extern char *opt_remux;
|
||||
|
||||
class cStreamdevServer: public cThread {
|
||||
private:
|
||||
bool m_Active;
|
||||
|
||||
static cStreamdevServer *m_Instance;
|
||||
static cList<cServerComponent> m_Servers;
|
||||
static cList<cServerConnection> m_Clients;
|
||||
@@ -35,6 +36,12 @@ public:
|
||||
static void Initialize(void);
|
||||
static void Destruct(void);
|
||||
static bool Active(void);
|
||||
|
||||
#if APIVERSNUM >= 20300
|
||||
static cList<cServerConnection>& Clients(cThreadLock& Lock);
|
||||
#else
|
||||
static const cList<cServerConnection>& Clients(cThreadLock& Lock);
|
||||
#endif
|
||||
};
|
||||
|
||||
inline bool cStreamdevServer::Active(void)
|
||||
|
||||
130
server/setup.c
130
server/setup.c
@@ -1,68 +1,132 @@
|
||||
/*
|
||||
* $Id: setup.c,v 1.3 2008/04/07 14:50:33 schmirl Exp $
|
||||
* $Id: setup.c,v 1.10 2010/07/19 13:49:31 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include <vdr/menuitems.h>
|
||||
|
||||
#include "server/setup.h"
|
||||
#include "server/server.h"
|
||||
#include "i18n.h"
|
||||
|
||||
cStreamdevServerSetup StreamdevServerSetup;
|
||||
|
||||
cStreamdevServerSetup::cStreamdevServerSetup(void) {
|
||||
HideMenuEntry = false;
|
||||
MaxClients = 5;
|
||||
StartSuspended = ssAuto;
|
||||
LiveBufferMs = 0;
|
||||
StartVTPServer = true;
|
||||
VTPServerPort = 2004;
|
||||
VTPPriority = 0;
|
||||
LoopPrevention = false;
|
||||
StartHTTPServer = true;
|
||||
HTTPServerPort = 3000;
|
||||
HTTPStreamType = stPES;
|
||||
SuspendMode = smAlways;
|
||||
HTTPPriority = 0;
|
||||
HTTPStreamType = stTS;
|
||||
StartIGMPServer = false;
|
||||
IGMPClientPort = 1234;
|
||||
IGMPPriority = 0;
|
||||
IGMPStreamType = stTS;
|
||||
AllowSuspend = false;
|
||||
strcpy(VTPBindIP, "0.0.0.0");
|
||||
strcpy(HTTPBindIP, "0.0.0.0");
|
||||
strcpy(IGMPBindIP, "0.0.0.0");
|
||||
}
|
||||
|
||||
bool cStreamdevServerSetup::SetupParse(const char *Name, const char *Value) {
|
||||
if (strcmp(Name, "MaxClients") == 0) MaxClients = atoi(Value);
|
||||
if (strcmp(Name, "HideMenuEntry") == 0) HideMenuEntry = atoi(Value);
|
||||
else if (strcmp(Name, "MaxClients") == 0) MaxClients = atoi(Value);
|
||||
else if (strcmp(Name, "StartSuspended") == 0) StartSuspended = atoi(Value);
|
||||
else if (strcmp(Name, "LiveBufferMs") == 0) LiveBufferMs = atoi(Value);
|
||||
else if (strcmp(Name, "StartServer") == 0) StartVTPServer = atoi(Value);
|
||||
else if (strcmp(Name, "ServerPort") == 0) VTPServerPort = atoi(Value);
|
||||
else if (strcmp(Name, "VTPPriority") == 0) VTPPriority = atoi(Value);
|
||||
else if (strcmp(Name, "VTPBindIP") == 0) strcpy(VTPBindIP, Value);
|
||||
else if (strcmp(Name, "LoopPrevention") == 0) LoopPrevention = atoi(Value);
|
||||
else if (strcmp(Name, "StartHTTPServer") == 0) StartHTTPServer = atoi(Value);
|
||||
else if (strcmp(Name, "HTTPServerPort") == 0) HTTPServerPort = atoi(Value);
|
||||
else if (strcmp(Name, "HTTPPriority") == 0) HTTPPriority = atoi(Value);
|
||||
else if (strcmp(Name, "HTTPStreamType") == 0) HTTPStreamType = atoi(Value);
|
||||
else if (strcmp(Name, "HTTPBindIP") == 0) strcpy(HTTPBindIP, Value);
|
||||
else if (strcmp(Name, "SuspendMode") == 0) SuspendMode = atoi(Value);
|
||||
else if (strcmp(Name, "StartIGMPServer") == 0) StartIGMPServer = atoi(Value);
|
||||
else if (strcmp(Name, "IGMPClientPort") == 0) IGMPClientPort = atoi(Value);
|
||||
else if (strcmp(Name, "IGMPPriority") == 0) IGMPPriority = atoi(Value);
|
||||
else if (strcmp(Name, "IGMPStreamType") == 0) IGMPStreamType = atoi(Value);
|
||||
else if (strcmp(Name, "IGMPBindIP") == 0) strcpy(IGMPBindIP, Value);
|
||||
else if (strcmp(Name, "AllowSuspend") == 0) AllowSuspend = atoi(Value);
|
||||
else return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* cStreamdevServerMenuSetupPage::StreamTypes[st_Count - 1] = {
|
||||
"TS",
|
||||
"PES",
|
||||
"PS",
|
||||
"ES",
|
||||
"EXT"
|
||||
};
|
||||
|
||||
cStreamdevServerMenuSetupPage::cStreamdevServerMenuSetupPage(void) {
|
||||
m_NewSetup = StreamdevServerSetup;
|
||||
|
||||
AddCategory (tr("Common Settings"));
|
||||
AddRangeEdit(tr("Maximum Number of Clients"), m_NewSetup.MaxClients, 0, 100);
|
||||
AddSuspEdit (tr("Suspend behaviour"), m_NewSetup.SuspendMode);
|
||||
AddBoolEdit (tr("Client may suspend"), m_NewSetup.AllowSuspend);
|
||||
|
||||
AddCategory (tr("VDR-to-VDR Server"));
|
||||
AddBoolEdit (tr("Start VDR-to-VDR Server"), m_NewSetup.StartVTPServer);
|
||||
AddShortEdit(tr("VDR-to-VDR Server Port"), m_NewSetup.VTPServerPort);
|
||||
AddIpEdit (tr("Bind to IP"), m_NewSetup.VTPBindIP);
|
||||
|
||||
AddCategory (tr("HTTP Server"));
|
||||
AddBoolEdit (tr("Start HTTP Server"), m_NewSetup.StartHTTPServer);
|
||||
AddShortEdit(tr("HTTP Server Port"), m_NewSetup.HTTPServerPort);
|
||||
AddTypeEdit (tr("HTTP Streamtype"), m_NewSetup.HTTPStreamType);
|
||||
AddIpEdit (tr("Bind to IP"), m_NewSetup.HTTPBindIP);
|
||||
|
||||
SetCurrent(Get(1));
|
||||
Set();
|
||||
}
|
||||
|
||||
cStreamdevServerMenuSetupPage::~cStreamdevServerMenuSetupPage() {
|
||||
}
|
||||
|
||||
void cStreamdevServerMenuSetupPage::Set(void) {
|
||||
static const char *StartSuspendedItems[ss_Count] =
|
||||
{
|
||||
trVDR("no"),
|
||||
trVDR("yes"),
|
||||
trVDR("auto")
|
||||
};
|
||||
|
||||
int current = Current();
|
||||
Clear();
|
||||
AddCategory (tr("Common Settings"));
|
||||
Add(new cMenuEditBoolItem(tr("Hide Mainmenu Entry"), &m_NewSetup.HideMenuEntry));
|
||||
Add(new cMenuEditStraItem(tr("Start with Live TV suspended"), &m_NewSetup.StartSuspended, ss_Count, StartSuspendedItems));
|
||||
Add(new cMenuEditIntItem (tr("Maximum Number of Clients"), &m_NewSetup.MaxClients, 0, 100));
|
||||
Add(new cMenuEditIntItem (tr("Live TV buffer delay (ms)"), &m_NewSetup.LiveBufferMs, 0, 1500));
|
||||
|
||||
|
||||
AddCategory (tr("VDR-to-VDR Server"));
|
||||
Add(new cMenuEditBoolItem(tr("Start VDR-to-VDR Server"), &m_NewSetup.StartVTPServer));
|
||||
Add(new cMenuEditIntItem (tr("VDR-to-VDR Server Port"), &m_NewSetup.VTPServerPort, 0, 65535));
|
||||
Add(new cMenuEditIpItem (tr("Bind to IP"), m_NewSetup.VTPBindIP));
|
||||
Add(new cMenuEditIntItem (tr("Legacy Client Priority"), &m_NewSetup.VTPPriority, MINPRIORITY, MAXPRIORITY));
|
||||
Add(new cMenuEditBoolItem(tr("Client may suspend"), &m_NewSetup.AllowSuspend));
|
||||
if (cPluginManager::CallFirstService(LOOP_PREVENTION_SERVICE))
|
||||
Add(new cMenuEditBoolItem(tr("Loop Prevention"), &m_NewSetup.LoopPrevention));
|
||||
|
||||
AddCategory (tr("HTTP Server"));
|
||||
Add(new cMenuEditBoolItem(tr("Start HTTP Server"), &m_NewSetup.StartHTTPServer));
|
||||
Add(new cMenuEditIntItem (tr("HTTP Server Port"), &m_NewSetup.HTTPServerPort, 0, 65535));
|
||||
Add(new cMenuEditIpItem (tr("Bind to IP"), m_NewSetup.HTTPBindIP));
|
||||
Add(new cMenuEditIntItem (tr("Priority"), &m_NewSetup.HTTPPriority, MINPRIORITY, MAXPRIORITY));
|
||||
Add(new cMenuEditStraItem(tr("HTTP Streamtype"), &m_NewSetup.HTTPStreamType, st_Count - 1, StreamTypes));
|
||||
|
||||
AddCategory (tr("Multicast Streaming Server"));
|
||||
Add(new cMenuEditBoolItem(tr("Start IGMP Server"), &m_NewSetup.StartIGMPServer));
|
||||
Add(new cMenuEditIntItem (tr("Multicast Client Port"), &m_NewSetup.IGMPClientPort, 0, 65535));
|
||||
Add(new cMenuEditIpItem (tr("Bind to IP"), m_NewSetup.IGMPBindIP));
|
||||
Add(new cMenuEditIntItem (tr("Priority"), &m_NewSetup.IGMPPriority, MINPRIORITY, MAXPRIORITY));
|
||||
Add(new cMenuEditStraItem(tr("Multicast Streamtype"), &m_NewSetup.IGMPStreamType, st_Count - 1, StreamTypes));
|
||||
SetCurrent(Get(current));
|
||||
Display();
|
||||
}
|
||||
|
||||
void cStreamdevServerMenuSetupPage::AddCategory(const char *Title) {
|
||||
|
||||
cString str = cString::sprintf("--- %s -------------------------------------------------"
|
||||
"---------------", Title );
|
||||
|
||||
cOsdItem *item = new cOsdItem(*str);
|
||||
item->SetSelectable(false);
|
||||
Add(item);
|
||||
}
|
||||
|
||||
void cStreamdevServerMenuSetupPage::Store(void) {
|
||||
bool restart = false;
|
||||
if (m_NewSetup.StartVTPServer != StreamdevServerSetup.StartVTPServer
|
||||
@@ -70,20 +134,33 @@ void cStreamdevServerMenuSetupPage::Store(void) {
|
||||
|| strcmp(m_NewSetup.VTPBindIP, StreamdevServerSetup.VTPBindIP) != 0
|
||||
|| m_NewSetup.StartHTTPServer != StreamdevServerSetup.StartHTTPServer
|
||||
|| m_NewSetup.HTTPServerPort != StreamdevServerSetup.HTTPServerPort
|
||||
|| strcmp(m_NewSetup.HTTPBindIP, StreamdevServerSetup.HTTPBindIP) != 0) {
|
||||
|| strcmp(m_NewSetup.HTTPBindIP, StreamdevServerSetup.HTTPBindIP) != 0
|
||||
|| m_NewSetup.StartIGMPServer != StreamdevServerSetup.StartIGMPServer
|
||||
|| m_NewSetup.IGMPClientPort != StreamdevServerSetup.IGMPClientPort
|
||||
|| strcmp(m_NewSetup.IGMPBindIP, StreamdevServerSetup.IGMPBindIP) != 0) {
|
||||
restart = true;
|
||||
cStreamdevServer::Destruct();
|
||||
}
|
||||
|
||||
SetupStore("HideMenuEntry", m_NewSetup.HideMenuEntry);
|
||||
SetupStore("MaxClients", m_NewSetup.MaxClients);
|
||||
SetupStore("StartSuspended", m_NewSetup.StartSuspended);
|
||||
SetupStore("LiveBufferMs", m_NewSetup.LiveBufferMs);
|
||||
SetupStore("StartServer", m_NewSetup.StartVTPServer);
|
||||
SetupStore("ServerPort", m_NewSetup.VTPServerPort);
|
||||
SetupStore("VTPBindIP", m_NewSetup.VTPBindIP);
|
||||
SetupStore("VTPPriority", m_NewSetup.VTPPriority);
|
||||
SetupStore("LoopPrevention", m_NewSetup.LoopPrevention);
|
||||
SetupStore("StartHTTPServer", m_NewSetup.StartHTTPServer);
|
||||
SetupStore("HTTPServerPort", m_NewSetup.HTTPServerPort);
|
||||
SetupStore("HTTPStreamType", m_NewSetup.HTTPStreamType);
|
||||
SetupStore("HTTPBindIP", m_NewSetup.HTTPBindIP);
|
||||
SetupStore("SuspendMode", m_NewSetup.SuspendMode);
|
||||
SetupStore("HTTPPriority", m_NewSetup.HTTPPriority);
|
||||
SetupStore("HTTPStreamType", m_NewSetup.HTTPStreamType);
|
||||
SetupStore("StartIGMPServer", m_NewSetup.StartIGMPServer);
|
||||
SetupStore("IGMPClientPort", m_NewSetup.IGMPClientPort);
|
||||
SetupStore("IGMPBindIP", m_NewSetup.IGMPBindIP);
|
||||
SetupStore("IGMPPriority", m_NewSetup.IGMPPriority);
|
||||
SetupStore("IGMPStreamType", m_NewSetup.IGMPStreamType);
|
||||
SetupStore("AllowSuspend", m_NewSetup.AllowSuspend);
|
||||
|
||||
StreamdevServerSetup = m_NewSetup;
|
||||
@@ -91,4 +168,3 @@ void cStreamdevServerMenuSetupPage::Store(void) {
|
||||
if (restart)
|
||||
cStreamdevServer::Initialize();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: setup.h,v 1.1 2004/12/30 22:44:21 lordjaxom Exp $
|
||||
* $Id: setup.h,v 1.4 2010/07/19 13:49:31 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SETUPSERVER_H
|
||||
@@ -7,29 +7,49 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
enum eStartSuspended {
|
||||
ssNo,
|
||||
ssYes,
|
||||
ssAuto,
|
||||
ss_Count
|
||||
};
|
||||
|
||||
struct cStreamdevServerSetup {
|
||||
cStreamdevServerSetup(void);
|
||||
|
||||
bool SetupParse(const char *Name, const char *Value);
|
||||
|
||||
int HideMenuEntry;
|
||||
int MaxClients;
|
||||
int StartSuspended;
|
||||
int LiveBufferMs;
|
||||
int StartVTPServer;
|
||||
int VTPServerPort;
|
||||
char VTPBindIP[20];
|
||||
int VTPPriority;
|
||||
int AllowSuspend;
|
||||
int LoopPrevention;
|
||||
int StartHTTPServer;
|
||||
int HTTPServerPort;
|
||||
int HTTPPriority;
|
||||
int HTTPStreamType;
|
||||
char HTTPBindIP[20];
|
||||
int SuspendMode;
|
||||
int AllowSuspend;
|
||||
int StartIGMPServer;
|
||||
int IGMPClientPort;
|
||||
int IGMPPriority;
|
||||
int IGMPStreamType;
|
||||
char IGMPBindIP[20];
|
||||
};
|
||||
|
||||
extern cStreamdevServerSetup StreamdevServerSetup;
|
||||
|
||||
class cStreamdevServerMenuSetupPage: public cStreamdevMenuSetupPage {
|
||||
class cStreamdevServerMenuSetupPage: public cMenuSetupPage {
|
||||
private:
|
||||
static const char* StreamTypes[];
|
||||
cStreamdevServerSetup m_NewSetup;
|
||||
|
||||
void AddCategory(const char *Title);
|
||||
void Set();
|
||||
protected:
|
||||
virtual void Store(void);
|
||||
|
||||
|
||||
275
server/streamdev-server.c
Normal file
275
server/streamdev-server.c
Normal file
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* streamdev.c: A plugin for the Video Disk Recorder
|
||||
*
|
||||
* See the README file for copyright information and how to reach the author.
|
||||
*
|
||||
* $Id: streamdev-server.c,v 1.2 2010/07/19 13:49:32 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include <getopt.h>
|
||||
#include <vdr/tools.h>
|
||||
#include "streamdev-server.h"
|
||||
#include "server/menu.h"
|
||||
#include "server/setup.h"
|
||||
#include "server/server.h"
|
||||
#include "server/suspend.h"
|
||||
|
||||
#if !defined(APIVERSNUM) || APIVERSNUM < 10725
|
||||
#error "VDR-1.7.25 or greater required to compile server! Use 'make client' to compile client only."
|
||||
#endif
|
||||
|
||||
cList<cMainThreadHookSubscriber> cMainThreadHookSubscriber::m_Subscribers;
|
||||
cMutex cMainThreadHookSubscriber::m_Mutex;
|
||||
|
||||
#if APIVERSNUM >= 20300
|
||||
cList<cMainThreadHookSubscriber>& cMainThreadHookSubscriber::Subscribers(cMutexLock& Lock)
|
||||
#else
|
||||
const cList<cMainThreadHookSubscriber>& cMainThreadHookSubscriber::Subscribers(cMutexLock& Lock)
|
||||
#endif
|
||||
{
|
||||
Lock.Lock(&m_Mutex);
|
||||
return m_Subscribers;
|
||||
}
|
||||
|
||||
cMainThreadHookSubscriber::cMainThreadHookSubscriber()
|
||||
{
|
||||
m_Mutex.Lock();
|
||||
m_Subscribers.Add(this);
|
||||
m_Mutex.Unlock();
|
||||
}
|
||||
|
||||
cMainThreadHookSubscriber::~cMainThreadHookSubscriber()
|
||||
{
|
||||
m_Mutex.Lock();
|
||||
m_Subscribers.Del(this, false);
|
||||
m_Mutex.Unlock();
|
||||
}
|
||||
|
||||
const char *cPluginStreamdevServer::DESCRIPTION = trNOOP("VDR Streaming Server");
|
||||
|
||||
cPluginStreamdevServer::cPluginStreamdevServer(void)
|
||||
{
|
||||
m_Suspend = false;
|
||||
}
|
||||
|
||||
cPluginStreamdevServer::~cPluginStreamdevServer()
|
||||
{
|
||||
free(opt_auth);
|
||||
free(opt_remux);
|
||||
}
|
||||
|
||||
const char *cPluginStreamdevServer::Description(void)
|
||||
{
|
||||
return tr(DESCRIPTION);
|
||||
}
|
||||
|
||||
const char *cPluginStreamdevServer::CommandLineHelp(void)
|
||||
{
|
||||
// return a string that describes all known command line options.
|
||||
return
|
||||
" -a <LOGIN:PASSWORD>, --auth=<LOGIN:PASSWORD> Credentials for HTTP authentication.\n"
|
||||
" -r <CMD>, --remux=<CMD> Define an external command for remuxing.\n"
|
||||
;
|
||||
}
|
||||
|
||||
bool cPluginStreamdevServer::ProcessArgs(int argc, char *argv[])
|
||||
{
|
||||
// implement command line argument processing here if applicable.
|
||||
static const struct option long_options[] = {
|
||||
{ "auth", required_argument, NULL, 'a' },
|
||||
{ "remux", required_argument, NULL, 'r' },
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
|
||||
int c;
|
||||
while((c = getopt_long(argc, argv, "a:r:", long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'a':
|
||||
{
|
||||
if (opt_auth)
|
||||
free(opt_auth);
|
||||
int l = strlen(optarg);
|
||||
cBase64Encoder Base64((uchar*) optarg, l, l * 4 / 3 + 3);
|
||||
const char *s = Base64.NextLine();
|
||||
if (s)
|
||||
opt_auth = strdup(s);
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
if (opt_remux)
|
||||
free(opt_remux);
|
||||
opt_remux = strdup(optarg);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cPluginStreamdevServer::Start(void)
|
||||
{
|
||||
I18nRegister(PLUGIN_NAME_I18N);
|
||||
if (!StreamdevHosts.Load(STREAMDEVHOSTSPATH, true, true)) {
|
||||
esyslog("streamdev-server: error while loading %s", STREAMDEVHOSTSPATH);
|
||||
fprintf(stderr, "streamdev-server: error while loading %s\n", STREAMDEVHOSTSPATH);
|
||||
if (access(STREAMDEVHOSTSPATH, F_OK) != 0) {
|
||||
fprintf(stderr, " Please install streamdevhosts.conf into the path "
|
||||
"printed above. Without it\n"
|
||||
" no client will be able to access your streaming-"
|
||||
"server. An example can be\n"
|
||||
" found together with this plugin's sources.\n");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!opt_remux)
|
||||
opt_remux = strdup(DEFAULT_EXTERNREMUX);
|
||||
|
||||
cStreamdevServer::Initialize();
|
||||
|
||||
m_Suspend = StreamdevServerSetup.StartSuspended == ssAuto ?
|
||||
!cDevice::PrimaryDevice()->HasDecoder() :
|
||||
StreamdevServerSetup.StartSuspended;
|
||||
return true;
|
||||
}
|
||||
|
||||
void cPluginStreamdevServer::Stop(void)
|
||||
{
|
||||
cStreamdevServer::Destruct();
|
||||
}
|
||||
|
||||
cString cPluginStreamdevServer::Active(void)
|
||||
{
|
||||
if (cStreamdevServer::Active())
|
||||
{
|
||||
static const char *Message = NULL;
|
||||
if (!Message) Message = tr("Streaming active");
|
||||
return Message;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *cPluginStreamdevServer::MainMenuEntry(void)
|
||||
{
|
||||
return !StreamdevServerSetup.HideMenuEntry ? tr("Streamdev Connections") : NULL;
|
||||
}
|
||||
|
||||
cOsdObject *cPluginStreamdevServer::MainMenuAction(void)
|
||||
{
|
||||
return new cStreamdevServerMenu();
|
||||
}
|
||||
|
||||
void cPluginStreamdevServer::MainThreadHook(void)
|
||||
{
|
||||
if (m_Suspend) {
|
||||
cControl::Launch(new cSuspendCtl);
|
||||
m_Suspend = false;
|
||||
}
|
||||
|
||||
cMutexLock lock;
|
||||
#if APIVERSNUM >= 20300
|
||||
cList<cMainThreadHookSubscriber>& subs = cMainThreadHookSubscriber::Subscribers(lock);
|
||||
#else
|
||||
const cList<cMainThreadHookSubscriber>& subs = cMainThreadHookSubscriber::Subscribers(lock);
|
||||
#endif
|
||||
for (cMainThreadHookSubscriber *s = subs.First(); s; s = subs.Next(s))
|
||||
s->MainThreadHook();
|
||||
}
|
||||
|
||||
cMenuSetupPage *cPluginStreamdevServer::SetupMenu(void)
|
||||
{
|
||||
return new cStreamdevServerMenuSetupPage;
|
||||
}
|
||||
|
||||
bool cPluginStreamdevServer::SetupParse(const char *Name, const char *Value)
|
||||
{
|
||||
return StreamdevServerSetup.SetupParse(Name, Value);
|
||||
}
|
||||
|
||||
const char **cPluginStreamdevServer::SVDRPHelpPages(void)
|
||||
{
|
||||
static const char *HelpPages[]=
|
||||
{
|
||||
"LSTC\n"
|
||||
" List connected clients\n",
|
||||
"DISC client_id\n"
|
||||
" Disconnect a client\n",
|
||||
NULL
|
||||
};
|
||||
return HelpPages;
|
||||
}
|
||||
|
||||
cString cPluginStreamdevServer::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
|
||||
{
|
||||
|
||||
cString reply = NULL;
|
||||
if (!strcasecmp(Command, "LSTC"))
|
||||
{
|
||||
reply = "";
|
||||
cThreadLock lock;
|
||||
#if APIVERSNUM >= 20300
|
||||
cList<cServerConnection>& clients = cStreamdevServer::Clients(lock);
|
||||
#else
|
||||
const cList<cServerConnection>& clients = cStreamdevServer::Clients(lock);
|
||||
#endif
|
||||
cServerConnection *s = clients.First();
|
||||
if (!s)
|
||||
{
|
||||
reply = "no client connected";
|
||||
ReplyCode = 550;
|
||||
} else
|
||||
{
|
||||
for (; s; s = clients.Next(s))
|
||||
{
|
||||
reply = cString::sprintf("%s%p: %s\n", (const char*) reply, s, (const char *) s->ToText());
|
||||
}
|
||||
ReplyCode = 250;
|
||||
}
|
||||
} else if (!strcasecmp(Command, "DISC"))
|
||||
{
|
||||
void *client = NULL;
|
||||
if (sscanf(Option, "%p", &client) != 1 || !client)
|
||||
{
|
||||
reply = "invalid client handle";
|
||||
ReplyCode = 501;
|
||||
} else
|
||||
{
|
||||
cThreadLock lock;
|
||||
#if APIVERSNUM >= 20300
|
||||
cList<cServerConnection>& clients = cStreamdevServer::Clients(lock);
|
||||
#else
|
||||
const cList<cServerConnection>& clients = cStreamdevServer::Clients(lock);
|
||||
#endif
|
||||
cServerConnection *s = clients.First();
|
||||
for (; s && s != client; s = clients.Next(s));
|
||||
|
||||
if (!s)
|
||||
{
|
||||
reply = "client not found";
|
||||
ReplyCode = 501;
|
||||
} else
|
||||
{
|
||||
s->Close();
|
||||
reply = "client disconnected";
|
||||
ReplyCode = 250;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
bool cPluginStreamdevServer::Service(const char *Id, void *Data)
|
||||
{
|
||||
if (strcmp(Id, "StreamdevServer::ClientCount-v1.0") == 0) {
|
||||
if (Data) {
|
||||
int *count = (int *) Data;
|
||||
cThreadLock lock;
|
||||
*count = cStreamdevServer::Clients(lock).Count();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
VDRPLUGINCREATOR(cPluginStreamdevServer); // Don't touch this!
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: streamdev-server.h,v 1.4 2007/02/19 12:08:16 schmirl Exp $
|
||||
* $Id: streamdev-server.h,v 1.2 2010/07/19 13:49:32 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEVSERVER_H
|
||||
@@ -7,11 +7,30 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <vdr/tools.h>
|
||||
#include <vdr/plugin.h>
|
||||
|
||||
class cMainThreadHookSubscriber: public cListObject {
|
||||
private:
|
||||
static cList<cMainThreadHookSubscriber> m_Subscribers;
|
||||
static cMutex m_Mutex;
|
||||
public:
|
||||
#if APIVERSNUM >= 20300
|
||||
static cList<cMainThreadHookSubscriber>& Subscribers(cMutexLock& Lock);
|
||||
#else
|
||||
static const cList<cMainThreadHookSubscriber>& Subscribers(cMutexLock& Lock);
|
||||
#endif
|
||||
|
||||
virtual void MainThreadHook() = 0;
|
||||
|
||||
cMainThreadHookSubscriber();
|
||||
virtual ~cMainThreadHookSubscriber();
|
||||
};
|
||||
|
||||
class cPluginStreamdevServer : public cPlugin {
|
||||
private:
|
||||
static const char *DESCRIPTION;
|
||||
bool m_Suspend;
|
||||
|
||||
public:
|
||||
cPluginStreamdevServer(void);
|
||||
@@ -26,8 +45,12 @@ public:
|
||||
virtual cString Active(void);
|
||||
virtual const char *MainMenuEntry(void);
|
||||
virtual cOsdObject *MainMenuAction(void);
|
||||
virtual void MainThreadHook(void);
|
||||
virtual cMenuSetupPage *SetupMenu(void);
|
||||
virtual bool SetupParse(const char *Name, const char *Value);
|
||||
virtual const char **SVDRPHelpPages(void);
|
||||
virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode);
|
||||
virtual bool Service(const char *Id, void *Data = NULL);
|
||||
};
|
||||
|
||||
#endif // VDR_STREAMDEVSERVER_H
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: streamer.c,v 1.16 2007/09/21 11:45:53 schmirl Exp $
|
||||
* $Id: streamer.c,v 1.21 2010/07/30 10:01:11 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include <vdr/ringbuffer.h>
|
||||
@@ -8,28 +8,32 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include "server/streamer.h"
|
||||
#include "server/suspend.h"
|
||||
#include "server/setup.h"
|
||||
#include "tools/socket.h"
|
||||
#include "tools/select.h"
|
||||
#include "common.h"
|
||||
|
||||
// --- cStreamdevBuffer -------------------------------------------------------
|
||||
|
||||
cStreamdevBuffer::cStreamdevBuffer(int Size, int Margin, bool Statistics, const char *Description):
|
||||
cRingBufferLinear(Size, Margin, Statistics, Description)
|
||||
{
|
||||
}
|
||||
|
||||
// --- cStreamdevWriter -------------------------------------------------------
|
||||
|
||||
cStreamdevWriter::cStreamdevWriter(cTBSocket *Socket,
|
||||
cStreamdevStreamer *Streamer):
|
||||
cThread("streamdev-writer"),
|
||||
m_Streamer(Streamer),
|
||||
m_Socket(Socket),
|
||||
m_Active(false)
|
||||
m_Socket(Socket)
|
||||
{
|
||||
}
|
||||
|
||||
cStreamdevWriter::~cStreamdevWriter()
|
||||
{
|
||||
Dprintf("destructing writer\n");
|
||||
m_Active = false;
|
||||
Cancel(3);
|
||||
if (Running())
|
||||
Cancel(3);
|
||||
}
|
||||
|
||||
void cStreamdevWriter::Action(void)
|
||||
@@ -39,111 +43,142 @@ void cStreamdevWriter::Action(void)
|
||||
int max = 0;
|
||||
uchar *block = NULL;
|
||||
int count, offset = 0;
|
||||
m_Active = true;
|
||||
int timeout = 0;
|
||||
|
||||
SetPriority(-3);
|
||||
sel.Clear();
|
||||
sel.Add(*m_Socket, true);
|
||||
while (m_Active) {
|
||||
while (Running()) {
|
||||
if (block == NULL) {
|
||||
block = m_Streamer->Get(count);
|
||||
offset = 0;
|
||||
// still no data - are we done?
|
||||
if (block == NULL && !m_Streamer->IsReceiving() && timeout++ > 20) {
|
||||
esyslog("streamdev-server: streamer done - writer exiting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (block != NULL) {
|
||||
if (sel.Select(15000) == -1) {
|
||||
if (sel.Select(600) == -1) {
|
||||
if (errno == ETIMEDOUT && timeout++ < 20)
|
||||
continue; // still Running()?
|
||||
esyslog("ERROR: streamdev-server: couldn't send data: %m");
|
||||
break;
|
||||
}
|
||||
timeout = 0;
|
||||
|
||||
if (sel.CanWrite(*m_Socket)) {
|
||||
int written;
|
||||
if ((written = m_Socket->Write(block + offset, count)) == -1) {
|
||||
esyslog("ERROR: streamdev-server: couldn't send data: %m");
|
||||
int pkgsize = count;
|
||||
// SOCK_DGRAM indicates multicast
|
||||
if (m_Socket->Type() == SOCK_DGRAM) {
|
||||
// don't fragment multicast packets
|
||||
// max. payload on standard local ethernet is 1416 to 1456 bytes
|
||||
// and some STBs expect complete TS packets
|
||||
// so let's always limit to 7 * TS_SIZE = 1316
|
||||
if (pkgsize > 7 * TS_SIZE)
|
||||
pkgsize = 7 * TS_SIZE;
|
||||
else
|
||||
pkgsize -= pkgsize % TS_SIZE;
|
||||
}
|
||||
if ((written = m_Socket->Write(block + offset, pkgsize)) == -1) {
|
||||
esyslog("ERROR: streamdev-server: couldn't send %d bytes: %m", pkgsize);
|
||||
break;
|
||||
}
|
||||
|
||||
// statistics
|
||||
if (count > max)
|
||||
max = count;
|
||||
|
||||
offset += written;
|
||||
count -= written;
|
||||
if (count == 0) {
|
||||
|
||||
// less than one TS packet left:
|
||||
// delete what we've written so far and get next chunk
|
||||
if (count < TS_SIZE) {
|
||||
m_Streamer->Del(offset);
|
||||
block = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_Active = false;
|
||||
m_Socket->Close();
|
||||
Dprintf("Max. Transmit Blocksize was: %d\n", max);
|
||||
}
|
||||
|
||||
// --- cRemuxDummy ------------------------------------------------------------
|
||||
|
||||
class cRemuxDummy: public Streamdev::cTSRemux {
|
||||
private:
|
||||
cStreamdevBuffer m_Buffer;
|
||||
public:
|
||||
cRemuxDummy();
|
||||
virtual int Put(const uchar *Data, int Count) { return m_Buffer.Put(Data, Count); }
|
||||
virtual uchar *Get(int& Count) { return m_Buffer.Get(Count); }
|
||||
virtual void Del(int Count) { return m_Buffer.Del(Count); }
|
||||
};
|
||||
|
||||
cRemuxDummy::cRemuxDummy(): m_Buffer(WRITERBUFSIZE, TS_SIZE * 2)
|
||||
{
|
||||
m_Buffer.SetTimeouts(100, 100);
|
||||
}
|
||||
|
||||
// --- cStreamdevStreamer -----------------------------------------------------
|
||||
|
||||
cStreamdevStreamer::cStreamdevStreamer(const char *Name):
|
||||
cStreamdevStreamer::cStreamdevStreamer(const char *Name, const cServerConnection *Connection):
|
||||
cThread(Name),
|
||||
m_Active(false),
|
||||
m_Running(false),
|
||||
m_Writer(NULL),
|
||||
m_RingBuffer(new cRingBufferLinear(STREAMERBUFSIZE, TS_SIZE * 2,
|
||||
true, "streamdev-streamer")),
|
||||
m_SendBuffer(new cRingBufferLinear(WRITERBUFSIZE, TS_SIZE * 2))
|
||||
m_Connection(Connection),
|
||||
m_Remux(new cRemuxDummy()),
|
||||
m_Writer(NULL)
|
||||
{
|
||||
m_RingBuffer->SetTimeouts(0, 100);
|
||||
m_SendBuffer->SetTimeouts(0, 100);
|
||||
}
|
||||
|
||||
cStreamdevStreamer::~cStreamdevStreamer()
|
||||
{
|
||||
Dprintf("Desctructing streamer\n");
|
||||
delete m_RingBuffer;
|
||||
delete m_SendBuffer;
|
||||
delete m_Remux;
|
||||
}
|
||||
|
||||
void cStreamdevStreamer::Start(cTBSocket *Socket)
|
||||
{
|
||||
Dprintf("start streamer\n");
|
||||
Dprintf("start writer\n");
|
||||
m_Writer = new cStreamdevWriter(Socket, this);
|
||||
m_Running = true;
|
||||
Attach();
|
||||
}
|
||||
|
||||
void cStreamdevStreamer::Activate(bool On)
|
||||
{
|
||||
if (On && !m_Active) {
|
||||
Dprintf("activate streamer\n");
|
||||
m_Writer->Start();
|
||||
m_Writer->Start();
|
||||
if (!Active()) {
|
||||
Dprintf("start streamer\n");
|
||||
cThread::Start();
|
||||
}
|
||||
Attach();
|
||||
}
|
||||
|
||||
void cStreamdevStreamer::Stop(void)
|
||||
{
|
||||
if (m_Active) {
|
||||
Dprintf("stopping streamer\n");
|
||||
m_Active = false;
|
||||
Detach();
|
||||
if (Running()) {
|
||||
Dprintf("stop streamer\n");
|
||||
Cancel(3);
|
||||
}
|
||||
if (m_Running) {
|
||||
Detach();
|
||||
m_Running = false;
|
||||
DELETENULL(m_Writer);
|
||||
}
|
||||
Dprintf("stop writer\n");
|
||||
DELETENULL(m_Writer);
|
||||
}
|
||||
|
||||
void cStreamdevStreamer::Action(void)
|
||||
{
|
||||
m_Active = true;
|
||||
while (m_Active) {
|
||||
SetPriority(-3);
|
||||
while (Running()) {
|
||||
int got;
|
||||
uchar *block = m_RingBuffer->Get(got);
|
||||
uchar *block = GetFromReceiver(got);
|
||||
|
||||
if (block) {
|
||||
int count = Put(block, got);
|
||||
if (count)
|
||||
m_RingBuffer->Del(count);
|
||||
else
|
||||
cCondWait::SleepMs(100);
|
||||
DelFromReceiver(count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int cStreamdevStreamer::Put(const uchar *Data, int Count) {
|
||||
return m_Remux->Put(Data, Count);
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user