113 Commits

Author SHA1 Message Date
Frank Schmirler
e2a9b979d3 fixed compilation for VDR 2.3.7 (thanks to Jasmin J) 2017-09-30 21:31:48 +02:00
Frank Schmirler
644078220b added .gitignore (thanks to Jasmin J) 2017-09-30 21:29:27 +02:00
Frank Schmirler
95256a52cf fixed some warnings in libdvbmpeg (thanks to Jasmin J) 2017-09-30 21:27:51 +02:00
Frank Schmirler
b84b7d858c fixed lseek error check in libdvbmpeg 2017-01-20 16:15:19 +01:00
Frank Schmirler
674bb5b331 Streamdev-server compatibility with VDR 2.3.1 (fixes #2249) 2016-03-21 00:28:02 +01:00
Frank Schmirler
d66c635a80 client compatibility with VDR 2.3.1 (refs #2243) 2015-10-05 01:02:13 +02:00
Frank Schmirler
fc52e920ad use cReceiver::SetPriority(...) in VDR 2.1.4+ 2015-10-04 21:41:35 +02:00
Frank Schmirler
84c6f6b6f3 doubled size of client's filter buffer (fixes #2045) 2015-01-24 00:55:39 +01:00
Frank Schmirler
3e06c59196 make sure TimedWrite(...) doesn't return failure after a slow but successful
write operation (refs #2045)
2015-01-24 00:49:51 +01:00
Frank Schmirler
b33d2631df Fixed problems related to VTP filter streaming like ringbuffer overflows,
stuttering or aborting video stream (refs #2045)

Toerless Eckert wrote:

This patch tries to resolve problems in streamdev-client that
can occur when enabling "StreamFilters". Enabling this option
is necessary to receive certain programs with dynamic PIDs such as
some german "regional" broadcast (eg: NDR).

Problem:

Without this fix, the following behavior was observed on a Raspberry
PI running streamdev-0.6.1-git with VDR-2.6.1:

- Buffer overflows of filter data
- Stop/go video on channels
- Total stopping of video

More logs in:

http://www.vdr-portal.de/board16-video-disk-recorder/board55-vdr-plugins/125237-
streamdev-client-filter-daten-streamen-ndr-raspberry-haengt/

Analysis:

VDR expect section data from filters separately from the
main program stream. Historically, it received each filter data
via a separate file descriptor from the DVB card. In the streamdev-client
module, a socketpair is used to feed filter data to the main VDR code.
During certain operations in VDR, such as startup or channel change
(depending also on the speed of initialization of the video output driver),
VDR does not consume the filter data as fast as it is provided by
streamdev-client, resulting in overflow of the default socket buffers
used by streamdev-client.

To add to the problem of overflowing the socketpair buffers, the
streamdev-client code sends several times a second short packets into
the socketpair to determine if the receiving side (VDR) has closed
the socketpair (IsClosed(), CarbageCollect()). This further clogs
up the socketpair() buffer.

The raspberry PI socketpair buffering behavior seems to be the same
as that of other 3.x linux systems, the socket buffer size is by
default 163840, and it can be increased via sysctl net.core.wmem_max.
During startup, it can take up to 10 seconds before VDR will consume
filter data, so the socketpair buffer can fill up with 10 seconds worth
of data.

Solution

1. IsClosed()/CarbageCollect() where removed from client/filter.c
and replaced by explicitly tracking when VDR closes a filter socket.
This alone seems to already resolve the problem of hanging or stop&go
video and seems to be sufficient to receive dynamic-PID channels reliably.

2. filter.c was enhanced to request a larger socket buffer size
if config option FilterSockBufSize is set.

3. If supported (if streamdev-client runs on linux), the socketpair
queue is "flushed" to reduce the amount of "random" packet drop messages
and to rather drop sequential messages.
2015-01-24 00:19:04 +01:00
Frank Schmirler
657c8bc49c Added Polish translation (closes #2038) 2014-12-23 12:40:13 +01:00
Frank Schmirler
5f5fb7953e Converted suspend.dat into proper PES format (closes #2034) 2014-12-22 21:56:41 +01:00
Frank Schmirler
7b17f7725c Implemented GetCurrentlyTunedTransponder() on client (closes #2010) 2014-11-20 14:21:44 +01:00
Frank Schmirler
1ee2049c4d Added service call returning the number of clients (closes #1967) 2014-11-07 23:51:13 +01:00
Frank Schmirler
99b223c55f Added SVDRP commands to list and disconnect clients (closes #1860) 2014-11-07 23:01:08 +01:00
Frank Schmirler
7df7185e1a fixed recplayer issues with large TS files (>4GB) 2014-10-24 12:29:49 +02:00
Frank Schmirler
dd556ee7fd Don't abort externremux when internal read buffer is empty 2014-09-19 15:23:14 +02:00
Frank Schmirler
58f0348578 Show old VDR PES recordings in HTTP menu only if PES mode is selected 2014-09-08 22:35:18 +02:00
Frank Schmirler
e83c9d92aa Implemented remuxing of recordings (closes #1892) 2014-09-07 02:48:07 +02:00
Frank Schmirler
520adaf3da Implemented remuxing when replaying recordings 2014-09-07 02:30:06 +02:00
Frank Schmirler
71c26e7455 Merged duplicate 2014-09-02 08:54:28 +02:00
Frank Schmirler
e8629b5ec6 Make ChannelChange retune only if CA IDs changed (closes #1767) 2014-08-31 00:20:35 +02:00
Frank Schmirler
2d919997a8 Moved remux from livestreamer to streamer 2014-08-10 15:57:16 +02:00
Frank Schmirler
703dffa0cb Updated HISTORY 2014-08-09 23:07:51 +02:00
Frank Schmirler
bdee8c1923 Implemented VDR 2.1.4 cStatus::ChannelChange(...) 2014-08-09 23:04:45 +02:00
Frank Schmirler
83262870d5 Call detach only if receiver is attached 2014-08-09 22:59:31 +02:00
Frank Schmirler
888cf0a2f8 Try changing to other device when receiver got detached 2014-06-23 23:30:55 +02:00
Frank Schmirler
1dc1423429 In TSPIDS mode, create and attach receiver with empty pid list to occupy device 2014-06-23 23:28:36 +02:00
Frank Schmirler
5a173b0b21 No need for Detach/Attach in SwitchDevice as it is only called when detached. 2014-06-23 23:27:10 +02:00
Frank Schmirler
3e9e7f7de6 Setting streamer to NULL if TUNE fails should not be necessary 2014-06-23 23:21:56 +02:00
Frank Schmirler
62fa951c61 Fixed initialization of m_StreamType 2014-06-23 23:13:30 +02:00
Frank Schmirler
bfbf19decc Dropped unused function and parameter. 2014-06-22 11:21:02 +02:00
Frank Schmirler
e555017565 Revised class responsibilities: Moved live TV related functions to livestreamer 2014-06-07 00:24:27 +02:00
Frank Schmirler
7be0c81a81 Moved streamer from each individual connection class to cServerConnection 2014-05-18 18:16:51 +02:00
Frank Schmirler
2cdf160648 Configurable buffer for live TV 2014-05-18 15:24:24 +02:00
Frank Schmirler
54440cb080 Typo 2014-05-18 15:16:13 +02:00
Frank Schmirler
40704cdcbc Release 0.6.1 2013-11-28 20:59:04 +01:00
Frank Schmirler
5fcd6eca69 Updated Slovak translation (closes #1626) 2013-11-25 12:44:22 +01:00
Frank Schmirler
a4a774c6ce Updated Finnish translation (thanks to Rolf Ahrenberg) 2013-11-25 12:40:30 +01:00
Frank Schmirler
c18f7d47e7 Disabled PS remuxer which is said to produce anything but PS 2013-11-17 11:20:42 +01:00
Frank Schmirler
1439b016b3 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.
2013-11-17 10:52:12 +01:00
Frank Schmirler
f194ca2074 Added option to suspend live TV when the server starts (closes #1296) 2013-11-02 16:58:17 +01:00
Frank Schmirler
1d4a7e06b4 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)
2013-11-01 15:33:19 +01:00
Frank Schmirler
458a21a62a Fixed channel switch issues with priority > 0 2013-10-30 21:18:56 +01:00
Frank Schmirler
69b654d539 Removed noisy debug messages 2013-10-21 22:21:12 +02:00
Frank Schmirler
5e5070edc0 Fixed HTTP menu destruction 2013-10-20 17:40:22 +02:00
Frank Schmirler
dfc8339c9e API change of VDR 2.1.2 2013-10-20 00:10:39 +02:00
Frank Schmirler
a9c2adb565 Fixed priority handling, messed up when adding multi-device support 2013-10-19 01:22:45 +02:00
Frank Schmirler
8c5859ed4a Added HTTP "Server" header 2013-10-02 00:03:39 +02:00
Frank Schmirler
e1ba17ca21 Ignore dummy file extensions (.ts, .vob, .vdr) when parsing HTTP URIs 2013-10-01 23:52:03 +02:00
Frank Schmirler
d3df5d07a1 Redesigned pos= parameter patch for streaming recordings and added missing
bits like HEAD and resume.# support
2013-10-01 23:47:25 +02:00
Frank Schmirler
c92de13d06 Select start position for replaying a recording by parameter pos=
Based on offset_5.diff from hivdr@vdrportal with the following modifications:
- indenting
- replaced isyslog with Dprintf
- left out HTTP header "Server:" for the moment
2013-09-27 17:33:18 +02:00
Frank Schmirler
d7652d89ca Start cSuspendCtl hidden or it will prevent idle shutdown.
As long as a cControl is not hidden, cControl::Control() will return a value
and LastInteract is updated in the VDR main loop.
2013-07-16 13:12:20 +02:00
Frank Schmirler
b25e53c867 Fixed recordings menu inode numbers: ino_t is a long long on some systems 2013-07-16 13:07:58 +02:00
Frank Schmirler
329129d9c1 Updated Slovak translation (closes #1293) 2013-03-19 12:08:01 +01:00
Frank Schmirler
75ee90a974 Added missing install target (fixes #1274) 2013-02-28 08:23:23 +01:00
Frank Schmirler
08198729ac Adapted Makefiles to VDR 1.7.36+ (thanks to macmenot). Old makefiles have
been renamed to Makefile-1.7.33 (fixes #1199)
2013-02-27 13:11:23 +01:00
Frank Schmirler
10db11acd9 API changes of VDR 1.7.38 (thanks to mal@vdr-developer) 2013-02-18 12:43:02 +01:00
Frank Schmirler
f58086a83a Added simple recordings menu in HTTP server 2013-02-03 12:40:46 +01:00
Frank Schmirler
d3dd72072c Restructured menuHTTP classes 2013-02-03 11:02:25 +01:00
Frank Schmirler
9bbb74b7fd Added RSS format for HTTP menus 2013-02-02 23:28:55 +01:00
Frank Schmirler
176df8341d Recordings can now also be selected by struct stat "st_dev:st_ino.rec" 2013-02-02 22:34:47 +01:00
Frank Schmirler
138580f284 API change of 1.7.28: missed one affected line leading to crashed in VTP
(refs #1226)
2013-02-01 22:28:46 +01:00
Frank Schmirler
525edc1ccf Implemented multi-device support for streamdev client (closes #1207) 2013-01-29 00:02:17 +01:00
Frank Schmirler
9135cde712 Basic support for HTTP streaming of recordings 2012-12-16 13:29:15 +01:00
Frank Schmirler
0cf406ed3a Added #include <string> 2012-12-16 13:21:45 +01:00
Frank Schmirler
1866716471 Close writer when streamer is finished 2012-12-16 13:21:19 +01:00
Frank Schmirler
50d249c62e Don't abort VTP connection if filter stream is broken 2012-12-16 13:12:42 +01:00
Frank Schmirler
0fb7076192 Use std::map at() is not available in old libs. Use find() 2012-12-16 13:09:29 +01:00
Frank Schmirler
f5da0ea1fc Restructured cStreamdevStreamer: Moved inbound buffer into actual subclass. 2012-12-16 12:40:44 +01:00
Frank Schmirler
0677f48329 In cStreamdevStreamer dropped Activate(bool) and moved its code into Start() 2012-12-04 17:21:36 +01:00
Frank Schmirler
eaf9321c4c API change of VDR 1.7.28 2012-12-04 17:19:35 +01:00
Frank Schmirler
83e9f3250f Moved cStreamdevFilterStreamer to livefilter.[hc] 2012-11-24 23:35:10 +01:00
Frank Schmirler
c267b585fd - 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
2012-11-16 02:00:09 +01:00
Frank Schmirler
be9da74958 Support HTTP HEAD requests with external remuxer 2012-11-02 09:09:15 +01:00
Frank Schmirler
e7bcc9349c Fixed always using priority 0 for HTTP HEAD requests 2012-11-02 09:07:19 +01:00
Frank Schmirler
b614fa0ec3 Start writer right after creating it 2012-11-02 09:02:22 +01:00
Frank Schmirler
84db6323a6 Corrected typos (thanks to Ville Skytt) 2012-06-28 17:17:59 +02:00
Frank Schmirler
281105f0c7 Fixed compiler error in client/device.c with VDR < 1.7.22 (reported by Uwe@vdrportal) 2012-06-22 08:54:47 +02:00
Frank Schmirler
80e40d4260 Updated Italian translation (thanks to Diego Pierotto) 2012-06-13 08:55:33 +02:00
Frank Schmirler
5cfa16c402 Added DeviceName() and DeviceType() to client device. The server IP and the
number of the device used on the server are returned respectively.
2012-06-07 19:23:14 +02:00
Frank Schmirler
af48d11b18 Release 0.6.0 2012-05-29 12:07:54 +02:00
Frank Schmirler
744dc6792c Reimplemented some client device methods 2012-05-29 01:25:47 +02:00
Frank Schmirler
16f8c75918 Dropped m_UpdatePriority 2012-05-27 01:31:45 +02:00
Frank Schmirler
fffd5aef4f Proper fix for "client sends ABRT after TUNE". Obsoletes many hacks in client 2012-05-21 00:42:08 +02:00
Frank Schmirler
6389c5fd90 Added CLOCK_MONOTONIC timestamp and thread id to Dprintf 2012-05-21 00:37:41 +02:00
Frank Schmirler
6a47e20435 Silenced warning (thanks to Rolf Ahrenberg) 2012-05-17 17:01:15 +02:00
Frank Schmirler
12b48591be Updated Finnish translation (thanks to Rolf Ahrenberg) 2012-05-17 16:59:42 +02:00
Frank Schmirler
ab26b4770a Updated README, dropped obsolete patches. 2012-05-12 13:41:45 +02:00
Frank Schmirler
00b7318a7b Cleaned up HISTORY file after merge 2012-05-12 13:05:42 +02:00
Frank Schmirler
c3ac597623 Replaced server-side suspend modes with priority based precedence handling 2012-05-12 12:58:42 +02:00
Frank Schmirler
ae634538f8 Dropped compatibility of streamdev-server with VDR < 1.7.25 2012-05-12 12:58:42 +02:00
Frank Schmirler
783b261bcb Release 0.5.2 2012-05-12 12:48:36 +02:00
Frank Schmirler
316ac3344d Use fileno() to retrieve the fd from a FILE structure (fixes #958) 2012-05-03 08:30:32 +02:00
Frank Schmirler
8719007f5a New special meaning "show current channel" when channel 0 is requested.
Applies to HTTP streaming only (thanks to Rolf Ahrenberg)
2012-04-21 22:47:36 +02:00
Frank Schmirler
2e8aefd2fe Added streamdev-client support for upcoming streamdev-server versions
with purely priority driven precedence.
2012-04-21 22:28:58 +02:00
Frank Schmirler
a1797719de Using SetOccupied() won't work as it isn't considered in GetDevice(). Trying
to compensate the loss of SetAvoidDevice() with streamdevs CheckConnection(),
ignoring the current live TV device. If a new device is returned it is just
switched to the new channel. Hopefully the main loop will pick it up later,
after streamdev switched aways live TV.
2012-03-11 09:41:33 +01:00
Frank Schmirler
5a3c535778 API change of VDR 1.7.26: Use "occupied" instead of "avoid device". 2012-03-10 23:44:51 +01:00
Frank Schmirler
173d2cbb7a Fixed ProvidesChannel() on client always returning true since the new timeout
option has been added.
2012-03-10 23:28:53 +01:00
Frank Schmirler
83b05a6292 Updated Finnish translation (thanks to Rolf Ahrenberg) 2012-03-06 15:27:12 +01:00
Frank Schmirler
a63f7247cb With VDR 1.7.25 priorities down to -99 will be used 2012-03-04 01:20:57 +01:00
Frank Schmirler
26af4459d8 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.
2012-03-04 01:15:40 +01:00
Frank Schmirler
3da6ae734e Timout for network operations now configurable in streamdev-client setup 2012-03-03 23:39:30 +01:00
Frank Schmirler
c1dc1453c5 Added timeout to Commit() 2012-03-03 23:39:30 +01:00
Frank Schmirler
a047fc7d32 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)
2012-03-01 09:01:37 +01:00
Frank Schmirler
229e8fbfff Update of po headers and Finnish translation (thanks to Rolf Ahrenberg) 2011-12-13 12:59:31 +01:00
Frank Schmirler
ba7c61fb39 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.
2011-12-11 17:03:09 +01:00
Frank Schmirler
6a971b9145 Added missing phrase 2011-12-11 11:35:12 +01:00
Frank Schmirler
3440072e7e API change of VDR 1.7.22 2011-12-09 09:05:09 +01:00
Frank Schmirler
59c6558ce3 VDR 1.7.22 obsoletes cap_net_raw patch.
Added cap_net_raw patch for VDR 1.7.5 - 1.7.21.
2011-12-09 09:04:00 +01:00
Frank Schmirler
d93ca82bd1 Update and UTF-8 conversion of Finnish po files (thanks to Rolf Ahrenberg) 2011-12-08 13:11:43 +01:00
Frank Schmirler
ffb8707118 Added "Hide mainmenu entry" option on server (thanks to Rolf Ahrenberg) 2011-12-08 13:07:17 +01:00
Frank Schmirler
afe255aa0b 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.

Squashed commit of the following:

commit 7175d7de91
Author: Frank Schmirler <vdr@schmirler.de>
Date:   Sun Nov 27 11:51:26 2011 +0100

    Updated README

commit 94aef85adc
Author: Frank Schmirler <vdr@schmirler.de>
Date:   Sun Nov 27 11:32:16 2011 +0100

    Moved "closing connection" log message to overload of cTBSocket::Close() in
    cServerConnection.

commit 9b91301d94
Author: Frank Schmirler <vdr@schmirler.de>
Date:   Fri Nov 25 00:24:37 2011 +0100

    Don't keep a pointer to the connection in components MulticastGroup
    structure as the connection may now be deleted from outside via menu.

commit 7347e24123
Author: Frank Schmirler <vdr@schmirler.de>
Date:   Thu Nov 24 23:45:59 2011 +0100

    Fixed missing Display() call after disconnecting a client.

commit c652e8fa81
Author: Frank Schmirler <vdr@schmirler.de>
Date:   Tue Nov 22 01:15:09 2011 +0100

    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.
2011-11-28 16:23:57 +01:00
88 changed files with 5486 additions and 3453 deletions

25
.gitignore vendored Normal file
View 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

View File

@@ -39,6 +39,9 @@ Rolf Ahrenberg
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
@@ -116,9 +119,10 @@ Jori Hamalainen
for extensive testing while making stream compatible to Network Media Tank
for adding Network Media Tank browser support to HTML pages
owagner
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
@@ -198,3 +202,60 @@ Ville Skytt
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

128
HISTORY
View File

@@ -1,6 +1,134 @@
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.

View File

@@ -1,57 +1,44 @@
#
# Makefile for a Video Disk Recorder plugin
#
# $Id: Makefile,v 1.23 2010/08/02 10:36:59 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.
# 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
# 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 version number of VDR (taken from VDR's "config.h"):
### The compiler options:
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 }')
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:
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
-include $(PLGCFG)
### 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)
LOCDIR := $(shell cd $(LOCDIR) >/dev/null 2>&1 && pwd)
export
unexport PLUGIN
@@ -63,6 +50,7 @@ PACKAGE = vdr-$(ARCHIVE)
### Includes and Defines (add further entries here):
INCLUDES += -I$(VDRDIR)/include -I..
export INCLUDES
DEFINES += -D_GNU_SOURCE
@@ -75,7 +63,7 @@ endif
### The main target:
.PHONY: all client server dist clean
.PHONY: all client server install install-client install-server dist clean
all: client server
### Targets:
@@ -83,20 +71,28 @@ all: client server
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
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

108
Makefile-1.7.33 Normal file
View 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

254
README
View File

@@ -101,13 +101,17 @@ as otherwise -r will be passed to VDR and not to streamdev.
2.1 Compatibility:
------------------
This version is not compatible to VDR releases older than 1.5.9. Take one of
the streamdev-0.4.x releases if you are running at least VDR 1.4.x. For older
VDRs 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 Compiling:
--------------
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 ;)
cd vdr-1.X.X/PLUGINS/src
tar xvfz vdr-streamdev-0.5.0.tgz
ln -s streamdev-0.5.0 streamdev
@@ -130,6 +134,24 @@ 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)
@@ -175,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:
----------------------
@@ -207,7 +245,6 @@ 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)
EXT Pass stream through external script (e.g. for converting with mencoder)
@@ -225,16 +262,18 @@ streams directly like this:
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.
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 'EXT'. 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
@@ -270,7 +309,7 @@ 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
inbetween. Also note that the client must not run on the streamdev-server
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
@@ -280,16 +319,11 @@ 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.
If you pass the -u option to VDR, it will drop almost all priviledges before
streamdev is even loaded. Apply vdr-cap_net_raw.diff to keep VDR from dropping
the CAP_NET_RAW capability required to bind the IGMP socket. The patch is part
of streamdev's source distribution. Check the patches subdirectory. There's no
need to patch VDR if it is kept running as root (not recommended).
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 adresse "0.0.0.0".
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
@@ -329,6 +363,13 @@ 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.
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:
----------------------------
@@ -337,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
@@ -348,66 +389,83 @@ 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.
With maximum 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".
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.
If you are running at least VDR 1.7.0, 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). From VDR 1.7.15 on, 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).
"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,
Note that streamdev-client acts similar to a DVB card. It is possible to receive
multiple channels simultaneously, but only from the same transponder. Just add
additional instances of streamdev-client and you will be able to receive as many
transponders at a time. The same trick allows a client to receive channels from
different servers. To create an additional instance, copy the streamdev-client
binary to a different name (e.g. streamdev-client2):
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
@@ -513,14 +571,10 @@ The script should perform the following steps (pseudocode):
6. Known Problems:
------------------
* There have been reports that channel switching with VDR 1.5.x/1.6.x clients
sometimes fails. Current version includes a workaround which seems to work, but
YMMV ;)
* Viewing encrypted channels became an issue with VDR's new CAM handling code.
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:
* 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
@@ -530,9 +584,9 @@ 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-intcamdevices.patch" or patch
"patches/vdr-1.6.0-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.
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.

View File

@@ -1,14 +1,18 @@
#
# Makefile for a Video Disk Recorder plugin
#
# $Id: Makefile,v 1.2 2010/07/19 13:49:25 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-client
### The name of the shared object file:
SOFILE = libvdr-$(PLUGIN).so
### Includes and Defines (add further entries here):
DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
@@ -22,8 +26,7 @@ CLIENTOBJS = $(PLUGIN).o \
### The main target:
.PHONY: all i18n dist clean
all: libvdr-$(PLUGIN).so i18n
all: $(SOFILE) i18n
### Implicit rules:
@@ -34,51 +37,47 @@ all: libvdr-$(PLUGIN).so i18n
MAKEDEP = $(CXX) -MM -MG
DEPFILE = .dependencies
$(DEPFILE): Makefile
@$(MAKEDEP) $(DEFINES) $(INCLUDES) $(CLIENTOBJS:%.o=%.c) $(COMMONOBJS:%.o=%.c) > $@
@$(MAKEDEP) $(CXXFLAGS) $(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))))))
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 --msgid-bugs-address='<http://www.vdr-developer.org/mantisbt/>' -o $@ $^
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 $@ $<
msgmerge -U --no-wrap --no-location --backup=none -q -N $@ $<
@touch $@
$(I18Nmsgs): $(LOCALEDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
@mkdir -p $(dir $@)
cp $< $@
$(I18Nmsgs): $(DESTDIR)$(LOCDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
install -D -m644 $< $@
i18n: $(I18Nmsgs)
.PHONY: i18n
i18n: $(I18Nmo) $(I18Npot)
install-i18n: $(I18Nmsgs)
### Targets:
libvdr-$(PLUGIN).so: $(CLIENTOBJS) $(COMMONOBJS) ../tools/sockettools.a
$(SOFILE): $(CLIENTOBJS) $(COMMONOBJS) ../tools/sockettools.a
$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $^ -o $@
%.so:
$(CXX) $(CXXFLAGS) -shared $^ -o $@
@cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION)
install-lib: $(SOFILE)
install -D $^ $(DESTDIR)$(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
install: install-lib install-i18n
clean:
@-rm -f $(COMMONOBJS) $(CLIENTOBJS) $(DEPFILE) $(PODIR)/*.mo $(PODIR)/*.pot *.so *.tgz core* *~
@-rm -f $(PODIR)/*.mo $(PODIR)/*.pot
@-rm -f $(COMMONOBJS) $(CLIENTOBJS) $(DEPFILE) *.so *.tgz core* *~

84
client/Makefile-1.7.33 Normal file
View 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* *~

View File

@@ -19,32 +19,38 @@
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_Priority = -1;
m_DvrClosed = true;
}
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);
@@ -52,6 +58,7 @@ cStreamdevDevice::~cStreamdevDevice() {
StopSectionHandler();
DELETENULL(m_Filters);
DELETENULL(m_TSBuffer);
delete m_ClientSocket;
}
#if APIVERSNUM >= 10700
@@ -70,27 +77,29 @@ 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)
&& Channel->Ca() == CA_FTA
&& m_Channel->Ca() == CA_FTA)
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 (StreamdevClientSetup.MinPriority <= StreamdevClientSetup.MaxPriority)
{
@@ -105,12 +114,41 @@ bool cStreamdevDevice::ProvidesChannel(const cChannel *Channel, int Priority,
return false;
}
if (ClientSocket.DataSocket(siLive) != NULL
&& TRANSPONDER(Channel, m_Channel))
res = true;
else {
res = prio && ClientSocket.ProvidesChannel(Channel, Priority);
ndr = true;
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)
@@ -121,139 +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;
m_UpdatePriority = ClientSocket.SupportsPrio();
if (LiveView)
return false;
if (ClientSocket.DataSocket(siLive) != NULL
&& TRANSPONDER(Channel, m_Channel)
&& Channel->Ca() == CA_FTA
&& m_Channel->Ca() == CA_FTA)
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;
m_UpdatePriority = ClientSocket.SupportsPrio();
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");
// 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);
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;
}
@@ -275,67 +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;
}
void cStreamdevDevice::UpdatePriority(void) {
if (m_Device) {
m_Device->Lock();
if (m_Device->m_UpdatePriority && ClientSocket.DataSocket(siLive)) {
int Priority = m_Device->Priority();
if (m_Device == cDevice::ActualDevice() && Priority < Setup.PrimaryLimit)
Priority = Setup.PrimaryLimit;
if (m_Device->m_Priority != Priority && ClientSocket.SetPriority(Priority))
m_Device->m_Priority = Priority;
}
m_Device->Unlock();
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 (ClientSocket.DataSocket(siLive) != NULL)
ClientSocket.GetSignal(&strength, NULL);
if (m_ClientSocket->DataSocket(siLive) != NULL)
m_ClientSocket->GetSignal(&strength, NULL, NULL);
return strength;
}
int cStreamdevDevice::SignalQuality(void) const {
int quality = -1;
if (ClientSocket.DataSocket(siLive) != NULL)
ClientSocket.GetSignal(NULL, &quality);
if (m_ClientSocket->DataSocket(siLive) != NULL)
m_ClientSocket->GetSignal(NULL, &quality, NULL);
return quality;
}

View File

@@ -17,22 +17,22 @@ class cTBString;
class cStreamdevDevice: public cDevice {
private:
bool m_Disabled;
cClientSocket *m_ClientSocket;
const cChannel *m_Channel;
cTSBuffer *m_TSBuffer;
cStreamdevFilters *m_Filters;
int m_Pids;
int m_Priority;
bool m_UpdatePriority;
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);
virtual bool HasLock(int TimeoutMs)
#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;
@@ -45,6 +45,7 @@ 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);
@@ -61,15 +62,21 @@ public:
#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 void UpdatePriority(void);
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

View File

@@ -6,20 +6,28 @@
#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);
@@ -29,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; }
@@ -47,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)
@@ -58,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");
}
@@ -67,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) {
@@ -94,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
}
}
}
@@ -123,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;
}
@@ -154,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)
@@ -198,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;
@@ -251,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
@@ -261,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);
@@ -273,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 {

View File

@@ -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

View File

@@ -6,15 +6,49 @@
msgid ""
msgstr ""
"Project-Id-Version: streamdev 0.5.0\n"
"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
"POT-Creation-Date: 2011-02-16 08:49+0100\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: <vdr@linuxtv.org>\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"
@@ -26,27 +60,3 @@ msgstr "Server ist pausiert"
msgid "Couldn't suspend Server!"
msgstr "Konnte Server nicht pausieren!"
msgid "Hide Mainmenu Entry"
msgstr "Hauptmenüeintrag verstecken"
msgid "Start Client"
msgstr "Client starten"
msgid "Remote IP"
msgstr "IP der Gegenseite"
msgid "Remote Port"
msgstr "Port der Gegenseite"
msgid "Filter Streaming"
msgstr "Filter-Daten streamen"
msgid "Minimum Priority"
msgstr "Minimale Priorität"
msgid "Maximum Priority"
msgstr "Maximale Priorität"
msgid "Broadcast Systems / Cost"
msgstr "Empfangssysteme / Kosten"

View File

@@ -6,15 +6,49 @@
msgid ""
msgstr ""
"Project-Id-Version: streamdev 0.5.0\n"
"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
"POT-Creation-Date: 2010-06-14 13:05+0200\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: <vdr@linuxtv.org>\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"
@@ -26,26 +60,3 @@ msgstr "Servidor en suspensi
msgid "Couldn't suspend Server!"
msgstr "Imposible suspender el servidor!"
msgid "Hide Mainmenu Entry"
msgstr "Ocultar entrada en menú principal"
msgid "Start Client"
msgstr "Iniciar Cliente"
msgid "Remote IP"
msgstr "Indicar IP del Servidor"
msgid "Remote Port"
msgstr "Indicar puerto remoto del Servidor"
msgid "Filter Streaming"
msgstr "Filtrar transmisión"
msgid "Minimum Priority"
msgstr "Prioridad mínima"
msgid "Maximum Priority"
msgstr "Prioridad máxima"

View File

@@ -1,50 +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 <rahrenbe@cc.hut.fi>, 2008
# Rolf Ahrenberg, 2008-
#
msgid ""
msgstr ""
"Project-Id-Version: streamdev 0.5.0\n"
"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
"POT-Creation-Date: 2010-06-14 13:05+0200\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 <rahrenbe@cc.hut.fi>\n"
"Language-Team: <vdr@linuxtv.org>\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=ISO-8859-15\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
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!"
msgid "Hide Mainmenu Entry"
msgstr "Piilota valinta päävalikosta"
msgstr "Piilota valinta päävalikosta"
msgid "Start Client"
msgstr "Käynnistä VDR-asiakas"
msgid "Simultaneously used Devices"
msgstr "Yhtäaikaiset laitteet"
msgid "Remote IP"
msgstr "Etäkoneen IP-osoite"
msgstr "Etäkoneen IP-osoite"
msgid "Remote Port"
msgstr "Etäkoneen portti"
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!"

View File

@@ -6,15 +6,49 @@
msgid ""
msgstr ""
"Project-Id-Version: streamdev 0.5.0\n"
"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
"POT-Creation-Date: 2010-06-14 13:05+0200\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: <vdr@linuxtv.org>\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"
@@ -26,25 +60,3 @@ msgstr "Le serveur est suspendu"
msgid "Couldn't suspend Server!"
msgstr "Impossible de suspendre le serveur!"
msgid "Hide Mainmenu Entry"
msgstr "Masquer dans le menu principal"
msgid "Start Client"
msgstr "Démarrage du client"
msgid "Remote IP"
msgstr "Adresse IP du serveur"
msgid "Remote Port"
msgstr "Port du serveur"
msgid "Filter Streaming"
msgstr "Filtre streaming"
msgid "Minimum Priority"
msgstr ""
msgid "Maximum Priority"
msgstr ""

View File

@@ -8,15 +8,49 @@
msgid ""
msgstr ""
"Project-Id-Version: streamdev 0.5.0\n"
"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
"POT-Creation-Date: 2010-06-14 13:05+0200\n"
"PO-Revision-Date: 2010-06-19 03:58+0100\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: <vdr@linuxtv.org>\n"
"Language-Team: Italian <vdr@linuxtv.org>\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=ISO-8859-15\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"
@@ -28,25 +62,3 @@ msgstr "Server sospeso"
msgid "Couldn't suspend Server!"
msgstr "Impossibile sospendere il server!"
msgid "Hide Mainmenu Entry"
msgstr "Nascondi voce menu principale"
msgid "Start Client"
msgstr "Avvia Client"
msgid "Remote IP"
msgstr "Indirizzo IP del Server"
msgid "Remote Port"
msgstr "Porta Server Remoto"
msgid "Filter Streaming"
msgstr "Filtra trasmissione"
msgid "Minimum Priority"
msgstr "Priorità minima"
msgid "Maximum Priority"
msgstr "Priorità massima"

View File

@@ -6,15 +6,49 @@
msgid ""
msgstr ""
"Project-Id-Version: streamdev 0.5.0\n"
"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
"POT-Creation-Date: 2010-06-14 13:05+0200\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: Lietuvių\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"
@@ -26,25 +60,3 @@ msgstr "Serveris sustabdytas"
msgid "Couldn't suspend Server!"
msgstr "Negali sustabdyti serverio!"
msgid "Hide Mainmenu Entry"
msgstr "Paslėpti pagrindinio meniu įrašą"
msgid "Start Client"
msgstr "Paleisti klientą"
msgid "Remote IP"
msgstr "Nuotolinis IP adresas"
msgid "Remote Port"
msgstr "Nuotolinis portas"
msgid "Filter Streaming"
msgstr "Filtruoti transliavimą"
msgid "Minimum Priority"
msgstr "Minimalus prioritetas"
msgid "Maximum Priority"
msgstr "Maksimalus prioritetas"

63
client/po/pl_PL.po Normal file
View 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!"

View File

@@ -6,15 +6,49 @@
msgid ""
msgstr ""
"Project-Id-Version: streamdev 0.5.0\n"
"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
"POT-Creation-Date: 2010-06-14 13:05+0200\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: <vdr@linuxtv.org>\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 ÚÛØÕÝâ"
@@ -26,25 +60,3 @@ msgstr "
msgid "Couldn't suspend Server!"
msgstr "ÝÕ ÜÞÓã ÞáâÐÝÞÒØâì áÕàÒÕà"
msgid "Hide Mainmenu Entry"
msgstr "ÁßàïâÐâì Ò ÓÛÐÒÝÞÜ ÜÕÝî"
msgid "Start Client"
msgstr "ÁâÐàâ ÚÛØÕÝâÐ"
msgid "Remote IP"
msgstr "ÃÔÐÛÕÝÝëÙ IP"
msgid "Remote Port"
msgstr "ÃÔÐÛÕÝÝëÙ ßÞàâ"
msgid "Filter Streaming"
msgstr "ÄØÛìâà ßÞâÞÚÐ"
msgid "Minimum Priority"
msgstr ""
msgid "Maximum Priority"
msgstr ""

50
client/po/sk_SK.po Normal file → Executable file
View File

@@ -6,34 +6,23 @@
msgid ""
msgstr ""
"Project-Id-Version: streamdev_SK\n"
"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
"POT-Creation-Date: 2010-06-14 13:05+0200\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 "VTP Streaming Client"
msgstr "VTP prúdový klient"
msgid "Suspend Server"
msgstr "Server pozastavený"
msgid "Server is suspended"
msgstr "Server je doèasne preru¹ený"
msgid "Couldn't suspend Server!"
msgstr "Nepodarilo sa pozastavi» Server!"
msgid "Hide Mainmenu Entry"
msgstr "Schova» polo¾ku v hlavnom menu"
msgid "Start Client"
msgstr "Spusti» Klienta"
msgid "Simultaneously used Devices"
msgstr "Súbe¾ne pou¾íva» zariadenia"
msgid "Remote IP"
msgstr "Vzdialená IP"
@@ -41,12 +30,35 @@ msgstr "Vzdialen
msgid "Remote Port"
msgstr "Vzdialený port"
msgid "Timeout (s)"
msgstr "Èasový limit (s)"
msgid "Filter Streaming"
msgstr "filtrova» prúdy"
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"
msgstr "Minimálna priorita"
msgid "Maximum Priority"
msgstr "maximálna priorita"
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»!"

View File

@@ -5,21 +5,28 @@
#include <vdr/menuitems.h>
#include "client/setup.h"
#include "client/device.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;
HideMenuEntry = false;
MinPriority = -1;
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) {
@@ -31,10 +38,13 @@ 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, "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
@@ -42,16 +52,21 @@ bool cStreamdevClientSetup::SetupParse(const char *Name, const char *Value) {
return true;
}
cStreamdevClientMenuSetupPage::cStreamdevClientMenuSetupPage(void) {
cStreamdevClientMenuSetupPage::cStreamdevClientMenuSetupPage(cPluginStreamdevClient *Plugin) {
m_Plugin = Plugin;
m_NewSetup = StreamdevClientSetup;
Add(new cMenuEditBoolItem(tr("Hide Mainmenu Entry"), &m_NewSetup.HideMenuEntry));
Add(new cMenuEditBoolItem(tr("Start Client"), &m_NewSetup.StartClient));
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));
Add(new cMenuEditIntItem (tr("Minimum Priority"), &m_NewSetup.MinPriority, -1, MAXPRIORITY));
Add(new cMenuEditIntItem (tr("Maximum Priority"), &m_NewSetup.MaxPriority, -1, MAXPRIORITY));
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
@@ -64,27 +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("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();
}

View File

@@ -15,8 +15,11 @@ struct cStreamdevClientSetup {
int StartClient;
char RemoteIp[20];
int RemotePort;
int Timeout;
int StreamFilters;
int FilterSockBufSize;
int HideMenuEntry;
int LivePriority;
int MinPriority;
int MaxPriority;
#if APIVERSNUM >= 10700
@@ -26,15 +29,18 @@ struct cStreamdevClientSetup {
extern cStreamdevClientSetup StreamdevClientSetup;
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();
};

View File

@@ -11,19 +11,24 @@
#define MINLOGREPEAT 10 //don't log connect failures too often (seconds)
#include "client/socket.h"
#include "client/setup.h"
#include "common.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();
}
@@ -35,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';
@@ -89,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) {
@@ -100,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);
@@ -126,34 +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);
}
const char *Filters = "";
if(Command("CAPS FILTERS", 220))
Filters = ",FILTERS";
const char *Prio = "";
if(Command("CAPS PRIO", 220)) {
Prio = ",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);
}
isyslog("Streamdev: Connected to server %s:%d using capabilities TSPIDS%s%s",
RemoteIp().c_str(), RemotePort(), Filters, Prio);
return true;
}
@@ -164,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) {
@@ -186,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;
}
@@ -201,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
@@ -217,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]);
@@ -228,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;
@@ -252,41 +271,41 @@ bool cClientSocket::SetChannelDevice(const cChannel *Channel) {
std::string command = (std::string)"TUNE "
+ (const char*)Channel->GetChannelID().ToString();
if (!Command(command, 220, 10000)) {
if (errno == 0)
esyslog("ERROR: Streamdev: Couldn't tune %s:%d to channel %s",
RemoteIp().c_str(), RemotePort(), Channel->Name());
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)) {
if (errno == 0)
esyslog("Streamdev: Failed to update priority on %s:%d", RemoteIp().c_str(),
RemotePort());
if (!Command(command, 220))
return false;
}
m_Priority = Priority;
return true;
}
bool cClientSocket::GetSignal(int *SignalStrength, int *SignalQuality) {
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;
if (!Command("SGNL") || !Expect(220, &buffer)
|| sscanf(buffer.c_str(), "%*d %*d %d:%d", &m_LastSignalStrength, &m_LastSignalQuality) != 2) {
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;
}
@@ -296,6 +315,8 @@ bool cClientSocket::GetSignal(int *SignalStrength, int *SignalQuality) {
*SignalStrength = m_LastSignalStrength;
if (SignalQuality)
*SignalQuality = m_LastSignalQuality;
if (Dev)
*Dev = m_LastDev;
return 0;
}
@@ -305,13 +326,7 @@ bool cClientSocket::SetPid(int Pid, bool On) {
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, RemoteIp().c_str(),
RemotePort());
return false;
}
return true;
return Command(command, 220);
}
bool cClientSocket::SetFilter(ushort Pid, uchar Tid, uchar Mask, bool On) {
@@ -321,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, RemoteIp().c_str(), RemotePort());
return false;
}
return true;
return Command(command, 220);
}
bool cClientSocket::CloseDvr(void) {
@@ -337,27 +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::Quit(void) {
bool res;
m_Abort = true;
if (!IsOpen()) return false;
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());
}
CMD_LOCK;
std::string command("QUIT");
bool res = Send(command) && Receive(command, NULL, NULL, QUIT_TIMEOUT_MS);
Close();
return res;
}
@@ -367,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);
}

View File

@@ -8,6 +8,7 @@
#include <tools/socket.h>
#include "common.h"
#include "client/setup.h"
#include <string>
@@ -20,23 +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);
@@ -50,10 +55,12 @@ public:
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);
bool GetSignal(int *SignalStrength, int *SignalQuality, int *Dev);
bool CloseDvr(void);
bool SuspendServer(void);
bool Quit(void);
@@ -61,6 +68,4 @@ public:
cTBSocket *DataSocket(eSocketId Id) const;
};
extern class cClientSocket ClientSocket;
#endif // VDR_STREAMDEV_CLIENT_CONNECTION_H

View File

@@ -16,7 +16,7 @@
const char *cPluginStreamdevClient::DESCRIPTION = trNOOP("VTP Streaming Client");
cPluginStreamdevClient::cPluginStreamdevClient(void) {
cPluginStreamdevClient::cPluginStreamdevClient(void): m_Devices() {
}
cPluginStreamdevClient::~cPluginStreamdevClient() {
@@ -26,9 +26,13 @@ const char *cPluginStreamdevClient::Description(void) {
return tr(DESCRIPTION);
}
bool cPluginStreamdevClient::Start(void) {
I18nRegister(PLUGIN_NAME_I18N);
cStreamdevDevice::Init();
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;
}
@@ -37,7 +41,7 @@ const char *cPluginStreamdevClient::MainMenuEntry(void) {
}
cOsdObject *cPluginStreamdevClient::MainMenuAction(void) {
if (ClientSocket.SuspendServer())
if (StreamdevClientSetup.StartClient && m_Devices[0]->SuspendServer())
Skins.Message(mtInfo, tr("Server is suspended"));
else
Skins.Message(mtError, tr("Couldn't suspend Server!"));
@@ -45,15 +49,24 @@ cOsdObject *cPluginStreamdevClient::MainMenuAction(void) {
}
cMenuSetupPage *cPluginStreamdevClient::SetupMenu(void) {
return new cStreamdevClientMenuSetupPage;
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) {
cStreamdevDevice::UpdatePriority();
for (int i = 0; i < StreamdevClientSetup.StartClient; i++)
m_Devices[i]->UpdatePriority();
}
VDRPLUGINCREATOR(cPluginStreamdevClient); // Don't touch this!

View File

@@ -9,20 +9,25 @@
#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 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);
};

View File

@@ -10,7 +10,7 @@
using namespace std;
const char *VERSION = "0.5.1-git";
const char *VERSION = "0.6.1-git";
const char cMenuEditIpItem::IpCharacters[] = "0123456789.";

View File

@@ -17,20 +17,23 @@
#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...)
#endif
#if APIVERSNUM >= 10714
#define TRANSPONDER(c1, c2) (c1->Transponder() == c2->Transponder() && !c1->IsSourceType('V'))
#else
#define TRANSPONDER(c1, c2) (c1->Transponder() == c2->Transponder())
#define Dprintf(x...)
#endif
#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)
@@ -46,13 +49,6 @@ enum eStreamType {
st_Count
};
enum eSuspendMode {
smOffer,
smAlways,
smNever,
sm_Count
};
enum eSocketId {
siLive,
siReplay,

View File

@@ -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];
@@ -1334,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];
@@ -1350,7 +1349,6 @@ void tfilter(trans *p)
tpid);
}
flag = cpid[0];
flags = p->packet[3];
if ( flags & ADAPT_FIELD ) {

View File

@@ -756,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;
@@ -787,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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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)

View File

@@ -1,11 +0,0 @@
--- vdr.c.orig 2009-02-13 09:45:55.000000000 +0100
+++ vdr.c 2009-02-13 09:46:24.000000000 +0100
@@ -115,7 +115,7 @@
static bool SetCapSysTime(void)
{
// drop all capabilities except cap_sys_time
- cap_t caps = cap_from_text("= cap_sys_time=ep");
+ cap_t caps = cap_from_text("= cap_sys_time,cap_net_raw=ep");
if (!caps) {
fprintf(stderr, "vdr: cap_from_text failed: %s\n", strerror(errno));
return false;

View File

@@ -3,6 +3,7 @@
#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>
@@ -25,7 +26,7 @@ protected:
virtual void Action(void);
public:
cTSExt(cRingBufferLinear *ResultBuffer, const cServerConnection *Connection, const cChannel *Channel, const int *Apids, const int *Dpids);
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);
@@ -34,7 +35,7 @@ public:
} // namespace Streamdev
using namespace Streamdev;
cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, const cServerConnection *Connection, const cChannel *Channel, const int *Apids, const int *Dpids):
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(-1),
@@ -73,17 +74,24 @@ cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, const cServerConnection *Connect
#define ADDENV(x...) if (asprintf(&env[i++], x) < 0) i--
// add channel ID, name and pids to environment
ADDENV("REMUX_CHANNEL_ID=%s", *Channel->GetChannelID().ToString());
ADDENV("REMUX_CHANNEL_NAME=%s", Channel->Name());
#if APIVERSNUM >= 10701
ADDENV("REMUX_VTYPE=%d", Channel->Vtype());
#endif
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());
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) {
@@ -94,9 +102,16 @@ cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, const cServerConnection *Connect
buffer.clear();
for (const int *pid = Apids; *pid; pid++) {
int j;
for (j = 0; Channel->Apid(j) && Channel->Apid(j) != *pid; j++)
;
(buffer += Channel->Alang(j)) += (*(pid + 1) ? " " : "");
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());
}
@@ -110,14 +125,21 @@ cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, const cServerConnection *Connect
buffer.clear();
for (const int *pid = Dpids; *pid; pid++) {
int j;
for (j = 0; Channel->Dpid(j) && Channel->Dpid(j) != *pid; j++)
;
(buffer += Channel->Dlang(j)) += (*(pid + 1) ? " " : "");
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->Spid(0)) {
if (Channel && Channel->Spid(0)) {
buffer.clear();
for (const int *pid = Channel->Spids(); *pid; pid++)
(buffer += (const char *) itoa(*pid)) += (*(pid + 1) ? " " : "");
@@ -128,6 +150,17 @@ cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, const cServerConnection *Connect
(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
@@ -270,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;
}
@@ -297,8 +330,14 @@ void cTSExt::Put(const uchar *Data, int Count)
}
cExternRemux::cExternRemux(const cServerConnection *Connection, const cChannel *Channel, const int *Apids, const int *Dpids):
m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE, TS_SIZE * 2)),
m_Remux(new cTSExt(m_ResultBuffer, Connection, Channel, Apids, 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);
}

View File

@@ -6,6 +6,7 @@
#include <string>
class cChannel;
class cPatPmtParser;
class cServerConnection;
namespace Streamdev {
@@ -19,6 +20,7 @@ private:
public:
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);

View File

@@ -1,3 +1,5 @@
#ifdef STREAMDEV_PS
#include "remux/ts2ps.h"
#include "server/streamer.h"
#include <vdr/channels.h>
@@ -216,3 +218,4 @@ uchar *cTS2PSRemux::Get(int &Count)
return resultData;
}
#endif

View File

@@ -1,3 +1,5 @@
#ifdef STREAMDEV_PS
#ifndef VDR_STREAMDEV_TS2PSREMUX_H
#define VDR_STREAMDEV_TS2PSREMUX_H
@@ -34,3 +36,5 @@ public:
} // namespace Streamdev
#endif // VDR_STREAMDEV_TS2PSREMUX_H
#endif

View File

@@ -1,14 +1,18 @@
#
# Makefile for a Video Disk Recorder plugin
#
# $Id: Makefile,v 1.2 2010/07/19 13:49:31 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-server
### The name of the shared object file:
SOFILE = libvdr-$(PLUGIN).so
### Includes and Defines (add further entries here):
DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
@@ -22,13 +26,12 @@ SERVEROBJS = $(PLUGIN).o \
componentVTP.o connectionVTP.o \
componentHTTP.o connectionHTTP.o menuHTTP.o \
componentIGMP.o connectionIGMP.o \
streamer.o livestreamer.o livefilter.o recplayer.o \
suspend.o setup.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
all: $(SOFILE) i18n
### Implicit rules:
@@ -39,44 +42,48 @@ all: libvdr-$(PLUGIN).so i18n
MAKEDEP = $(CXX) -MM -MG
DEPFILE = .dependencies
$(DEPFILE): Makefile
@$(MAKEDEP) $(DEFINES) $(INCLUDES) $(SERVEROBJS:%.o=%.c) $(COMMONOBJS:%.o=%.c) > $@
@$(MAKEDEP) $(CXXFLAGS) $(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))))))
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 --msgid-bugs-address='<http://www.vdr-developer.org/mantisbt/>' -o $@ $^
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 $@ $<
msgmerge -U --no-wrap --no-location --backup=none -q -N $@ $<
@touch $@
$(I18Nmsgs): $(LOCALEDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
@mkdir -p $(dir $@)
cp $< $@
$(I18Nmsgs): $(DESTDIR)$(LOCDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
install -D -m644 $< $@
i18n: $(I18Nmsgs)
.PHONY: i18n
i18n: $(I18Nmo) $(I18Npot)
install-i18n: $(I18Nmsgs)
### Targets:
libvdr-$(PLUGIN).so: $(SERVEROBJS) $(COMMONOBJS) \
$(SOFILE): $(SERVEROBJS) $(COMMONOBJS) \
../tools/sockettools.a ../remux/remux.a ../libdvbmpeg/libdvbmpegtools.a
$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $^ -o $@
%.so:
$(CXX) $(CXXFLAGS) -shared $^ -o $@
@cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION)
install-lib: $(SOFILE)
install -D $^ $(DESTDIR)$(LIBDIR)/$^.$(APIVERSION)
install: install-lib install-i18n
clean:
@-rm -f $(COMMONOBJS) $(SERVEROBJS) $(DEPFILE) $(PODIR)/*.mo $(PODIR)/*.pot *.so *.tgz core* *~
@-rm -f $(PODIR)/*.mo $(PODIR)/*.pot
@-rm -f $(COMMONOBJS) $(SERVEROBJS) $(DEPFILE) *.so *.tgz core* *~

82
server/Makefile-1.7.33 Normal file
View 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* *~

View File

@@ -6,6 +6,7 @@
#include "server/componentIGMP.h"
#include "server/connectionIGMP.h"
#include "server/server.h"
#include "server/setup.h"
#ifndef IGMP_ALL_HOSTS
@@ -37,7 +38,6 @@
class cMulticastGroup: public cListObject
{
public:
cConnectionIGMP *connection;
in_addr_t group;
in_addr_t reporter;
struct timeval timeout;
@@ -48,7 +48,6 @@ public:
};
cMulticastGroup::cMulticastGroup(in_addr_t Group) :
connection(NULL),
group(Group),
reporter(0)
{
@@ -106,7 +105,11 @@ 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)
@@ -118,7 +121,12 @@ 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;
@@ -147,7 +155,12 @@ void cComponentIGMP::Destruct(void)
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;
@@ -235,10 +248,7 @@ cServerConnection* cComponentIGMP::ProcessMessage(struct igmp *Igmp, in_addr_t G
group = new cMulticastGroup(Group);
m_Groups.Add(group);
}
if (!group->connection) {
IGMPStartMulticast(group);
conn = group->connection;
}
conn = IGMPStartMulticast(group);
IGMPStartTimer(group, Sender);
if (Igmp->igmp_type == IGMP_V1_MEMBERSHIP_REPORT)
IGMPStartV1HostTimer(group);
@@ -430,20 +440,49 @@ void cComponentIGMP::IGMPSendGroupQuery(cMulticastGroup* Group)
IGMPSendQuery(Group->group, IGMP_LAST_MEMBER_QUERY_INTERVAL_TS);
}
void cComponentIGMP::IGMPStartMulticast(cMulticastGroup* Group)
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);
Group->connection = (cConnectionIGMP*) NewClient();
if (!Group->connection->SetChannel(channel, Group->group)) {
DELETENULL(Group->connection);
#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)
{
if (Group->connection)
Group->connection->Stop();
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();
}
}

View File

@@ -9,8 +9,8 @@
#include <time.h>
#include <vdr/thread.h>
#include "server/component.h"
#include "../common.h"
class cConnectionIGMP;
class cMulticastGroup;
class cComponentIGMP: public cServerComponent, public cThread {
@@ -24,7 +24,11 @@ private:
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);
@@ -42,7 +46,7 @@ private:
void IGMPStartRetransmitTimer(cMulticastGroup* Group);
void IGMPClearRetransmitTimer(cMulticastGroup* Group);
void IGMPSendGroupQuery(cMulticastGroup* Group);
void IGMPStartMulticast(cMulticastGroup* Group);
cServerConnection* IGMPStartMulticast(cMulticastGroup* Group);
void IGMPStopMulticast(cMulticastGroup* Group);
virtual void Action();

View File

@@ -9,59 +9,10 @@
#include <vdr/tools.h>
#include <vdr/thread.h>
#include <vdr/transfer.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
class cSwitchLive {
private:
cMutex mutex;
cCondWait switched;
cDevice *device;
const cChannel *channel;
public:
cDevice* Switch(cDevice *Device, const cChannel *Channel);
void Switch(void);
cSwitchLive(void);
};
cSwitchLive::cSwitchLive(): device(NULL), channel(NULL)
{
}
cDevice* cSwitchLive::Switch(cDevice *Device, const cChannel *Channel)
{
mutex.Lock();
device = Device;
channel = Channel;
mutex.Unlock();
switched.Wait();
return device;
}
void cSwitchLive::Switch(void)
{
mutex.Lock();
if (channel && device) {
cDevice::SetAvoidDevice(device);
if (!Channels.SwitchTo(cDevice::CurrentChannel())) {
if (StreamdevServerSetup.SuspendMode == smAlways) {
Channels.SwitchTo(channel->Number());
Skins.Message(mtInfo, tr("Streaming active"));
}
else {
esyslog("streamdev: Can't receive channel %d (%s) from device %d. Moving live TV to other device failed (PrimaryDevice=%d, ActualDevice=%d)", channel->Number(), channel->Name(), device->CardIndex(), cDevice::PrimaryDevice()->CardIndex(), cDevice::ActualDevice()->CardIndex());
device = NULL;
}
}
// make sure we don't come in here next time
channel = NULL;
switched.Signal();
}
mutex.Unlock();
}
cServerConnection::cServerConnection(const char *Protocol, int Type):
cTBSocket(Type),
m_Protocol(Protocol),
@@ -69,14 +20,14 @@ cServerConnection::cServerConnection(const char *Protocol, int Type):
m_Pending(false),
m_ReadBytes(0),
m_WriteBytes(0),
m_WriteIndex(0)
m_WriteIndex(0),
m_Streamer(NULL)
{
m_SwitchLive = new cSwitchLive();
}
cServerConnection::~cServerConnection()
{
delete m_SwitchLive;
delete(m_Streamer);
}
const cChannel* cServerConnection::ChannelFromString(const char *String, int *Apid, int *Dpid) {
@@ -93,14 +44,31 @@ const cChannel* cServerConnection::ChannelFromString(const char *String, int *Ap
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;
@@ -237,173 +205,14 @@ bool cServerConnection::Respond(const char *Message, bool Last, ...)
return true;
}
#if APIVERSNUM >= 10700
static int GetClippedNumProvidedSystems(int AvailableBits, cDevice *Device)
bool cServerConnection::Close()
{
int MaxNumProvidedSystems = (1 << AvailableBits) - 1;
int NumProvidedSystems = Device->NumProvidedSystems();
if (NumProvidedSystems > MaxNumProvidedSystems) {
esyslog("ERROR: device %d supports %d modulation systems but cDevice::GetDevice() currently only supports %d delivery systems which should be fixed", Device->CardIndex() + 1, NumProvidedSystems, MaxNumProvidedSystems);
NumProvidedSystems = MaxNumProvidedSystems;
}
else if (NumProvidedSystems <= 0) {
esyslog("ERROR: device %d reported an invalid number (%d) of supported delivery systems - assuming 1", Device->CardIndex() + 1, NumProvidedSystems);
NumProvidedSystems = 1;
}
return NumProvidedSystems;
}
#endif
/*
* copy of cDevice::GetDevice(...) but without side effects (not detaching receivers)
*/
cDevice* cServerConnection::CheckDevice(const cChannel *Channel, int Priority, bool LiveView, const cDevice *AvoidDevice)
{
//cDevice *AvoidDevice = avoidDevice;
//avoidDevice = NULL;
// Collect the current priorities of all CAM slots that can decrypt the channel:
int NumCamSlots = CamSlots.Count();
int SlotPriority[NumCamSlots];
int NumUsableSlots = 0;
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
if (CamSlot->ModuleStatus() == msReady) {
if (CamSlot->ProvidesCa(Channel->Caids())) {
if (!ChannelCamRelations.CamChecked(Channel->GetChannelID(), CamSlot->SlotNumber())) {
SlotPriority[CamSlot->Index()] = CamSlot->Priority();
NumUsableSlots++;
}
}
}
}
if (!NumUsableSlots)
return NULL; // no CAM is able to decrypt this channel
}
cDevice *d = NULL;
//cCamSlot *s = NULL;
uint32_t Impact = 0xFFFFFFFF; // we're looking for a device with the least impact
for (int j = 0; j < NumCamSlots || !NumUsableSlots; j++) {
if (NumUsableSlots && SlotPriority[j] > MAXPRIORITY)
continue; // there is no CAM available in this slot
for (int i = 0; i < cDevice::NumDevices(); i++) {
cDevice *device = cDevice::GetDevice(i);
if (device == AvoidDevice)
continue; // we've been asked to skip this device
if (Channel->Ca() && Channel->Ca() <= CA_DVB_MAX && Channel->Ca() != device->CardIndex() + 1)
continue; // a specific card was requested, but not this one
if (NumUsableSlots && !CamSlots.Get(j)->Assign(device, true))
continue; // CAM slot can't be used with this device
bool ndr;
if (device->ProvidesChannel(Channel, Priority, &ndr)) { // this device is basicly able to do the job
if (NumUsableSlots && device->CamSlot() && device->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
// by one bit in the number (or several bits, if the condition is actually
// a numeric value). The sequence in which the conditions are listed corresponds
// to their individual severity, where the one listed first will make the most
// difference, because it results in the most significant bit of the result.
uint32_t imp = 0;
imp <<= 1; imp |= LiveView ? !device->IsPrimaryDevice() || ndr : 0; // prefer the primary device for live viewing if we don't need to detach existing receivers
imp <<= 1; imp |= !device->Receiving() && (device != cTransferControl::ReceiverDevice() || device->IsPrimaryDevice()) || ndr; // use receiving devices if we don't need to detach existing receivers, but avoid primary device in local transfer mode
imp <<= 1; imp |= device->Receiving(); // avoid devices that are receiving
#if APIVERSNUM >= 10700
imp <<= 4; imp |= GetClippedNumProvidedSystems(4, device) - 1; // avoid cards which support multiple delivery systems
#endif
imp <<= 1; imp |= device == cTransferControl::ReceiverDevice(); // avoid the Transfer Mode receiver device
imp <<= 8; imp |= min(max(device->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 <<= 1; imp |= ndr; // avoid devices if we need to detach existing receivers
#if VDRVERSNUM < 10719
imp <<= 1; imp |= device->IsPrimaryDevice(); // avoid the primary device
#endif
imp <<= 1; imp |= NumUsableSlots ? 0 : device->HasCi(); // avoid cards with Common Interface for FTA channels
#if VDRVERSNUM < 10719
imp <<= 1; imp |= device->HasDecoder(); // avoid full featured cards
#else
imp <<= 1; imp |= device->AvoidRecording(); // avoid SD full featured cards
#endif
imp <<= 1; imp |= NumUsableSlots ? !ChannelCamRelations.CamDecrypt(Channel->GetChannelID(), j + 1) : 0; // prefer CAMs that are known to decrypt this channel
#if VDRVERSNUM >= 10719
imp <<= 1; imp |= device->IsPrimaryDevice(); // avoid the primary device
#endif
if (imp < Impact) {
// This device has less impact than any previous one, so we take it.
Impact = imp;
d = device;
}
}
}
if (!NumUsableSlots)
break; // no CAM necessary, so just one loop over the devices
}
return d;
if (IsOpen())
isyslog("streamdev-server: closing %s connection to %s:%d", Protocol(), RemoteIp().c_str(), RemotePort());
return cTBSocket::Close();
}
bool cServerConnection::UsedByLiveTV(cDevice *device)
cString cServerConnection::ToText(char Delimiter) const
{
return device == cTransferControl::ReceiverDevice() ||
(device->IsPrimaryDevice() && device->HasDecoder() && !device->Replaying());
}
cDevice *cServerConnection::GetDevice(const cChannel *Channel, int Priority)
{
// turn off the streams of this connection
Detach();
// This call may detach receivers of the device it returns
cDevice *device = cDevice::GetDevice(Channel, Priority, false);
if (device && !device->IsTunedToTransponder(Channel)
&& UsedByLiveTV(device)) {
// now we would have to switch away live tv...let's see if live tv
// can be handled by another device
device = m_SwitchLive->Switch(device, Channel);
}
if (!device) {
// can't switch - continue the current stream
Attach();
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());
}
return device;
}
bool cServerConnection::ProvidesChannel(const cChannel *Channel, int Priority)
{
cDevice *device = CheckDevice(Channel, Priority, false);
if (!device || (StreamdevServerSetup.SuspendMode != smAlways
&& !device->IsTunedToTransponder(Channel)
&& UsedByLiveTV(device))) {
// no device available or the device is in use for live TV and suspend mode doesn't allow us to switch it:
// maybe a device would be free if THIS connection did turn off its streams?
Detach();
device = CheckDevice(Channel, Priority, false);
Attach();
if (device && StreamdevServerSetup.SuspendMode != smAlways
&& !device->IsTunedToTransponder(Channel)
&& UsedByLiveTV(device)) {
// now we would have to switch away live tv...let's see if live tv
// can be handled by another device
const cChannel *current = Channels.GetByNumber(cDevice::CurrentChannel());
cDevice *newdev = current ? CheckDevice(current, 0, true, device) : NULL;
if (newdev) {
dsyslog("streamdev: Providing channel %d (%s) at priority %d requires moving live TV to device %d (PrimaryDevice=%d, ActualDevice=%d)", Channel->Number(), Channel->Name(), Priority, newdev->CardIndex(), cDevice::PrimaryDevice()->CardIndex(), cDevice::ActualDevice()->CardIndex());
}
else {
device = NULL;
dsyslog("streamdev: Not providing channel %d (%s) at priority %d - live TV not suspended", Channel->Number(), Channel->Name(), Priority);
}
}
else if (!device)
dsyslog("streamdev: No device provides channel %d (%s) at priority %d", Channel->Number(), Channel->Name(), Priority);
}
return device;
}
void cServerConnection::MainThreadHook()
{
m_SwitchLive->Switch();
return cString::sprintf("%s%c%s:%d", Protocol(), Delimiter, RemoteIp().c_str(), RemotePort());
}

View File

@@ -7,15 +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;
class cSwitchLive;
/* Basic capabilities of a straight text-based protocol, most functions
virtual to support more complicated protocols */
@@ -34,19 +34,10 @@ private:
uint m_WriteBytes;
uint m_WriteIndex;
cSwitchLive *m_SwitchLive;
cStreamdevStreamer *m_Streamer;
tStrStrMap m_Headers;
/* Check if a device would be available for transfering the given
channel. This call has no side effects except for temporarily
detaching this connection's receivers. */
cDevice *CheckDevice(const cChannel *Channel, int Priority, bool LiveView, const cDevice *AvoidDevice = NULL);
/* 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);
protected:
/* Will be called when a command terminated by a newline has been
received */
@@ -63,6 +54,12 @@ protected:
/* 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:
@@ -103,25 +100,17 @@ 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. Receivers have
already been attached from the device if necessary. Use the returned
cDevice in a following call to StartTransfer */
cDevice *GetDevice(const cChannel *Channel, int Priority);
/* Test if a call to GetDevice would return a usable device. */
bool ProvidesChannel(const cChannel *Channel, int Priority);
/* Do things which must be done in VDR's main loop */
void MainThreadHook();
/* 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; }
};

View File

@@ -3,6 +3,15 @@
*/
#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"
@@ -12,10 +21,12 @@
cConnectionHTTP::cConnectionHTTP(void):
cServerConnection("HTTP"),
m_Status(hsRequest),
m_LiveStreamer(NULL),
m_Channel(NULL),
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;
@@ -24,7 +35,9 @@ cConnectionHTTP::cConnectionHTTP(void):
cConnectionHTTP::~cConnectionHTTP()
{
delete m_LiveStreamer;
SetStreamer(NULL);
delete m_RecPlayer;
delete m_MenuList;
}
bool cConnectionHTTP::CanAuthenticate(void)
@@ -48,9 +61,24 @@ bool cConnectionHTTP::Command(char *Cmd)
*v = 0;
SetHeader("REQUEST_METHOD", Cmd);
q = strchr(p, '?');
if (q)
if (q) {
*q = 0;
SetHeader("QUERY_STRING", q ? ++q : "");
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;
@@ -133,97 +161,260 @@ bool cConnectionHTTP::ProcessRequest(void)
}
if (!authOk) {
isyslog("streamdev-server: HTTP authorization required");
DeferClose();
return Respond("HTTP/1.0 401 Authorization Required")
&& Respond("WWW-authenticate: basic Realm=\"Streamdev-Server\")")
&& Respond("");
return HttpResponse(401, true, NULL, "WWW-authenticate: basic Realm=\"Streamdev-Server\"");
}
}
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_ChannelList)
return Respond("%s", true, m_ChannelList->HttpHeader().c_str());
if (m_MenuList)
return Respond("%s", true, m_MenuList->HttpHeader().c_str());
else if (m_Channel != NULL) {
cDevice *device = NULL;
if (ProvidesChannel(m_Channel, 0))
device = GetDevice(m_Channel, 0);
if (device != NULL) {
device->SwitchChannel(m_Channel, false);
m_LiveStreamer = new cStreamdevLiveStreamer(0, this);
if (m_LiveStreamer->SetChannel(m_Channel, m_StreamType, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL)) {
m_LiveStreamer->SetDevice(device);
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 Respond("HTTP/1.0 200 OK")
&& Respond("Content-Type: audio/mpeg")
&& Respond("icy-name: %s", true, m_Channel->Name())
&& Respond("");
return HttpResponse(200, false, "audio/mpeg", "icy-name: %s", m_Channel->Name());
} else if (ISRADIO(m_Channel)) {
return Respond("HTTP/1.0 200 OK")
&& Respond("Content-Type: audio/mpeg")
&& Respond("");
return HttpResponse(200, false, "audio/mpeg");
} else {
return Respond("HTTP/1.0 200 OK")
&& Respond("Content-Type: video/mpeg")
&& Respond("");
return HttpResponse(200, false, "video/mpeg");
}
}
DELETENULL(m_LiveStreamer);
SetStreamer(NULL);
delete liveStreamer;
}
DeferClose();
return Respond("HTTP/1.0 409 Channel not available")
&& Respond("");
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 {
DeferClose();
return Respond("HTTP/1.0 404 not found")
&& Respond("");
return HttpResponse(404, true);
}
} else if (it_method->second.compare("HEAD") == 0 && ProcessURI(it_pathinfo->second)) {
DeferClose();
if (m_ChannelList)
return Respond("%s", true, m_ChannelList->HttpHeader().c_str());
if (m_MenuList) {
DeferClose();
return Respond("%s", true, m_MenuList->HttpHeader().c_str());
}
else if (m_Channel != NULL) {
if (ProvidesChannel(m_Channel, 0)) {
if (cStreamdevLiveStreamer::ProvidesChannel(m_Channel, StreamdevServerSetup.HTTPPriority)) {
if (m_StreamType == stEXT) {
// TODO
return Respond("HTTP/1.0 200 OK")
&& Respond("");
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 Respond("HTTP/1.0 200 OK")
&& Respond("Content-Type: audio/mpeg")
&& Respond("icy-name: %s", true, m_Channel->Name())
&& Respond("");
return HttpResponse(200, true, "audio/mpeg", "icy-name: %s", m_Channel->Name());
} else if (ISRADIO(m_Channel)) {
return Respond("HTTP/1.0 200 OK")
&& Respond("Content-Type: audio/mpeg")
&& Respond("");
return HttpResponse(200, true, "audio/mpeg");
} else {
return Respond("HTTP/1.0 200 OK")
&& Respond("Content-Type: video/mpeg")
&& Respond("");
return HttpResponse(200, true, "video/mpeg");
}
}
return Respond("HTTP/1.0 409 Channel not available")
&& Respond("");
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 Respond("HTTP/1.0 404 not found")
&& Respond("");
return HttpResponse(404, true);
}
}
DeferClose();
return Respond("HTTP/1.0 400 Bad Request")
&& Respond("");
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)
@@ -231,21 +422,21 @@ void cConnectionHTTP::Flushed(void)
if (m_Status != hsBody)
return;
if (m_ChannelList) {
if (m_ChannelList->HasNext()) {
if (!Respond("%s", true, m_ChannelList->Next().c_str()))
if (m_MenuList) {
if (m_MenuList->HasNext()) {
if (!Respond("%s", true, m_MenuList->Next().c_str()))
DeferClose();
}
else {
DELETENULL(m_ChannelList);
DELETENULL(m_MenuList);
m_Status = hsFinished;
DeferClose();
}
return;
}
else if (m_Channel != NULL) {
else if (Streamer()) {
Dprintf("streamer start\n");
m_LiveStreamer->Start(this);
Streamer()->Start(this);
m_Status = hsFinished;
}
else {
@@ -255,57 +446,61 @@ void cConnectionHTTP::Flushed(void)
}
}
cChannelList* cConnectionHTTP::ChannelListFromString(const std::string& Path, const std::string& Filebase, const std::string& Fileext) const
cMenuList* cConnectionHTTP::MenuListFromString(const std::string& Path, const std::string& Filebase, const std::string& Fileext) const
{
// keys for Headers() hash
const static std::string QUERY_STRING("QUERY_STRING");
const static std::string HOST("HTTP_HOST");
tStrStrMap::const_iterator it_query = Headers().find(QUERY_STRING);
const std::string& query = it_query == Headers().end() ? "" : it_query->second;
std::string groupTarget;
cChannelIterator *iterator = NULL;
cItemIterator *iterator = NULL;
const static std::string GROUP("group");
if (Filebase.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);
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) {
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);
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);
}
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;
if (!query.empty())
self += '?' + query;
return new cHtmlChannelList(iterator, m_StreamType, self.c_str(), groupTarget.c_str());
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) {
std::string base;
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;
return new cM3uChannelList(iterator, base.c_str());
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;
}
@@ -313,6 +508,93 @@ cChannelList* cConnectionHTTP::ChannelListFromString(const std::string& Path, co
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;
@@ -320,11 +602,24 @@ bool cConnectionHTTP::ProcessURI(const std::string& PathInfo)
if (file_pos != std::string::npos) {
size_t ext_pos = PathInfo.rfind('.');
// file basename with leading / stripped off
filespec = PathInfo.substr(file_pos + 1, ext_pos - file_pos - 1);
if (ext_pos != std::string::npos)
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
@@ -335,10 +630,12 @@ bool cConnectionHTTP::ProcessURI(const std::string& PathInfo)
// Streamtype with leading / stripped off
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) {
@@ -349,9 +646,12 @@ bool cConnectionHTTP::ProcessURI(const std::string& PathInfo)
Dprintf("before channelfromstring: type(%s) filespec(%s) fileext(%s)\n", type.c_str(), filespec.c_str(), fileext.c_str());
if ((m_ChannelList = ChannelListFromString(PathInfo.substr(1, file_pos), filespec.c_str(), fileext.c_str())) != NULL) {
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;
@@ -359,3 +659,8 @@ bool cConnectionHTTP::ProcessURI(const std::string& PathInfo)
return false;
}
cString cConnectionHTTP::ToText(char Delimiter) const
{
cString str = cServerConnection::ToText(Delimiter);
return Streamer() ? cString::sprintf("%s%c%s", *str, Delimiter, *Streamer()->ToText()) : str;
}

View File

@@ -7,13 +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:
@@ -26,17 +26,32 @@ private:
std::string m_Authorization;
eHTTPStatus m_Status;
tStrStrMap m_Params;
eStreamType m_StreamType;
// job: transfer
cStreamdevLiveStreamer *m_LiveStreamer;
const cChannel *m_Channel;
int m_Apid[2];
int m_Dpid[2];
eStreamType m_StreamType;
// 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);
cChannelList* ChannelListFromString(const std::string &PathInfo, const std::string &Filebase, const std::string &Fileext) const;
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);
@@ -44,8 +59,7 @@ 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);
@@ -57,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

View File

@@ -11,7 +11,6 @@
cConnectionIGMP::cConnectionIGMP(const char* Name, int ClientPort, eStreamType StreamType) :
cServerConnection(Name, SOCK_DGRAM),
m_LiveStreamer(NULL),
m_ClientPort(ClientPort),
m_StreamType(StreamType),
m_Channel(NULL)
@@ -20,10 +19,13 @@ cConnectionIGMP::cConnectionIGMP(const char* Name, int ClientPort, eStreamType S
cConnectionIGMP::~cConnectionIGMP()
{
delete m_LiveStreamer;
}
#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;
@@ -42,32 +44,34 @@ bool cConnectionIGMP::SetChannel(cChannel *Channel, in_addr_t Dst)
void cConnectionIGMP::Welcome()
{
cDevice *device = NULL;
if (ProvidesChannel(m_Channel, 0))
device = GetDevice(m_Channel, 0);
if (device != NULL) {
device->SwitchChannel(m_Channel, false);
m_LiveStreamer = new cStreamdevLiveStreamer(0, this);
if (m_LiveStreamer->SetChannel(m_Channel, m_StreamType)) {
m_LiveStreamer->SetDevice(device);
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");
m_LiveStreamer->Start(this);
liveStreamer->Start(this);
}
else {
SetStreamer(NULL);
delete liveStreamer;
esyslog("streamdev-server IGMP: SetChannel failed");
DELETENULL(m_LiveStreamer);
}
}
else
esyslog("streamdev-server IGMP: GetDevice failed");
esyslog("streamdev-server IGMP: SwitchDevice failed");
}
void cConnectionIGMP::Stop()
bool cConnectionIGMP::Close()
{
if (m_LiveStreamer) {
m_LiveStreamer->Stop();
DELETENULL(m_LiveStreamer);
}
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;
}

View File

@@ -17,31 +17,37 @@ class cStreamdevLiveStreamer;
class cConnectionIGMP: public cServerConnection {
private:
cStreamdevLiveStreamer *m_LiveStreamer;
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);
void Stop();
virtual cString ToText(char Delimiter = ' ') const;
/* Not used here */
virtual bool Command(char *Cmd) { return false; }
virtual void Attach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Attach(); }
virtual void Detach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Detach(); }
virtual bool Close(void);
virtual bool Abort(void) const;
};
inline bool cConnectionIGMP::Abort(void) const
{
return !m_LiveStreamer || m_LiveStreamer->Abort();
return !IsOpen() || !Streamer() || Streamer()->Abort();
}
#endif // VDR_STREAMDEV_SERVERS_CONNECTIONIGMP_H

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,6 @@
#include "server/recplayer.h"
class cTBSocket;
class cStreamdevLiveStreamer;
class cStreamdevFilterStreamer;
class cLSTEHandler;
class cLSTCHandler;
@@ -20,7 +19,6 @@ class cConnectionVTP: public cServerConnection {
private:
cTBSocket *m_LiveSocket;
cStreamdevLiveStreamer *m_LiveStreamer;
cTBSocket *m_FilterSocket;
cStreamdevFilterStreamer *m_FilterStreamer;
cTBSocket *m_RecSocket;
@@ -28,7 +26,9 @@ private:
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
@@ -53,12 +53,15 @@ 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);

View File

@@ -2,18 +2,52 @@
* $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_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];
@@ -32,8 +66,86 @@ void cStreamdevLiveFilter::Process(u_short Pid, u_char Tid, const u_char *Data,
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;
}

View File

@@ -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

View File

@@ -8,45 +8,47 @@
#include "remux/ts2es.h"
#include "remux/extern.h"
#include <vdr/ringbuffer.h>
#include <vdr/transfer.h>
#include "server/livestreamer.h"
#include "server/livefilter.h"
#include "server/setup.h"
#include "common.h"
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 -------------------------------------------------
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:
cStreamdevLiveReceiver(cStreamdevStreamer *Streamer, const cChannel *Channel, int Priority, const int *Pids);
cStreamdevLiveReceiver(cStreamdevLiveStreamer *Streamer, const cChannel *Channel, int Priority, const int *Pids);
virtual ~cStreamdevLiveReceiver();
};
cStreamdevLiveReceiver::cStreamdevLiveReceiver(cStreamdevStreamer *Streamer, const cChannel *Channel,
int Priority, const int *Pids):
#if APIVERSNUM >= 10712
cStreamdevLiveReceiver::cStreamdevLiveReceiver(cStreamdevLiveStreamer *Streamer, const cChannel *Channel, int Priority, const int *Pids):
cReceiver(Channel, Priority),
#else
cReceiver(Channel->GetChannelID(), Priority, 0, Pids),
#endif
m_Streamer(Streamer)
{
#if APIVERSNUM >= 10712
// clears all PIDs but channel remains set
SetPids(NULL);
AddPids(Pids);
#endif
}
cStreamdevLiveReceiver::~cStreamdevLiveReceiver()
@@ -55,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 ----------------------------------------------------
@@ -260,7 +258,12 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
SI::PAT::Association assoc;
for (SI::Loop::Iterator it; pat.associationLoop.getNext(assoc, it); ) {
if (!assoc.isNITPid()) {
#if APIVERSNUM >= 20300
LOCK_CHANNELS_READ;
const cChannel *Channel = Channels->GetByServiceID(Source(), Transponder(), assoc.getServiceId());
#else
const cChannel *Channel = Channels.GetByServiceID(Source(), Transponder(), assoc.getServiceId());
#endif
if (Channel && (Channel == m_Channel)) {
int prevPmtPid = pmtPid;
if (0 != (pmtPid = assoc.getPid())) {
@@ -344,29 +347,36 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
// --- cStreamdevLiveStreamer -------------------------------------------------
cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority, const cServerConnection *Connection):
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_NumPids(0),
m_StreamType(stTSPIDS),
m_Channel(NULL),
m_Channel(Channel),
m_Device(NULL),
m_Receiver(NULL),
m_PatFilter(NULL),
m_Remux(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_Remux;
delete m_ReceiveBuffer;
}
bool cStreamdevLiveStreamer::HasPid(int Pid)
@@ -446,27 +456,45 @@ bool cStreamdevLiveStreamer::SetPids(int Pid, const int *Pids1, const int *Pids2
void cStreamdevLiveStreamer::SetPriority(int Priority)
{
m_Priority = Priority;
StartReceiver();
#if VDRVERSNUM >= 20104
cThreadLock ThreadLock(m_Device);
if (m_Receiver)
m_Receiver->SetPriority(Priority);
else
#endif
StartReceiver();
}
void cStreamdevLiveStreamer::GetSignal(int *DevNum, int *Strength, int *Quality) const
{
if (m_Device) {
*DevNum = m_Device->DeviceNumber() + 1;
#if APIVERSNUM >= 10719
*Strength = m_Device->SignalStrength();
*Quality = m_Device->SignalQuality();
#endif
}
}
void cStreamdevLiveStreamer::StartReceiver(void)
cString cStreamdevLiveStreamer::ToText() const
{
if (m_NumPids > 0) {
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;
m_Receiver = new cStreamdevLiveReceiver(this, m_Channel, m_Priority, m_Pids);
cThreadLock ThreadLock(m_Device);
m_Receiver = new cStreamdevLiveReceiver(this, m_Channel, m_Priority, m_Pids);
if (IsRunning())
Attach();
delete current;
@@ -475,17 +503,15 @@ void cStreamdevLiveStreamer::StartReceiver(void)
DELETENULL(m_Receiver);
}
bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType StreamType, const int* Apid, const int *Dpid)
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;
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();
@@ -493,20 +519,22 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str
pid = Apid[0];
else if (Dpid && Dpid[0])
pid = Dpid[0];
m_Remux = new cTS2ESRemux(pid);
SetRemux(new cTS2ESRemux(pid));
return SetPids(pid);
}
case stPES:
m_Remux = new cTS2PESRemux(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids());
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_Remux = new cTS2PSRemux(m_Channel->Vpid(), Apids, 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:
m_Remux = new cExternRemux(Connection(), m_Channel, Apids, Dpids);
SetRemux(new cExternRemux(Connection(), m_Channel, Apids, Dpids));
// fall through
case stTS:
// This should never happen, but ...
@@ -524,12 +552,40 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str
case stTSPIDS:
Dprintf("pid streaming mode\n");
// No PIDs requested yet. Start receiver anyway to occupy device
StartReceiver(true);
return true;
default:
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)
{
// insert si data
@@ -537,42 +593,21 @@ int cStreamdevLiveStreamer::Put(const uchar *Data, int Count)
int siCount;
uchar *siData = m_PatFilter->Get(siCount);
if (siData) {
if (m_Remux)
siCount = m_Remux->Put(siData, siCount);
else
siCount = cStreamdevStreamer::Put(siData, siCount);
siCount = cStreamdevStreamer::Put(siData, siCount);
if (siCount)
m_PatFilter->Del(siCount);
}
}
if (m_Remux)
return m_Remux->Put(Data, Count);
else
return cStreamdevStreamer::Put(Data, Count);
return cStreamdevStreamer::Put(Data, Count);
}
uchar *cStreamdevLiveStreamer::Get(int &Count)
{
if (m_Remux)
return m_Remux->Get(Count);
else
return cStreamdevStreamer::Get(Count);
}
void cStreamdevLiveStreamer::Del(int Count)
{
if (m_Remux)
m_Remux->Del(Count);
else
cStreamdevStreamer::Del(Count);
}
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) {
@@ -593,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;
@@ -609,105 +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;
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

View File

@@ -2,82 +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"
namespace Streamdev {
class cTSRemux;
}
#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;
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;
Streamdev::cTSRemux *m_Remux;
bool m_SwitchLive;
void StartReceiver(void);
void StartReceiver(bool Force = false);
bool HasPid(int Pid);
/* 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);
/* 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 void Action(void);
virtual void ChannelChange(const cChannel *Channel);
public:
cStreamdevLiveStreamer(int Priority, const cServerConnection *Connection);
cStreamdevLiveStreamer(const cServerConnection *Connection, const cChannel *Channel, int Priority, eStreamType StreamType, const int* Apid = NULL, const int* Dpid = NULL);
virtual ~cStreamdevLiveStreamer();
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, const int* Apid = NULL, const int* Dpid = NULL);
void SetPriority(int Priority);
void GetSignal(int *DevNum, int *Strength, int *Quality) const;
virtual cString ToText() const;
virtual int Put(const uchar *Data, int Count);
virtual uchar *Get(int &Count);
virtual void Del(int Count);
#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
View 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
View 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

View File

@@ -1,58 +1,244 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <vdr/channels.h>
#include "server/menuHTTP.h"
//**************************** cChannelIterator **************
cChannelIterator::cChannelIterator(const cChannel *First): channel(First)
{}
//**************************** cRecordingIterator **************
#if APIVERSNUM >= 20300
cRecordingsIterator::cRecordingsIterator(eStreamType StreamType)
#else
cRecordingsIterator::cRecordingsIterator(eStreamType StreamType): RecordingsLock(&Recordings)
#endif
{
streamType = StreamType;
#if APIVERSNUM >= 20300
LOCK_RECORDINGS_READ;
first = NextSuitable(Recordings->First());
#else
first = NextSuitable(Recordings.First());
#endif
current = NULL;
}
const cChannel* cChannelIterator::Next()
const cRecording* cRecordingsIterator::NextSuitable(const cRecording *Recording)
{
const cChannel *current = channel;
channel = NextChannel(channel);
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 = 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(GetNextChannelInGroup(Group))
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;
}
@@ -62,67 +248,65 @@ const cChannel* cListGroup::NextChannel(const cChannel *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 = 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;
}
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 =
// ******************** 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=\"channels.html\" tvid=\"BLUE\">Channels</a> (<a href=\"channels.m3u\">Playlist</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 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"
@@ -138,7 +322,7 @@ const char* cHtmlChannelList::css =
"-->\n"
"</style>";
const char* cHtmlChannelList::js =
const char* cHtmlMenuList::js =
"<script language=\"JavaScript\">\n"
"<!--\n"
@@ -199,13 +383,15 @@ 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] " :
@@ -215,27 +401,29 @@ std::string cHtmlChannelList::StreamTypeMenu()
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)
{
@@ -252,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)
@@ -309,99 +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;
std::string suffix;
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*) itoa(current->Number()) + "\">";
line += (std::string) "<a href=\"" + (std::string) current->GetChannelID().ToString() + suffix + "\"";
line += (std::string) "<li value=\"" + (const char*) ItemId() + "\">";
line += (std::string) "<a href=\"" + (const char*) ItemRessource() + suffix + "\"";
// for Network Media Tank
line += (std::string) " vod ";
if (current->Number() < 1000)
line += (std::string) " tvid=\"" + (const char*) itoa(current->Number()) + "\"";
if (strlen(ItemId()) < 4)
line += (std::string) " tvid=\"" + (const char*) ItemId() + "\"";
line += (std::string) ">" + current->Name() + "</a>";
line += (std::string) ">" + ItemTitle() + "</a>";
int count = 0;
for (int i = 0; current->Apid(i) != 0; ++i, ++count)
;
for (int i = 0; current->Dpid(i) != 0; ++i, ++count)
;
if (count > 1)
// 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) + suffix + "\" class=\"apid\" vod>" + 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) + suffix + "\" class=\"dpid\" vod>" + 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),
// ******************** 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)
{
@@ -409,26 +598,83 @@ std::string cM3uChannelList::Next()
return "#EXTM3U";
}
const cChannel *channel = NextChannel();
if (!channel)
if (!NextItem())
{
m3uState = msLast;
return "";
}
std::string name = (std::string) m_IConv.Convert(channel->Name());
std::string name = (std::string) m_IConv.Convert(ItemTitle());
if (channel->GroupSep())
if (IsGroup())
{
return (std::string) "#EXTINF:-1," + name + "\r\n" +
base + "group.m3u?group=" +
(const char*) itoa(cChannelList::GetGroupIndex(channel));
base + "group.m3u?group=" + (const char*) ItemId();
}
else
{
return (std::string) "#EXTINF:-1," +
(const char*) itoa(channel->Number()) + " " + name + "\r\n" +
base + (std::string) channel->GetChannelID().ToString();
(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";
}
}

View File

@@ -3,19 +3,66 @@
#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 cRecording* NextSuitable(const cRecording *Recording);
public:
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:
const cChannel* Next();
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() {};
};
@@ -54,7 +101,7 @@ class cListGroup: public cChannelIterator
protected:
virtual const cChannel* NextChannel(const cChannel *Channel);
public:
cListGroup(const cChannel *Group);
cListGroup(const char *GroupId);
virtual ~cListGroup() {};
};
@@ -66,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;
@@ -104,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();
@@ -117,18 +166,18 @@ class cHtmlChannelList: public cChannelList
std::string PageBottom();
public:
virtual std::string HttpHeader() {
return cChannelList::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;
@@ -136,17 +185,41 @@ class cM3uChannelList: public cChannelList
eM3uState m3uState;
cCharSetConv m_IConv;
public:
virtual std::string HttpHeader() { return cChannelList::HttpHeader() + "Content-type: audio/x-mpegurl; charset=UTF-8\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;
}

View File

@@ -5,45 +5,43 @@
#
msgid ""
msgstr ""
"Project-Id-Version: streamdev 0.5.0\n"
"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
"POT-Creation-Date: 2010-06-14 13:06+0200\n"
"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: <vdr@linuxtv.org>\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 "VDR Streaming Server"
msgstr "VDR Streaming Server"
msgid "Streaming active"
msgstr "Streamen im Gange"
msgid "Suspend Live TV"
msgstr "Live-TV pausieren"
msgid "Streamdev Connections"
msgstr "Streamdev Verbindungen"
msgid "Offer suspend mode"
msgstr "Pausieren anbieten"
msgid "Disconnect"
msgstr "Trennen"
msgid "Always suspended"
msgstr "Immer pausiert"
msgid "Never suspended"
msgstr "Nie pausiert"
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 "Suspend behaviour"
msgstr "Pausierverhalten"
msgid "Client may suspend"
msgstr "Client darf pausieren"
msgid "Live TV buffer delay (ms)"
msgstr "Live-TV Pufferdauer (ms)"
msgid "VDR-to-VDR Server"
msgstr "VDR-zu-VDR Server"
@@ -57,6 +55,15 @@ 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"
@@ -66,6 +73,9 @@ msgstr "HTTP Server starten"
msgid "HTTP Server Port"
msgstr "Port des HTTP Servers"
msgid "Priority"
msgstr "Priorität"
msgid "HTTP Streamtype"
msgstr "HTTP Streamtyp"
@@ -81,3 +91,5 @@ msgstr "Port des Multicast Clients"
msgid "Multicast Streamtype"
msgstr "Multicast Streamtyp"
msgid "VDR Streaming Server"
msgstr "VDR Streaming Server"

View File

@@ -6,44 +6,42 @@
msgid ""
msgstr ""
"Project-Id-Version: streamdev 0.5.0\n"
"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
"POT-Creation-Date: 2010-06-14 13:06+0200\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: <vdr@linuxtv.org>\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 "VDR Streaming Server"
msgstr "Servidor de transmisiones del VDR"
msgid "Streaming active"
msgstr "Trasmisión activa"
msgid "Suspend Live TV"
msgstr "Suspender TV en vivo"
msgid "Streamdev Connections"
msgstr ""
msgid "Offer suspend mode"
msgstr "Ofrecer modo de suspensión"
msgid "Disconnect"
msgstr ""
msgid "Always suspended"
msgstr "Siempre suspendido"
msgid "Never suspended"
msgstr "Nunca suspendido"
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 "Suspend behaviour"
msgstr "Comportamiento de la suspensión"
msgid "Client may suspend"
msgstr "Permitir suspender al cliente"
msgid "Live TV buffer delay (ms)"
msgstr ""
msgid "VDR-to-VDR Server"
msgstr "Servidor VDR-a-VDR"
@@ -57,6 +55,15 @@ 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"
@@ -66,6 +73,9 @@ msgstr "Iniciar Servidor HTTP"
msgid "HTTP Server Port"
msgstr "Puerto del Servidor HTTP"
msgid "Priority"
msgstr ""
msgid "HTTP Streamtype"
msgstr "Tipo de flujo HTTP"
@@ -81,4 +91,5 @@ msgstr "Puerto del Cliente Multicast"
msgid "Multicast Streamtype"
msgstr "Tipo de flujo Multicast"
msgid "VDR Streaming Server"
msgstr "Servidor de transmisiones del VDR"

View File

@@ -1,55 +1,53 @@
# 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 <rahrenbe@cc.hut.fi>, 2008
# Rolf Ahrenberg, 2008-
#
msgid ""
msgstr ""
"Project-Id-Version: streamdev 0.5.0\n"
"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
"POT-Creation-Date: 2010-06-14 13:06+0200\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 <rahrenbe@cc.hut.fi>\n"
"Language-Team: <vdr@linuxtv.org>\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=ISO-8859-15\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "VDR Streaming Server"
msgstr "VDR-suoratoistopalvelin"
msgid "Streaming active"
msgstr "Suoratoistopalvelin aktiivinen"
msgid "Suspend Live TV"
msgstr "Pysäytä suora TV-lähetys"
msgid "Streamdev Connections"
msgstr "Suoratoistoyhteydet"
msgid "Offer suspend mode"
msgstr "tyrkytä"
msgid "Disconnect"
msgstr "Katkaise"
msgid "Always suspended"
msgstr "aina"
msgid "Never suspended"
msgstr "ei koskaan"
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ä"
msgstr "Suurin sallittu asiakkaiden määrä"
msgid "Suspend behaviour"
msgstr "Pysäytystoiminto"
msgid "Client may suspend"
msgstr "Asiakas saa pysäyttää palvelimen"
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"
msgstr "Käynnistä VDR-palvelin"
msgid "VDR-to-VDR Server Port"
msgstr "VDR-palvelimen portti"
@@ -57,27 +55,41 @@ 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"
msgstr "Käynnistä HTTP-palvelin"
msgid "HTTP Server Port"
msgstr "HTTP-palvelimen portti"
msgid "Priority"
msgstr "Prioriteetti"
msgid "HTTP Streamtype"
msgstr "HTTP-lähetysmuoto"
msgstr "HTTP-lähetysmuoto"
msgid "Multicast Streaming Server"
msgstr "Multicast-suoratoistopalvelin"
msgid "Start IGMP Server"
msgstr "Käynnistä IGMP-palvelin"
msgstr "Käynnistä IGMP-palvelin"
msgid "Multicast Client Port"
msgstr "Multicast-portti"
msgid "Multicast Streamtype"
msgstr "Multicast-lähetysmuoto"
msgstr "Multicast-lähetysmuoto"
msgid "VDR Streaming Server"
msgstr "VDR-suoratoistopalvelin"

View File

@@ -6,44 +6,42 @@
msgid ""
msgstr ""
"Project-Id-Version: streamdev 0.5.0\n"
"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
"POT-Creation-Date: 2010-06-14 13:06+0200\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: <vdr@linuxtv.org>\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 "VDR Streaming Server"
msgstr "Serveur de streaming VDR"
msgid "Streaming active"
msgstr "Streaming actif"
msgid "Suspend Live TV"
msgstr "Suspendre Live TV"
msgid "Streamdev Connections"
msgstr ""
msgid "Offer suspend mode"
msgstr "Offrir le mode suspendre"
msgid "Disconnect"
msgstr ""
msgid "Always suspended"
msgstr "Toujours suspendre"
msgid "Never suspended"
msgstr "Jamais suspendre"
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 "Suspend behaviour"
msgstr "Suspendre"
msgid "Client may suspend"
msgstr "Le client peut suspendre"
msgid "Live TV buffer delay (ms)"
msgstr ""
msgid "VDR-to-VDR Server"
msgstr "VDR-to-VDR Serveur"
@@ -57,6 +55,15 @@ 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"
@@ -66,6 +73,9 @@ msgstr "D
msgid "HTTP Server Port"
msgstr "Port du serveur HTTP"
msgid "Priority"
msgstr ""
msgid "HTTP Streamtype"
msgstr "Type de Streaming HTTP"
@@ -81,3 +91,5 @@ msgstr ""
msgid "Multicast Streamtype"
msgstr ""
msgid "VDR Streaming Server"
msgstr "Serveur de streaming VDR"

View File

@@ -8,44 +8,42 @@
msgid ""
msgstr ""
"Project-Id-Version: streamdev 0.5.0\n"
"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
"POT-Creation-Date: 2010-06-14 13:06+0200\n"
"PO-Revision-Date: 2010-06-19 03:58+0100\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: <vdr@linuxtv.org>\n"
"Language-Team: Italian <vdr@linuxtv.org>\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=ISO-8859-15\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "VDR Streaming Server"
msgstr "Server trasmissione VDR"
msgid "Streaming active"
msgstr "Trasmissione attiva"
msgid "Suspend Live TV"
msgstr "Sospendi TV dal vivo"
msgid "Streamdev Connections"
msgstr "Connessioni Streamdev"
msgid "Offer suspend mode"
msgstr "Offri mod. sospensione"
msgid "Disconnect"
msgstr "Disconnetti"
msgid "Always suspended"
msgstr "Sempre sospeso"
msgid "Never suspended"
msgstr "Mai sospeso"
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 "Suspend behaviour"
msgstr "Tipo sospensione"
msgid "Client may suspend"
msgstr "Permetti sospensione al Client"
msgid "Live TV buffer delay (ms)"
msgstr ""
msgid "VDR-to-VDR Server"
msgstr "Server VDR-a-VDR"
@@ -59,6 +57,15 @@ 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"
@@ -68,6 +75,9 @@ msgstr "Avvia Server HTTP"
msgid "HTTP Server Port"
msgstr "Porta Server HTTP"
msgid "Priority"
msgstr "Priorità"
msgid "HTTP Streamtype"
msgstr "Tipo flusso HTTP"
@@ -83,3 +93,5 @@ msgstr "Porta Client Multicast"
msgid "Multicast Streamtype"
msgstr "Tipo flusso Multicast"
msgid "VDR Streaming Server"
msgstr "Server trasmissione VDR"

View File

@@ -6,44 +6,42 @@
msgid ""
msgstr ""
"Project-Id-Version: streamdev 0.5.0\n"
"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
"POT-Creation-Date: 2010-06-14 13:06+0200\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: Lietuvių\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 "VDR Streaming Server"
msgstr "VDR transliavimo serveris"
msgid "Streaming active"
msgstr "Transliavimas vyksta"
msgid "Suspend Live TV"
msgstr "Pristabdyti Live TV"
msgid "Streamdev Connections"
msgstr ""
msgid "Offer suspend mode"
msgstr "Klausti dėl sustabdymo"
msgid "Disconnect"
msgstr ""
msgid "Always suspended"
msgstr "Visada stabdyti"
msgid "Never suspended"
msgstr "Niekada nestabdyti"
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 "Suspend behaviour"
msgstr "Pristabdyti veikimą"
msgid "Client may suspend"
msgstr "Klientas gali pristabdyti"
msgid "Live TV buffer delay (ms)"
msgstr ""
msgid "VDR-to-VDR Server"
msgstr "VDR-su-VDR Serveris"
@@ -57,6 +55,15 @@ 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"
@@ -66,6 +73,9 @@ msgstr "Paleisti HTTP serverį"
msgid "HTTP Server Port"
msgstr "HTTP serverio portas"
msgid "Priority"
msgstr ""
msgid "HTTP Streamtype"
msgstr "HTTP transliavimo tipas"
@@ -81,3 +91,5 @@ 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
View 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"

View File

@@ -6,44 +6,42 @@
msgid ""
msgstr ""
"Project-Id-Version: streamdev 0.5.0\n"
"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
"POT-Creation-Date: 2010-06-14 13:06+0200\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: <vdr@linuxtv.org>\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 "VDR Streaming Server"
msgstr "VDR Streaming áÕàÒÕà"
msgid "Streaming active"
msgstr "ÁâàØÜØÝÓ ÐÚâØÒÕÝ"
msgid "Suspend Live TV"
msgstr "¾áâÐÝÞÒÚÐ Live TV"
msgid "Streamdev Connections"
msgstr ""
msgid "Offer suspend mode"
msgstr "¿àÕÔÛÐÓÐâì ÞáâÐÝÞÒÚã"
msgid "Disconnect"
msgstr ""
msgid "Always suspended"
msgstr "²áÕÓÔÐ ÞáâÐÝÞÒÛÕÝ"
msgid "Never suspended"
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 "Suspend behaviour"
msgstr "¿ÞÒÕÔÕÝØÕ ÞáâÐÝÞÒÚØ"
msgid "Client may suspend"
msgstr "ºÛØÕÝâ ÜÞÖÕâ ÞáâÐÝÐÒÛØÒÐâì"
msgid "Live TV buffer delay (ms)"
msgstr ""
msgid "VDR-to-VDR Server"
msgstr "VDR-to-VDR áÕàÒÕà"
@@ -57,6 +55,15 @@ 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 áÕàÒÕà"
@@ -66,6 +73,9 @@ msgstr "
msgid "HTTP Server Port"
msgstr "HTTP áÕàÒÕà ¿Þàâ"
msgid "Priority"
msgstr ""
msgid "HTTP Streamtype"
msgstr "ÂØß HTTP ßÞâÞÚÐ"
@@ -81,3 +91,5 @@ msgstr ""
msgid "Multicast Streamtype"
msgstr ""
msgid "VDR Streaming Server"
msgstr "VDR Streaming áÕàÒÕà"

74
server/po/sk_SK.po Normal file → Executable file
View File

@@ -6,80 +6,92 @@
msgid ""
msgstr ""
"Project-Id-Version: streamdev_SK\n"
"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n"
"POT-Creation-Date: 2010-06-14 13:06+0200\n"
"PO-Revision-Date: \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 "VDR Streaming Server"
msgstr "VDR prúdový server"
msgid "Streaming active"
msgstr "streamovanie aktivne"
msgstr "Streamovanie aktívne"
msgid "Suspend Live TV"
msgstr "Pozastavenie ¾ivého vysielania"
msgid "Streamdev Connections"
msgstr "Streamdev spojenia"
msgid "Offer suspend mode"
msgstr "Výber re¾ímu pozastavenia"
msgid "Disconnect"
msgstr "Odpoji»"
msgid "Always suspended"
msgstr "V¾dy pozastavi»"
msgid "Never suspended"
msgstr "Nikdy nepozastavi»"
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 "Suspend behaviour"
msgstr "Správanie preru¹enia"
msgid "Client may suspend"
msgstr "Klient mô¾e pozastavi»"
msgid "Live TV buffer delay (ms)"
msgstr ""
msgid "VDR-to-VDR Server"
msgstr "VDR-do-VDR server"
msgstr "Prenos z VDR do VDR"
msgid "Start VDR-to-VDR Server"
msgstr "Spusti» VDR-do-VDR Server"
msgstr "Spusti» prenos z VDR do VDR"
msgid "VDR-to-VDR Server Port"
msgstr "Port serveru pre VDR-do-VDR"
msgstr "Port servera prenosu z VDR do VDR"
msgid "Bind to IP"
msgstr "viaza» na 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 "server HTTP"
msgstr "HTTP server "
msgid "Start HTTP Server"
msgstr "Spusti» HTTP Server"
msgid "HTTP Server Port"
msgstr "Port serveru HTTP"
msgstr "Port HTTP servera"
msgid "Priority"
msgstr "Priorita"
msgid "HTTP Streamtype"
msgstr "typ prúdu HTTP"
msgstr "Typ HTTP streamu"
msgid "Multicast Streaming Server"
msgstr "Multicast prúdový server"
msgstr "Streamovanie Multicastového servera"
msgid "Start IGMP Server"
msgstr "Spusti» IGMP Server"
msgid "Multicast Client Port"
msgstr "Port klienta Multicast"
msgstr "Port Multicast klienta"
msgid "Multicast Streamtype"
msgstr "Multicast typ streamu"
msgstr "Typ Multicast streamu"
msgid "VDR Streaming Server"
msgstr "VDR server streamovania"

View File

@@ -24,27 +24,30 @@
// for TSPLAY patch detection
#include "vdr/device.h"
#undef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600
#include <fcntl.h>
RecPlayer::RecPlayer(cRecording* rec)
RecPlayer::RecPlayer(const char* FileName)
{
file = NULL;
fileOpen = 0;
lastPosition = 0;
recording = rec;
recording = new cRecording(FileName);
for(int i = 1; i < 1000; i++) segments[i] = NULL;
// FIXME find out max file path / name lengths
#if VDRVERSNUM >= 10703 || defined(TSPLAY_PATCH_VERSION)
indexFile = new cIndexFile(recording->FileName(), false, rec->IsPesRecording());
#else
indexFile = new cIndexFile(recording->FileName(), false);
#endif
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()
@@ -61,24 +64,18 @@ void RecPlayer::scan()
for(i = 1; i < 1000; i++)
{
#if APIVERSNUM < 10703 || !defined(TSPLAY_PATCH_VERSION)
snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), i);
//log->log("RecPlayer", Log::DEBUG, "FILENAME: %s", fileName);
file = fopen(fileName, "r");
#else
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");
}
#endif
if (!file) break;
segments[i] = new Segment();
segments[i]->start = totalLength;
fseek(file, 0, SEEK_END);
totalLength += ftell(file);
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;
@@ -94,6 +91,9 @@ RecPlayer::~RecPlayer()
int i = 1;
while(segments[i++]) delete segments[i];
if (file) fclose(file);
delete indexFile;
delete recording;
delete parser;
}
int RecPlayer::openFile(int index)
@@ -102,7 +102,6 @@ int RecPlayer::openFile(int index)
char fileName[2048];
#if APIVERSNUM >= 10703 || defined(TSPLAY_PATCH_VERSION)
snprintf(fileName, 2047, "%s/%05i.ts", recording->FileName(), index);
isyslog("openFile called for index %i string:%s", index, fileName);
@@ -112,7 +111,6 @@ int RecPlayer::openFile(int index)
fileOpen = index;
return 1;
}
#endif
snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), index);
isyslog("openFile called for index %i string:%s", index, fileName);
@@ -178,7 +176,7 @@ unsigned long RecPlayer::getBlock(unsigned char* buffer, uint64_t position, unsi
uint32_t yetToGet = amount;
uint32_t got = 0;
uint32_t getFromThisSegment = 0;
uint32_t filePosition;
uint64_t filePosition;
while(got < amount)
{
@@ -200,7 +198,7 @@ unsigned long RecPlayer::getBlock(unsigned char* buffer, uint64_t position, unsi
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(file->_fileno, filePosition, getFromThisSegment, POSIX_FADV_DONTNEED);
posix_fadvise(fileno(file), filePosition, getFromThisSegment, POSIX_FADV_DONTNEED);
got += getFromThisSegment;
currentPosition += getFromThisSegment;
@@ -221,17 +219,47 @@ 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;
#if VDRVERSNUM >= 10703 || defined(TSPLAY_PATCH_VERSION)
uint16_t retFileNumber;
off_t retFileOffset;
#else
uchar retFileNumber;
int retFileOffset;
#endif
if (!indexFile->Get((int)frameNumber, &retFileNumber, &retFileOffset))
{
@@ -262,7 +290,7 @@ uint32_t RecPlayer::frameNumberFromPosition(uint64_t position)
if ((position >= segments[segmentNumber]->start) && (position < segments[segmentNumber]->end)) break;
// position is in this block
}
uint32_t askposition = position - segments[segmentNumber]->start;
uint64_t askposition = position - segments[segmentNumber]->start;
return indexFile->Get((int)segmentNumber, askposition);
}

View File

@@ -23,6 +23,7 @@
#include <stdio.h>
#include <vdr/recording.h>
#include <vdr/remux.h>
#include "server/streamer.h"
@@ -36,7 +37,7 @@ class Segment
class RecPlayer
{
public:
RecPlayer(cRecording* rec);
RecPlayer(const char* FileName);
~RecPlayer();
uint64_t getLengthBytes();
uint32_t getLengthFrames();
@@ -44,7 +45,12 @@ class RecPlayer
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);
@@ -52,6 +58,7 @@ class RecPlayer
private:
cRecording* recording;
cIndexFile* indexFile;
cPatPmtParser* parser;
FILE* file;
int fileOpen;
Segment* segments[1000];

98
server/recstreamer.c Normal file
View 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
View 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

View File

@@ -152,9 +152,8 @@ 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();
@@ -178,9 +177,12 @@ void cStreamdevServer::Action(void)
}
}
void cStreamdevServer::MainThreadHook(void)
#if APIVERSNUM >= 20300
cList<cServerConnection>& cStreamdevServer::Clients(cThreadLock& Lock)
#else
const cList<cServerConnection>& cStreamdevServer::Clients(cThreadLock& Lock)
#endif
{
cThreadLock lock(m_Instance);
for (cServerConnection *s = m_Clients.First(); s; s = m_Clients.Next(s))
s->MainThreadHook();
Lock.Lock(m_Instance);
return m_Clients;
}

View File

@@ -36,7 +36,12 @@ public:
static void Initialize(void);
static void Destruct(void);
static bool Active(void);
static void MainThreadHook(void);
#if APIVERSNUM >= 20300
static cList<cServerConnection>& Clients(cThreadLock& Lock);
#else
static const cList<cServerConnection>& Clients(cThreadLock& Lock);
#endif
};
inline bool cStreamdevServer::Active(void)

View File

@@ -10,16 +10,22 @@
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;
HTTPPriority = 0;
HTTPStreamType = stTS;
StartIGMPServer = false;
IGMPClientPort = 1234;
IGMPPriority = 0;
IGMPStreamType = stTS;
SuspendMode = smAlways;
AllowSuspend = false;
strcpy(VTPBindIP, "0.0.0.0");
strcpy(HTTPBindIP, "0.0.0.0");
@@ -27,19 +33,25 @@ cStreamdevServerSetup::cStreamdevServerSetup(void) {
}
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, "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, "SuspendMode") == 0) SuspendMode = atoi(Value);
else if (strcmp(Name, "AllowSuspend") == 0) AllowSuspend = atoi(Value);
else return false;
return true;
@@ -53,12 +65,6 @@ const char* cStreamdevServerMenuSetupPage::StreamTypes[st_Count - 1] = {
"EXT"
};
const char* cStreamdevServerMenuSetupPage::SuspendModes[sm_Count] = {
trNOOP("Offer suspend mode"),
trNOOP("Always suspended"),
trNOOP("Never suspended")
};
cStreamdevServerMenuSetupPage::cStreamdevServerMenuSetupPage(void) {
m_NewSetup = StreamdevServerSetup;
@@ -69,34 +75,44 @@ cStreamdevServerMenuSetupPage::~cStreamdevServerMenuSetupPage() {
}
void cStreamdevServerMenuSetupPage::Set(void) {
static const char* modes[sm_Count];
for (int i = 0; i < sm_Count; i++)
modes[i] = tr(SuspendModes[i]);
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));
Add(new cMenuEditStraItem(tr("Suspend behaviour"), &m_NewSetup.SuspendMode, sm_Count, modes));
if (m_NewSetup.SuspendMode == smOffer)
Add(new cMenuEditBoolItem(tr("Client may suspend"), &m_NewSetup.AllowSuspend));
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 cMenuEditStraItem(tr("HTTP Streamtype"), &m_NewSetup.HTTPStreamType, st_Count - 1, StreamTypes));
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 cMenuEditStraItem(tr("Multicast Streamtype"), &m_NewSetup.IGMPStreamType, st_Count - 1, StreamTypes));
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();
}
@@ -126,19 +142,25 @@ void cStreamdevServerMenuSetupPage::Store(void) {
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("HTTPPriority", m_NewSetup.HTTPPriority);
SetupStore("HTTPStreamType", m_NewSetup.HTTPStreamType);
SetupStore("StartIGMPServer", m_NewSetup.StartIGMPServer);
SetupStore("IGMPClientPort", m_NewSetup.IGMPClientPort);
SetupStore("IGMPStreamType", m_NewSetup.IGMPStreamType);
SetupStore("IGMPBindIP", m_NewSetup.IGMPBindIP);
SetupStore("SuspendMode", m_NewSetup.SuspendMode);
SetupStore("IGMPPriority", m_NewSetup.IGMPPriority);
SetupStore("IGMPStreamType", m_NewSetup.IGMPStreamType);
SetupStore("AllowSuspend", m_NewSetup.AllowSuspend);
StreamdevServerSetup = m_NewSetup;
@@ -146,11 +168,3 @@ void cStreamdevServerMenuSetupPage::Store(void) {
if (restart)
cStreamdevServer::Initialize();
}
eOSState cStreamdevServerMenuSetupPage::ProcessKey(eKeys Key) {
int oldMode = m_NewSetup.SuspendMode;
eOSState state = cMenuSetupPage::ProcessKey(Key);
if (oldMode != m_NewSetup.SuspendMode)
Set();
return state;
}

View File

@@ -7,25 +7,38 @@
#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 StartIGMPServer;
int IGMPClientPort;
int IGMPPriority;
int IGMPStreamType;
char IGMPBindIP[20];
int SuspendMode;
int AllowSuspend;
};
extern cStreamdevServerSetup StreamdevServerSetup;
@@ -33,14 +46,12 @@ extern cStreamdevServerSetup StreamdevServerSetup;
class cStreamdevServerMenuSetupPage: public cMenuSetupPage {
private:
static const char* StreamTypes[];
static const char* SuspendModes[];
cStreamdevServerSetup m_NewSetup;
void AddCategory(const char *Title);
void Set();
protected:
virtual void Store(void);
virtual eOSState ProcessKey(eKeys Key);
public:
cStreamdevServerMenuSetupPage(void);

View File

@@ -9,18 +9,47 @@
#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 < 10516
#error "VDR-1.5.16 API version or greater is required!"
#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()
@@ -98,6 +127,9 @@ bool cPluginStreamdevServer::Start(void)
cStreamdevServer::Initialize();
m_Suspend = StreamdevServerSetup.StartSuspended == ssAuto ?
!cDevice::PrimaryDevice()->HasDecoder() :
StreamdevServerSetup.StartSuspended;
return true;
}
@@ -119,20 +151,29 @@ cString cPluginStreamdevServer::Active(void)
const char *cPluginStreamdevServer::MainMenuEntry(void)
{
if (StreamdevServerSetup.SuspendMode == smOffer && !cSuspendCtl::IsActive())
return tr("Suspend Live TV");
return NULL;
return !StreamdevServerSetup.HideMenuEntry ? tr("Streamdev Connections") : NULL;
}
cOsdObject *cPluginStreamdevServer::MainMenuAction(void)
{
cControl::Launch(new cSuspendCtl);
return NULL;
return new cStreamdevServerMenu();
}
void cPluginStreamdevServer::MainThreadHook(void)
{
cStreamdevServer::MainThreadHook();
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)
@@ -145,4 +186,90 @@ 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!

View File

@@ -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);
@@ -29,6 +48,9 @@ public:
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

View File

@@ -8,8 +8,6 @@
#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"
@@ -47,15 +45,18 @@ void cStreamdevWriter::Action(void)
int count, offset = 0;
int timeout = 0;
#if APIVERSNUM >= 10705
SetPriority(-3);
#endif
sel.Clear();
sel.Add(*m_Socket, true);
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) {
@@ -102,72 +103,82 @@ void cStreamdevWriter::Action(void)
}
}
}
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, const cServerConnection *Connection):
cThread(Name),
m_Connection(Connection),
m_Writer(NULL),
m_RingBuffer(new cStreamdevBuffer(STREAMERBUFSIZE, TS_SIZE * 2,
true, "streamdev-streamer")),
m_SendBuffer(new cStreamdevBuffer(WRITERBUFSIZE, TS_SIZE * 2))
m_Remux(new cRemuxDummy()),
m_Writer(NULL)
{
m_RingBuffer->SetTimeouts(0, 100);
m_SendBuffer->SetTimeouts(100, 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);
Attach();
}
void cStreamdevStreamer::Activate(bool On)
{
if (On && !Active()) {
Dprintf("activate streamer\n");
m_Writer->Start();
m_Writer->Start();
if (!Active()) {
Dprintf("start streamer\n");
cThread::Start();
}
Attach();
}
void cStreamdevStreamer::Stop(void)
{
Detach();
if (Running()) {
Dprintf("stopping streamer\n");
Dprintf("stop streamer\n");
Cancel(3);
}
if (m_Writer) {
Detach();
DELETENULL(m_Writer);
}
Dprintf("stop writer\n");
DELETENULL(m_Writer);
}
void cStreamdevStreamer::Action(void)
{
#if APIVERSNUM >= 10705
SetPriority(-3);
#endif
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);
DelFromReceiver(count);
}
}
}
int cStreamdevStreamer::Put(const uchar *Data, int Count) {
return m_Remux->Put(Data, Count);
}

View File

@@ -9,6 +9,8 @@
#include <vdr/ringbuffer.h>
#include <vdr/tools.h>
#include "remux/tsremux.h"
class cTBSocket;
class cStreamdevStreamer;
class cServerConnection;
@@ -17,7 +19,6 @@ class cServerConnection;
#define TS_SIZE 188
#endif
#define STREAMERBUFSIZE (20000 * TS_SIZE)
#define WRITERBUFSIZE (20000 * TS_SIZE)
// --- cStreamdevBuffer -------------------------------------------------------
@@ -66,14 +67,18 @@ public:
class cStreamdevStreamer: public cThread {
private:
const cServerConnection *m_Connection;
cStreamdevWriter *m_Writer;
cStreamdevBuffer *m_RingBuffer;
cStreamdevBuffer *m_SendBuffer;
Streamdev::cTSRemux *m_Remux;
cStreamdevWriter *m_Writer;
cStreamdevBuffer *m_SendBuffer;
protected:
virtual uchar* GetFromReceiver(int &Count) = 0;
virtual void DelFromReceiver(int Count) = 0;
virtual int Put(const uchar *Data, int Count);
virtual void Action(void);
bool IsRunning(void) const { return m_Writer; }
void SetRemux(Streamdev::cTSRemux *Remux) { delete m_Remux; m_Remux = Remux; }
public:
cStreamdevStreamer(const char *Name, const cServerConnection *Connection = NULL);
@@ -83,18 +88,16 @@ public:
virtual void Start(cTBSocket *Socket);
virtual void Stop(void);
virtual bool IsReceiving(void) const = 0;
bool Abort(void);
void Activate(bool On);
int Receive(uchar *Data, int Length) { return m_RingBuffer->PutTS(Data, Length); }
void ReportOverflow(int Bytes) { m_RingBuffer->ReportOverflow(Bytes); }
virtual int Put(const uchar *Data, int Count) { return m_SendBuffer->PutTS(Data, Count); }
virtual uchar *Get(int &Count) { return m_SendBuffer->Get(Count); }
virtual void Del(int Count) { m_SendBuffer->Del(Count); }
uchar *Get(int &Count) { return m_Remux->Get(Count); }
void Del(int Count) { m_Remux->Del(Count); }
virtual void Detach(void) {}
virtual void Attach(void) {}
virtual cString ToText() const { return ""; };
};
inline bool cStreamdevStreamer::Abort(void)

View File

@@ -39,7 +39,7 @@ void cSuspendLive::Action(void) {
bool cSuspendCtl::m_Active = false;
cSuspendCtl::cSuspendCtl(void):
cControl(m_Suspend = new cSuspendLive) {
cControl(m_Suspend = new cSuspendLive, true) {
m_Active = true;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
#include "tools/socket.h"
#include "tools/select.h"
#include <vdr/tools.h>
#include <string.h>
@@ -27,7 +28,7 @@ cTBSocket::~cTBSocket() {
if (IsOpen()) Close();
}
bool cTBSocket::Connect(const std::string &Host, unsigned int Port) {
bool cTBSocket::Connect(const std::string &Host, unsigned int Port, unsigned int TimeoutMs) {
socklen_t len;
int socket;
@@ -45,13 +46,36 @@ bool cTBSocket::Connect(const std::string &Host, unsigned int Port) {
return false;
}
if (TimeoutMs > 0 && ::fcntl(socket, F_SETFL, O_NONBLOCK) == -1) {
::close(socket);
return false;
}
m_RemoteAddr.sin_family = AF_INET;
m_RemoteAddr.sin_port = htons(Port);
m_RemoteAddr.sin_addr.s_addr = inet_addr(Host.c_str());
if (::connect(socket, (struct sockaddr*)&m_RemoteAddr,
sizeof(m_RemoteAddr)) == -1) {
::close(socket);
return false;
if (::connect(socket, (struct sockaddr*)&m_RemoteAddr, sizeof(m_RemoteAddr)) == -1) {
if (TimeoutMs > 0 && errno == EINPROGRESS) {
int so_error;
socklen_t len = sizeof(so_error);
cTBSelect select;
select.Add(socket);
if (select.Select(TimeoutMs) == -1 ||
::getsockopt(socket, SOL_SOCKET, SO_ERROR, &so_error, &len) == -1) {
::close(socket);
return false;
}
if (so_error) {
errno = so_error;
::close(socket);
return false;
}
}
else {
::close(socket);
return false;
}
}
if (m_Type == SOCK_STREAM) {

View File

@@ -32,11 +32,13 @@ public:
Reimplemented for TCP/IPv4 sockets. */
virtual ssize_t SysWrite(const void *Buffer, size_t Length) const;
/* Connect() tries to connect an available local socket to the port given
by Port of the target host given by Host in numbers-and-dots notation
(i.e. "212.43.45.21"). Returns true if the connection attempt was
successful and false otherwise, setting errno appropriately. */
virtual bool Connect(const std::string &Host, uint Port);
/* Connect() tries to connect an available local socket within TimeoutMs
milliseconds to the port given by Port of the target host given by
Host in numbers-and-dots notation (i.e. "212.43.45.21"). A TimeoutMs
of 0 will disable non-blocking IO for the connect call. Returns true
if the connection attempt was successful and false otherwise, setting
errno appropriately. */
virtual bool Connect(const std::string &Host, uint Port, uint TimeoutMs = 0);
/* Shutdown() shuts down one or both ends of a socket. If called with How
set to SHUT_RD, further reads on this socket will be denied. If called
@@ -60,7 +62,7 @@ public:
/* Accept() returns a newly created cTBSocket, which is connected to the
first connection request on the queue of pending connections of a
listening socket. If no connection request was pending, or if any other
error occured, the resulting cTBSocket is closed. */
error occurred, the resulting cTBSocket is closed. */
virtual cTBSocket Accept(void) const;
/* Accept() extracts the first connection request on the queue of pending

View File

@@ -57,15 +57,20 @@ ssize_t cTBSource::Write(const void *Buffer, size_t Length) {
bool cTBSource::TimedWrite(const void *Buffer, size_t Length, uint TimeoutMs) {
cTBSelect sel;
int ms, offs;
cTimeMs starttime;
ms = TimeoutMs;
offs = 0;
sel.Clear();
sel.Add(m_Filed, true);
while (Length > 0) {
int b;
ms = TimeoutMs - starttime.Elapsed();
if (ms <= 0) {
errno = ETIMEDOUT;
return false;
}
if (sel.Select(ms) == -1)
return false;
@@ -76,11 +81,6 @@ bool cTBSource::TimedWrite(const void *Buffer, size_t Length, uint TimeoutMs) {
Length -= b;
}
ms = TimeoutMs - starttime.Elapsed();
if (ms <= 0) {
errno = ETIMEDOUT;
return false;
}
}
return true;
}

View File

@@ -54,7 +54,7 @@ public:
/* Close() resets the source to the uninitialized state (IsOpen() == false)
and must be called by any derivations after really closing the source.
Returns true on success and false on error, setting errno appropriately.
The object is in closed state afterwards, even if an error occured. */
The object is in closed state afterwards, even if an error occurred. */
virtual bool Close(void);
/* Read() reads at most Length bytes into the storage pointed to by Buffer,