mirror of
https://github.com/rofafor/vdr-plugin-iptv.git
synced 2023-10-10 11:37:03 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c975d458f5 | ||
|
|
11d864c16b | ||
|
|
75d17b289b | ||
|
|
547306b67c | ||
|
|
763c83209d | ||
|
|
af85ef1703 | ||
|
|
24ecadf414 | ||
|
|
f4126b7e2c | ||
|
|
c7cbde301b | ||
|
|
06506c41f6 | ||
|
|
c33b05076a | ||
|
|
38bd9a21f6 | ||
|
|
4c7b2ea69b | ||
|
|
c475b26515 | ||
|
|
9145fb8bb8 | ||
|
|
7a8b2962b2 | ||
|
|
626d0402c2 | ||
|
|
b3b06e569f | ||
|
|
ff84c54d50 | ||
|
|
4c39d8cf72 | ||
|
|
0b790260fa | ||
|
|
79113bbe09 | ||
|
|
30de763a4b | ||
|
|
868865c47d | ||
|
|
a73921041f | ||
|
|
49e3a9e23a | ||
|
|
73311873bc | ||
|
|
816c357065 |
30
HISTORY
30
HISTORY
@@ -158,3 +158,33 @@ VDR Plugin 'iptv' Revision History
|
||||
- Canonicalized the configuration directory.
|
||||
- Added support for LDFLAGS.
|
||||
- Added cppcheck target into Makefile.
|
||||
|
||||
2012-04-02: Version 0.5.1
|
||||
|
||||
- Updated for vdr-1.7.27.
|
||||
- Updated Makefile.
|
||||
- Silenced compilation warnings.
|
||||
- Fixed channel switching bugs.
|
||||
- Added support for a service interface.
|
||||
- Changed UDP protocol to always utilize the source address
|
||||
validation.
|
||||
|
||||
2012-04-26: Version 0.5.2
|
||||
|
||||
- Fixed connection problems in HTTP protocol.
|
||||
|
||||
2012-06-03: Version 1.0.0
|
||||
|
||||
- Optimized reading from UDP sockets.
|
||||
- Fixed ProvidesChannel method.
|
||||
|
||||
2012-07-10: Version 1.0.1
|
||||
|
||||
- Added FreeBSD support (Thanks to Jürgen Lock).
|
||||
|
||||
2012-09-30: Version 1.1.0
|
||||
|
||||
- Updated for vdr-1.7.30.
|
||||
- Added support for source-specific multicasts (SSM).
|
||||
- Changed default external script directory from the
|
||||
configuration to the resource.
|
||||
|
||||
25
Makefile
25
Makefile
@@ -5,9 +5,15 @@
|
||||
# Debugging on/off
|
||||
#IPTV_DEBUG = 1
|
||||
|
||||
# Default shell for EXT protocol
|
||||
#IPTV_EXTSHELL = /bin/bash
|
||||
|
||||
# Strip debug symbols? Set eg. to /bin/true if not
|
||||
STRIP = strip
|
||||
|
||||
# Install command
|
||||
INSTALL = cp --remove-destination
|
||||
|
||||
# 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.
|
||||
@@ -19,18 +25,19 @@ PLUGIN = iptv
|
||||
### The version number of this plugin (taken from the main source file):
|
||||
|
||||
VERSION = $(shell grep 'const char VERSION\[\] *=' $(PLUGIN).c | awk '{ print $$5 }' | sed -e 's/[";]//g')
|
||||
GITTAG = $(shell git describe --always 2>/dev/null)
|
||||
|
||||
### The C++ compiler and options:
|
||||
|
||||
CXX ?= g++
|
||||
CXXFLAGS ?= -fPIC -g -O3 -Wall -Wextra -Wswitch-default -Wfloat-equal -Wundef -Wpointer-arith -Wconversion -Wcast-align -Wredundant-decls -Wno-unused-parameter -Woverloaded-virtual -Wno-parentheses
|
||||
CXXFLAGS ?= -fPIC -g -O3 -Wall -Wextra -Wswitch-default -Wfloat-equal -Wundef -Wpointer-arith -Wconversion -Wcast-align -Wredundant-decls -Wno-unused-parameter -Werror=overloaded-virtual -Wno-parentheses
|
||||
LDFLAGS ?= -Wl,--as-needed
|
||||
|
||||
### The directory environment:
|
||||
|
||||
VDRDIR = ../../..
|
||||
LIBDIR = ../../lib
|
||||
TMPDIR = /tmp
|
||||
VDRDIR ?= ../../..
|
||||
LIBDIR ?= ../../lib
|
||||
TMPDIR ?= /tmp
|
||||
|
||||
### Make sure that necessary options are included:
|
||||
|
||||
@@ -59,6 +66,14 @@ ifdef IPTV_DEBUG
|
||||
DEFINES += -DDEBUG
|
||||
endif
|
||||
|
||||
ifdef IPTV_EXTSHELL
|
||||
DEFINES += -DEXTSHELL='"$(IPTV_EXTSHELL)"'
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(GITTAG)),)
|
||||
DEFINES += -DGITVERSION='"-GIT-$(GITTAG)"'
|
||||
endif
|
||||
|
||||
.PHONY: all all-redirect
|
||||
all-redirect: all
|
||||
|
||||
@@ -118,7 +133,7 @@ libvdr-$(PLUGIN).so: $(OBJS)
|
||||
ifndef IPTV_DEBUG
|
||||
@$(STRIP) $@
|
||||
endif
|
||||
@cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION)
|
||||
@$(INSTALL) $@ $(LIBDIR)/$@.$(APIVERSION)
|
||||
|
||||
dist: $(I18Npo) clean
|
||||
@-rm -rf $(TMPDIR)/$(ARCHIVE)
|
||||
|
||||
26
README
26
README
@@ -44,7 +44,7 @@ cd /put/your/path/here/VDR/PLUGINS/src
|
||||
tar -xzf /put/your/path/here/vdr-iptv-X.Y.Z.tgz
|
||||
ln -s iptv-X.Y.Z iptv
|
||||
cd /put/your/path/here/VDR
|
||||
cp -R PLUGINS/src/iptv/iptv /path/to/vdrconf/plugins/
|
||||
cp -R PLUGINS/src/iptv/iptv /path/to/vdrresource/plugins/
|
||||
make
|
||||
make plugins
|
||||
./vdr -P iptv
|
||||
@@ -91,14 +91,15 @@ Configuration:
|
||||
TV4;IPTV:40:S=1|P=0|F=EXT|U=iptvstream.sh|A=0:I:0:0:680:0:0:4:0:0:0
|
||||
TV3;IPTV:30:S=0|P=1|F=FILE|U=/video/stream.ts|A=5:I:0:514:670:2321:0:3:0:0:0
|
||||
TV2;IPTV:20:S=0|P=1|F=HTTP|U=127.0.0.1/TS/2|A=3000:I:0:513:660:2321:0:2:0:0:0
|
||||
TV1;IPTV:10:S=1|P=0|F=UDP|U=127.0.0.1@127.0.0.1|A=1234:I:0:512:650:2321:0:1:0:0:0
|
||||
TV1;IPTV:10:S=1|P=0|F=UDP|U=127.0.0.1|A=1234:I:0:512:650:2321:0:1:0:0:0
|
||||
^ ^ ^ ^ ^ ^ ^
|
||||
| | | | | | Source type ("I")
|
||||
| | | | | Stream parameter (multicast port
|
||||
| | | | | number, HTTP port number, file delay
|
||||
| | | | | (ms), script parameter)
|
||||
| | | | Stream address (multicast address, URL, file
|
||||
| | | | location, script location)
|
||||
| | | | Stream address (multicast source@group address,
|
||||
| | | | URL, file location, script location)
|
||||
| | | Stream protocol ("UDP", "HTTP", "FILE", "EXT")
|
||||
| | Pid scanner ("0" disable, "1" enable)
|
||||
| Section id (Sid/Nid/Tid) scanner ("0" disable, "1" enable)
|
||||
@@ -106,19 +107,22 @@ Configuration:
|
||||
|
||||
- UDP multicast rules for iptables firewall
|
||||
|
||||
# Multicast UDP -packets
|
||||
# Multicast UDP packets
|
||||
iptables -A INPUT -i eth0 -p udp -d 224.0.0.0/4 --dport 1234 -j ACCEPT
|
||||
|
||||
# IGMP required by multicasts
|
||||
iptables -A INPUT -i eth0 -p igmp -d 224.0.0.0/4 -j ACCEPT
|
||||
|
||||
# Default routing for multicast
|
||||
route add -net 224.0.0.0 netmask 224.0.0.0 eth0
|
||||
|
||||
External streaming:
|
||||
|
||||
- To watch an externally received channel add an EXT entry to channels.conf
|
||||
and specify a script name and parameter. The specified script is executed
|
||||
from plugin configuration directory when VDR tunes to the channel. The
|
||||
specified script parameter is passed to the script and it can be used to
|
||||
select for example between different URLs.
|
||||
from plugin resource directory when VDR tunes to the channel. The specified
|
||||
script parameter is passed to the script and it can be used to select for
|
||||
example between different URLs.
|
||||
|
||||
- When an EXT channel is opened the IPTV plugin opens an UDP listening port
|
||||
on the localhost. The external script is responsible for supplying IPTV
|
||||
@@ -161,12 +165,14 @@ Notes:
|
||||
- EIT scanning functionality can be disabled for all IPTV channels by applying
|
||||
the "disable_eitscan" patch to the VDR.
|
||||
|
||||
- Source address validation can be enabled for UDP protocol separated by
|
||||
adding the source address after a ';' character: "U=239.192.0.1;239.192.0.2"
|
||||
|
||||
- Section id and pid scanners should be disabled after the correct data is
|
||||
found. This can be made via VDR's channel editor.
|
||||
|
||||
- Source-specific multicast (SSM) can be enabled by defining both the source
|
||||
address and the group address separated by a '@' character. This will use
|
||||
IGMP v3 protocol:
|
||||
"U=<source address>@<group address>"
|
||||
|
||||
Acknowledgements:
|
||||
|
||||
- The IPTV section filtering code is derived from Linux kernel.
|
||||
|
||||
17
common.h
17
common.h
@@ -30,6 +30,8 @@
|
||||
#define IPTV_DEVICE_INFO_GENERAL 1
|
||||
#define IPTV_DEVICE_INFO_PIDS 2
|
||||
#define IPTV_DEVICE_INFO_FILTERS 3
|
||||
#define IPTV_DEVICE_INFO_PROTOCOL 4
|
||||
#define IPTV_DEVICE_INFO_BITRATE 5
|
||||
|
||||
#define IPTV_STATS_ACTIVE_PIDS_COUNT 10
|
||||
#define IPTV_STATS_ACTIVE_FILTERS_COUNT 10
|
||||
@@ -37,13 +39,14 @@
|
||||
#define SECTION_FILTER_TABLE_SIZE 7
|
||||
|
||||
#define ERROR_IF_FUNC(exp, errstr, func, ret) \
|
||||
do { \
|
||||
if (exp) { \
|
||||
char tmp[64]; \
|
||||
error(errstr": %s", strerror_r(errno, tmp, sizeof(tmp))); \
|
||||
func; \
|
||||
ret; \
|
||||
} \
|
||||
do { \
|
||||
if (exp) { \
|
||||
char tmp[64]; \
|
||||
strerror_r(errno, tmp, sizeof(tmp)); \
|
||||
error(errstr": %s", tmp); \
|
||||
func; \
|
||||
ret; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
|
||||
|
||||
2
config.c
2
config.c
@@ -42,6 +42,6 @@ void cIptvConfig::SetDisabledFilters(unsigned int Index, int Number)
|
||||
|
||||
void cIptvConfig::SetConfigDirectory(const char *directoryP)
|
||||
{
|
||||
debug("cIptvConfig::SetConfigDirectory(%s)", directoryP);
|
||||
debug("cIptvConfig::SetConfigDirectory(%s)\n", directoryP);
|
||||
ERROR_IF(!realpath(directoryP, configDirectory), "Cannot canonicalize configuration directory");
|
||||
}
|
||||
|
||||
72
device.c
72
device.c
@@ -123,7 +123,7 @@ cIptvDevice *cIptvDevice::GetIptvDevice(int CardIndex)
|
||||
cString cIptvDevice::GetGeneralInformation(void)
|
||||
{
|
||||
//debug("cIptvDevice::GetGeneralInformation(%d)\n", deviceIndex);
|
||||
return cString::sprintf("IPTV device: %d\nCardIndex: %d\n%s%s%sChannel: %s",
|
||||
return cString::sprintf("IPTV device: %d\nCardIndex: %d\nStream: %s\nStream bitrate: %s\n%sChannel: %s",
|
||||
deviceIndex, CardIndex(),
|
||||
pIptvStreamer ? *pIptvStreamer->GetInformation() : "",
|
||||
pIptvStreamer ? *pIptvStreamer->GetStreamerStatistic() : "",
|
||||
@@ -169,6 +169,12 @@ cString cIptvDevice::GetInformation(unsigned int Page)
|
||||
case IPTV_DEVICE_INFO_FILTERS:
|
||||
info = GetFiltersInformation();
|
||||
break;
|
||||
case IPTV_DEVICE_INFO_PROTOCOL:
|
||||
info = pIptvStreamer ? *pIptvStreamer->GetInformation() : "";
|
||||
break;
|
||||
case IPTV_DEVICE_INFO_BITRATE:
|
||||
info = pIptvStreamer ? *pIptvStreamer->GetStreamerStatistic() : "";
|
||||
break;
|
||||
default:
|
||||
info = cString::sprintf("%s%s%s",
|
||||
*GetGeneralInformation(),
|
||||
@@ -179,6 +185,18 @@ cString cIptvDevice::GetInformation(unsigned int Page)
|
||||
return info;
|
||||
}
|
||||
|
||||
cString cIptvDevice::DeviceType(void) const
|
||||
{
|
||||
debug("cIptvDevice::DeviceType(%d)\n", deviceIndex);
|
||||
return "IPTV";
|
||||
}
|
||||
|
||||
cString cIptvDevice::DeviceName(void) const
|
||||
{
|
||||
debug("cIptvDevice::DeviceName(%d)\n", deviceIndex);
|
||||
return cString::sprintf("IPTV %d", deviceIndex);
|
||||
}
|
||||
|
||||
int cIptvDevice::SignalStrength(void) const
|
||||
{
|
||||
debug("cIptvDevice::SignalStrength(%d)\n", deviceIndex);
|
||||
@@ -206,11 +224,20 @@ bool cIptvDevice::ProvidesTransponder(const cChannel *Channel) const
|
||||
bool cIptvDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers) const
|
||||
{
|
||||
bool result = false;
|
||||
bool needsDetachReceivers = Receiving(true) && Channel && !(Channel->GetChannelID() == channelId);
|
||||
bool hasPriority = Priority == IDLEPRIORITY || Priority > this->Priority();
|
||||
bool needsDetachReceivers = false;
|
||||
|
||||
debug("cIptvDevice::ProvidesChannel(%d)\n", deviceIndex);
|
||||
if (ProvidesTransponder(Channel))
|
||||
result = true;
|
||||
|
||||
if (Channel && ProvidesTransponder(Channel)) {
|
||||
result = hasPriority;
|
||||
if (Receiving()) {
|
||||
if (Channel->GetChannelID() == channelId)
|
||||
result = true;
|
||||
else
|
||||
needsDetachReceivers = Receiving();
|
||||
}
|
||||
}
|
||||
if (NeedsDetachReceivers)
|
||||
*NeedsDetachReceivers = needsDetachReceivers;
|
||||
return result;
|
||||
@@ -316,26 +343,37 @@ int cIptvDevice::OpenFilter(u_short Pid, u_char Tid, u_char Mask)
|
||||
for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) {
|
||||
if (!secfilters[i]) {
|
||||
//debug("cIptvDevice::OpenFilter(%d): Pid=%d Tid=%02X Mask=%02X Index=%d\n", deviceIndex, Pid, Tid, Mask, i);
|
||||
secfilters[i] = new cIptvSectionFilter(deviceIndex, i, Pid, Tid, Mask);
|
||||
return secfilters[i]->GetReadDesc();
|
||||
secfilters[i] = new cIptvSectionFilter(deviceIndex, Pid, Tid, Mask);
|
||||
if (secfilters[i])
|
||||
return i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// No free filter slot found
|
||||
return -1;
|
||||
}
|
||||
|
||||
int cIptvDevice::ReadFilter(int Handle, void *Buffer, size_t Length)
|
||||
{
|
||||
// Lock
|
||||
cMutexLock MutexLock(&mutex);
|
||||
// ... and load
|
||||
if (secfilters[Handle]) {
|
||||
return secfilters[Handle]->Read(Buffer, Length);
|
||||
//debug("cIptvDevice::ReadFilter(%d): %d %d\n", deviceIndex, Handle, Length);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cIptvDevice::CloseFilter(int Handle)
|
||||
{
|
||||
// Lock
|
||||
cMutexLock MutexLock(&mutex);
|
||||
// Search the filter for deletion
|
||||
for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) {
|
||||
if (secfilters[i] && (Handle == secfilters[i]->GetReadDesc())) {
|
||||
//debug("cIptvDevice::CloseFilter(%d): %d\n", deviceIndex, Handle);
|
||||
DeleteFilter(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// ... and load
|
||||
if (secfilters[Handle]) {
|
||||
//debug("cIptvDevice::CloseFilter(%d): %d\n", deviceIndex, Handle);
|
||||
DeleteFilter(Handle);
|
||||
}
|
||||
}
|
||||
|
||||
bool cIptvDevice::OpenDvr(void)
|
||||
@@ -368,6 +406,12 @@ bool cIptvDevice::HasLock(int TimeoutMs)
|
||||
return (!IsBuffering());
|
||||
}
|
||||
|
||||
bool cIptvDevice::HasInternalCam(void)
|
||||
{
|
||||
//debug("cIptvDevice::HasInternalCam(%d)\n", deviceIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
void cIptvDevice::ResetBuffering(void)
|
||||
{
|
||||
debug("cIptvDevice::ResetBuffering(%d)\n", deviceIndex);
|
||||
|
||||
8
device.h
8
device.h
@@ -76,6 +76,9 @@ private:
|
||||
bool IsBlackListed(u_short Pid, u_char Tid, u_char Mask) const;
|
||||
|
||||
// for channel info
|
||||
public:
|
||||
virtual cString DeviceType(void) const;
|
||||
virtual cString DeviceName(void) const;
|
||||
virtual int SignalStrength(void) const;
|
||||
virtual int SignalQuality(void) const;
|
||||
|
||||
@@ -99,11 +102,16 @@ protected:
|
||||
// for section filtering
|
||||
public:
|
||||
virtual int OpenFilter(u_short Pid, u_char Tid, u_char Mask);
|
||||
virtual int ReadFilter(int Handle, void *Buffer, size_t Length);
|
||||
virtual void CloseFilter(int Handle);
|
||||
|
||||
// for transponder lock
|
||||
public:
|
||||
virtual bool HasLock(int);
|
||||
|
||||
// for common interface
|
||||
public:
|
||||
virtual bool HasInternalCam(void);
|
||||
};
|
||||
|
||||
#endif // __IPTV_DEVICE_H
|
||||
|
||||
27
iptv.c
27
iptv.c
@@ -11,12 +11,17 @@
|
||||
#include "config.h"
|
||||
#include "setup.h"
|
||||
#include "device.h"
|
||||
#include "iptvservice.h"
|
||||
|
||||
#if defined(APIVERSNUM) && APIVERSNUM < 10721
|
||||
#error "VDR-1.7.21 API version or greater is required!"
|
||||
#if defined(APIVERSNUM) && APIVERSNUM < 10730
|
||||
#error "VDR-1.7.30 API version or greater is required!"
|
||||
#endif
|
||||
|
||||
const char VERSION[] = "0.5.0";
|
||||
#ifndef GITVERSION
|
||||
#define GITVERSION ""
|
||||
#endif
|
||||
|
||||
const char VERSION[] = "1.1.0" GITVERSION;
|
||||
static const char DESCRIPTION[] = trNOOP("Experience the IPTV");
|
||||
|
||||
class cPluginIptv : public cPlugin {
|
||||
@@ -94,7 +99,7 @@ bool cPluginIptv::Initialize(void)
|
||||
{
|
||||
debug("cPluginIptv::Initialize()\n");
|
||||
// Initialize any background activities the plugin shall perform.
|
||||
IptvConfig.SetConfigDirectory(cPlugin::ConfigDirectory(PLUGIN_NAME_I18N));
|
||||
IptvConfig.SetConfigDirectory(cPlugin::ResourceDirectory(PLUGIN_NAME_I18N));
|
||||
return cIptvDevice::Initialize(deviceCount);
|
||||
}
|
||||
|
||||
@@ -196,8 +201,18 @@ bool cPluginIptv::SetupParse(const char *Name, const char *Value)
|
||||
|
||||
bool cPluginIptv::Service(const char *Id, void *Data)
|
||||
{
|
||||
//debug("cPluginIptv::Service()\n");
|
||||
// Handle custom service requests from other plugins
|
||||
debug("cPluginIptv::Service()\n");
|
||||
if (strcmp(Id,"IptvService-v1.0") == 0) {
|
||||
if (Data) {
|
||||
IptvService_v1_0 *data = (IptvService_v1_0*)Data;
|
||||
cIptvDevice *dev = cIptvDevice::GetIptvDevice(data->cardIndex);
|
||||
if (!dev)
|
||||
return false;
|
||||
data->protocol = dev->GetInformation(IPTV_DEVICE_INFO_PROTOCOL);
|
||||
data->bitrate = dev->GetInformation(IPTV_DEVICE_INFO_BITRATE);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
22
iptvservice.h
Normal file
22
iptvservice.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* iptvservice.h: IPTV plugin for the Video Disk Recorder
|
||||
*
|
||||
* See the README file for copyright information and how to reach the author.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __IPTVSERVICE_H
|
||||
#define __IPTVSERVICE_H
|
||||
|
||||
#include <vdr/tools.h>
|
||||
|
||||
#define stIptv ('I' << 24)
|
||||
|
||||
struct IptvService_v1_0 {
|
||||
unsigned int cardIndex;
|
||||
cString protocol;
|
||||
cString bitrate;
|
||||
};
|
||||
|
||||
#endif //__IPTVSERVICE_H
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
diff -Nru vdr-1.7.23-vanilla/pat.c vdr-1.7.23-disable_ca_updates/pat.c
|
||||
--- vdr-1.7.23-vanilla/pat.c 2012-01-15 20:26:29.000000000 +0200
|
||||
+++ vdr-1.7.23-disable_ca_updates/pat.c 2012-02-02 18:48:23.000000000 +0200
|
||||
@@ -537,6 +537,7 @@
|
||||
}
|
||||
if (Setup.UpdateChannels >= 2) {
|
||||
Channel->SetPids(Vpid, Ppid, Vtype, Apids, Atypes, ALangs, Dpids, Dtypes, DLangs, Spids, SLangs, Tpid);
|
||||
+ if (!cSource::IsType(Channel->Source(), 'I'))
|
||||
Channel->SetCaIds(CaDescriptors->CaIds());
|
||||
Channel->SetSubtitlingDescriptors(SubtitlingTypes, CompositionPageIds, AncillaryPageIds);
|
||||
}
|
||||
@@ -5,10 +5,10 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: vdr-iptv 0.5.0\n"
|
||||
"Project-Id-Version: vdr-iptv 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: <see README>\n"
|
||||
"POT-Creation-Date: 2012-02-02 02:02+0300\n"
|
||||
"PO-Revision-Date: 2012-02-02 02:02+0300\n"
|
||||
"POT-Creation-Date: 2012-06-03 06:03+0300\n"
|
||||
"PO-Revision-Date: 2012-06-03 06:03+0300\n"
|
||||
"Last-Translator: Tobias Grimm <tg@e-tobi.net>\n"
|
||||
"Language-Team: German <vdr@linuxtv.org>\n"
|
||||
"Language: de\n"
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: vdr-iptv 0.5.0\n"
|
||||
"Project-Id-Version: vdr-iptv 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: <see README>\n"
|
||||
"POT-Creation-Date: 2012-02-02 02:02+0300\n"
|
||||
"PO-Revision-Date: 2012-02-02 02:02+0300\n"
|
||||
"POT-Creation-Date: 2012-06-03 06:03+0300\n"
|
||||
"PO-Revision-Date: 2012-06-03 06:03+0300\n"
|
||||
"Last-Translator: Rolf Ahrenberg\n"
|
||||
"Language-Team: Finnish <vdr@linuxtv.org>\n"
|
||||
"Language: fi\n"
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: vdr-iptv 0.5.0\n"
|
||||
"Project-Id-Version: vdr-iptv 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: <see README>\n"
|
||||
"POT-Creation-Date: 2012-02-02 02:02+0300\n"
|
||||
"PO-Revision-Date: 2010-09-09 09:09+0300\n"
|
||||
"POT-Creation-Date: 2012-06-03 06:03+0300\n"
|
||||
"PO-Revision-Date: 2012-06-03 06:03+0300\n"
|
||||
"Last-Translator: NIVAL Michaël <mnival@club-internet.fr>\n"
|
||||
"Language-Team: French <vdr@linuxtv.org>\n"
|
||||
"Language: fr\n"
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: vdr-iptv 0.5.0\n"
|
||||
"Project-Id-Version: vdr-iptv 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: <see README>\n"
|
||||
"POT-Creation-Date: 2012-02-02 02:02+0300\n"
|
||||
"PO-Revision-Date: 2012-02-02 02:02+0300\n"
|
||||
"POT-Creation-Date: 2012-06-03 06:03+0300\n"
|
||||
"PO-Revision-Date: 2012-06-03 06:03+0300\n"
|
||||
"Last-Translator: Diego Pierotto <vdr-italian@tiscali.it>\n"
|
||||
"Language-Team: Italian <vdr@linuxtv.org>\n"
|
||||
"Language: it\n"
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: vdr-iptv 0.5.0\n"
|
||||
"Project-Id-Version: vdr-iptv 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: <see README>\n"
|
||||
"POT-Creation-Date: 2012-02-02 02:02+0300\n"
|
||||
"PO-Revision-Date: 2012-02-02 02:02+0300\n"
|
||||
"POT-Creation-Date: 2012-06-03 06:03+0300\n"
|
||||
"PO-Revision-Date: 2012-06-03 06:03+0300\n"
|
||||
"Last-Translator: Carel\n"
|
||||
"Language-Team: Dutch <vdr@linuxtv.org>\n"
|
||||
"Language: nl\n"
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: vdr-iptv 0.5.0\n"
|
||||
"Project-Id-Version: vdr-iptv 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: <see README>\n"
|
||||
"POT-Creation-Date: 2012-02-02 02:02+0300\n"
|
||||
"PO-Revision-Date: 2010-09-09 09:09+0300\n"
|
||||
"POT-Creation-Date: 2012-06-03 06:03+0300\n"
|
||||
"PO-Revision-Date: 2012-06-03 06:03+0300\n"
|
||||
"Last-Translator: Alexander Gross <Bikalexander@gmail.com>\n"
|
||||
"Language-Team: Russian <vdr@linuxtv.org>\n"
|
||||
"Language: ru\n"
|
||||
|
||||
@@ -19,10 +19,15 @@
|
||||
#include "config.h"
|
||||
#include "protocolext.h"
|
||||
|
||||
#ifndef EXTSHELL
|
||||
#define EXTSHELL "/bin/bash"
|
||||
#endif
|
||||
|
||||
cIptvProtocolExt::cIptvProtocolExt()
|
||||
: pid(-1),
|
||||
scriptFile(""),
|
||||
scriptParameter(0)
|
||||
scriptParameter(0),
|
||||
streamPort(0)
|
||||
{
|
||||
debug("cIptvProtocolExt::cIptvProtocolExt()\n");
|
||||
}
|
||||
@@ -53,9 +58,11 @@ void cIptvProtocolExt::ExecuteScript(void)
|
||||
for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++)
|
||||
close(i);
|
||||
// Execute the external script
|
||||
cString cmd = cString::sprintf("%s %d %d", *scriptFile, scriptParameter, socketPort);
|
||||
cString cmd = cString::sprintf("%s %d %d", *scriptFile, scriptParameter, streamPort);
|
||||
debug("cIptvProtocolExt::ExecuteScript(child): %s\n", *cmd);
|
||||
if (execl("/bin/bash", "sh", "-c", *cmd, (char *)NULL) == -1) {
|
||||
// Create a new session for a process group
|
||||
ERROR_IF_RET(setsid() == -1, "setsid()", _exit(-1));
|
||||
if (execl(EXTSHELL, "sh", "-c", *cmd, (char *)NULL) == -1) {
|
||||
error("Script execution failed: %s", *cmd);
|
||||
_exit(-1);
|
||||
}
|
||||
@@ -74,27 +81,36 @@ void cIptvProtocolExt::TerminateScript(void)
|
||||
if (pid > 0) {
|
||||
const unsigned int timeoutms = 100;
|
||||
unsigned int waitms = 0;
|
||||
siginfo_t waitStatus;
|
||||
bool waitOver = false;
|
||||
// signal and wait for termination
|
||||
int retval = kill(pid, SIGINT);
|
||||
// Signal and wait for termination
|
||||
int retval = killpg(pid, SIGINT);
|
||||
ERROR_IF_RET(retval < 0, "kill()", waitOver = true);
|
||||
while (!waitOver) {
|
||||
retval = 0;
|
||||
waitms += timeoutms;
|
||||
if ((waitms % 2000) == 0) {
|
||||
error("Script '%s' won't terminate - killing it!", *scriptFile);
|
||||
kill(pid, SIGKILL);
|
||||
killpg(pid, SIGKILL);
|
||||
}
|
||||
// Clear wait status to make sure child exit status is accessible
|
||||
// and wait for child termination
|
||||
#ifdef __FreeBSD__
|
||||
int waitStatus = 0;
|
||||
retval = waitpid(pid, &waitStatus, WNOHANG);
|
||||
#else // __FreeBSD__
|
||||
siginfo_t waitStatus;
|
||||
memset(&waitStatus, '\0', sizeof(waitStatus));
|
||||
// Wait for child termination
|
||||
retval = waitid(P_PID, pid, &waitStatus, (WNOHANG | WEXITED));
|
||||
#endif // __FreeBSD__
|
||||
ERROR_IF_RET(retval < 0, "waitid()", waitOver = true);
|
||||
// These are the acceptable conditions under which child exit is
|
||||
// regarded as successful
|
||||
#ifdef __FreeBSD__
|
||||
if (retval > 0 && (WIFEXITED(waitStatus) || WIFSIGNALED(waitStatus))) {
|
||||
#else // __FreeBSD__
|
||||
if (!retval && waitStatus.si_pid && (waitStatus.si_pid == pid) &&
|
||||
((waitStatus.si_code == CLD_EXITED) || (waitStatus.si_code == CLD_KILLED))) {
|
||||
#endif // __FreeBSD__
|
||||
debug("Child (%d) exited as expected\n", pid);
|
||||
waitOver = true;
|
||||
}
|
||||
@@ -113,7 +129,7 @@ bool cIptvProtocolExt::Open(void)
|
||||
if (!strlen(*scriptFile))
|
||||
return false;
|
||||
// Create the listening socket
|
||||
OpenSocket(socketPort);
|
||||
OpenSocket(streamPort);
|
||||
// Execute the external script
|
||||
ExecuteScript();
|
||||
isActive = true;
|
||||
@@ -149,7 +165,7 @@ bool cIptvProtocolExt::Set(const char* Location, const int Parameter, const int
|
||||
}
|
||||
scriptParameter = Parameter;
|
||||
// Update listen port
|
||||
socketPort = IptvConfig.GetExtProtocolBasePort() + Index;
|
||||
streamPort = IptvConfig.GetExtProtocolBasePort() + Index;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ private:
|
||||
int pid;
|
||||
cString scriptFile;
|
||||
int scriptParameter;
|
||||
int streamPort;
|
||||
|
||||
private:
|
||||
void TerminateScript(void);
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
|
||||
cIptvProtocolHttp::cIptvProtocolHttp()
|
||||
: streamAddr(strdup("")),
|
||||
streamPath(strdup("/"))
|
||||
streamPath(strdup("/")),
|
||||
streamPort(0)
|
||||
{
|
||||
debug("cIptvProtocolHttp::cIptvProtocolHttp()\n");
|
||||
}
|
||||
@@ -41,7 +42,7 @@ bool cIptvProtocolHttp::Connect(void)
|
||||
// Check that stream address is valid
|
||||
if (!isActive && !isempty(streamAddr) && !isempty(streamPath)) {
|
||||
// Ensure that socket is valid and connect
|
||||
OpenSocket(socketPort, streamAddr);
|
||||
OpenSocket(streamPort, streamAddr);
|
||||
if (!ConnectSocket()) {
|
||||
CloseSocket();
|
||||
return false;
|
||||
@@ -127,15 +128,19 @@ bool cIptvProtocolHttp::ProcessHeaders(void)
|
||||
{
|
||||
debug("cIptvProtocolHttp::ProcessHeaders()\n");
|
||||
unsigned int lineLength = 0;
|
||||
int response = 0;
|
||||
int version = 0, response = 0;
|
||||
bool responseFound = false;
|
||||
char fmt[32];
|
||||
char buf[4096];
|
||||
|
||||
// Generate HTTP response format string with 2 arguments
|
||||
snprintf(fmt, sizeof(fmt), "HTTP/1.%%%zui %%%zui ", sizeof(version) - 1, sizeof(response) - 1);
|
||||
|
||||
while (!responseFound || lineLength != 0) {
|
||||
memset(buf, '\0', sizeof(buf));
|
||||
if (!GetHeaderLine(buf, sizeof(buf), lineLength))
|
||||
return false;
|
||||
if (!responseFound && sscanf(buf, "HTTP/1.%*i %i ", &response) != 1) {
|
||||
if (!responseFound && sscanf(buf, fmt, &version, &response) != 2) {
|
||||
error("Expected HTTP header not found\n");
|
||||
continue;
|
||||
}
|
||||
@@ -185,8 +190,8 @@ bool cIptvProtocolHttp::Set(const char* Location, const int Parameter, const int
|
||||
}
|
||||
else
|
||||
streamPath = strcpyrealloc(streamPath, "/");
|
||||
socketPort = Parameter;
|
||||
//debug("http://%s:%d%s\n", streamAddr, socketPort, streamPath);
|
||||
streamPort = Parameter;
|
||||
//debug("http://%s:%d%s\n", streamAddr, streamPort, streamPath);
|
||||
// Re-connect the socket
|
||||
Connect();
|
||||
}
|
||||
@@ -196,5 +201,5 @@ bool cIptvProtocolHttp::Set(const char* Location, const int Parameter, const int
|
||||
cString cIptvProtocolHttp::GetInformation(void)
|
||||
{
|
||||
//debug("cIptvProtocolHttp::GetInformation()");
|
||||
return cString::sprintf("http://%s:%d%s", streamAddr, socketPort, streamPath);
|
||||
return cString::sprintf("http://%s:%d%s", streamAddr, streamPort, streamPath);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ class cIptvProtocolHttp : public cIptvTcpSocket, public cIptvProtocolIf {
|
||||
private:
|
||||
char* streamAddr;
|
||||
char* streamPath;
|
||||
int streamPort;
|
||||
|
||||
private:
|
||||
bool Connect(void);
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
#include "socket.h"
|
||||
|
||||
cIptvProtocolUdp::cIptvProtocolUdp()
|
||||
: streamAddr(strdup("")),
|
||||
sourceAddr(strdup(""))
|
||||
: isIGMPv3(false),
|
||||
sourceAddr(strdup("")),
|
||||
streamAddr(strdup("")),
|
||||
streamPort(0)
|
||||
{
|
||||
debug("cIptvProtocolUdp::cIptvProtocolUdp()\n");
|
||||
}
|
||||
@@ -37,28 +39,29 @@ cIptvProtocolUdp::~cIptvProtocolUdp()
|
||||
|
||||
bool cIptvProtocolUdp::Open(void)
|
||||
{
|
||||
debug("cIptvProtocolUdp::Open()\n");
|
||||
OpenSocket(socketPort, isempty(sourceAddr) ? INADDR_ANY : inet_addr(sourceAddr));
|
||||
debug("cIptvProtocolUdp::Open(): streamAddr=%s\n", streamAddr);
|
||||
OpenSocket(streamPort, streamAddr, sourceAddr, isIGMPv3);
|
||||
if (!isempty(streamAddr)) {
|
||||
// Join a new multicast group
|
||||
JoinMulticast(inet_addr(streamAddr));
|
||||
JoinMulticast();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cIptvProtocolUdp::Close(void)
|
||||
{
|
||||
debug("cIptvProtocolUdp::Close()\n");
|
||||
debug("cIptvProtocolUdp::Close(): streamAddr=%s\n", streamAddr);
|
||||
if (!isempty(streamAddr)) {
|
||||
// Drop the multicast group
|
||||
OpenSocket(socketPort, isempty(sourceAddr) ? INADDR_ANY : inet_addr(sourceAddr));
|
||||
DropMulticast(inet_addr(streamAddr));
|
||||
OpenSocket(streamPort, streamAddr, sourceAddr, isIGMPv3);
|
||||
DropMulticast();
|
||||
}
|
||||
// Close the socket
|
||||
CloseSocket();
|
||||
// Reset stream and source addresses
|
||||
streamAddr = strcpyrealloc(streamAddr, "");
|
||||
sourceAddr = strcpyrealloc(sourceAddr, "");
|
||||
// Do NOT reset stream and source addresses
|
||||
//sourceAddr = strcpyrealloc(sourceAddr, "");
|
||||
//streamAddr = strcpyrealloc(streamAddr, "");
|
||||
//streamPort = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -73,23 +76,28 @@ bool cIptvProtocolUdp::Set(const char* Location, const int Parameter, const int
|
||||
if (!isempty(Location)) {
|
||||
// Drop the multicast group
|
||||
if (!isempty(streamAddr)) {
|
||||
OpenSocket(socketPort, isempty(sourceAddr) ? INADDR_ANY : inet_addr(sourceAddr));
|
||||
DropMulticast(inet_addr(streamAddr));
|
||||
OpenSocket(streamPort, streamAddr, sourceAddr, isIGMPv3);
|
||||
DropMulticast();
|
||||
}
|
||||
// Update stream address and port
|
||||
streamAddr = strcpyrealloc(streamAddr, Location);
|
||||
char *p = strstr(streamAddr, ";");
|
||||
// <group address> or <source address>@<group address>
|
||||
char *p = strstr(streamAddr, "@");
|
||||
if (p) {
|
||||
sourceAddr = strcpyrealloc(sourceAddr, p + 1);
|
||||
*p = 0;
|
||||
sourceAddr = strcpyrealloc(sourceAddr, streamAddr);
|
||||
streamAddr = strcpyrealloc(streamAddr, p + 1);
|
||||
isIGMPv3 = true;
|
||||
}
|
||||
else
|
||||
sourceAddr = strcpyrealloc(sourceAddr, "");
|
||||
socketPort = Parameter;
|
||||
else {
|
||||
sourceAddr = strcpyrealloc(sourceAddr, streamAddr);
|
||||
isIGMPv3 = false;
|
||||
}
|
||||
streamPort = Parameter;
|
||||
// Join a new multicast group
|
||||
if (!isempty(streamAddr)) {
|
||||
OpenSocket(socketPort, isempty(sourceAddr) ? INADDR_ANY : inet_addr(sourceAddr));
|
||||
JoinMulticast(inet_addr(streamAddr));
|
||||
OpenSocket(streamPort, streamAddr, sourceAddr, isIGMPv3);
|
||||
JoinMulticast();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -98,5 +106,7 @@ bool cIptvProtocolUdp::Set(const char* Location, const int Parameter, const int
|
||||
cString cIptvProtocolUdp::GetInformation(void)
|
||||
{
|
||||
//debug("cIptvProtocolUdp::GetInformation()");
|
||||
return cString::sprintf("udp://%s:%d", streamAddr, socketPort);
|
||||
if (isIGMPv3)
|
||||
return cString::sprintf("udp://%s@%s:%d", sourceAddr, streamAddr, streamPort);
|
||||
return cString::sprintf("udp://%s:%d", streamAddr, streamPort);
|
||||
}
|
||||
|
||||
@@ -14,8 +14,10 @@
|
||||
|
||||
class cIptvProtocolUdp : public cIptvUdpSocket, public cIptvProtocolIf {
|
||||
private:
|
||||
char* streamAddr;
|
||||
bool isIGMPv3;
|
||||
char* sourceAddr;
|
||||
char* streamAddr;
|
||||
int streamPort;
|
||||
|
||||
public:
|
||||
cIptvProtocolUdp();
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "sectionfilter.h"
|
||||
|
||||
cIptvSectionFilter::cIptvSectionFilter(int DeviceIndex, int Index,
|
||||
uint16_t Pid, uint8_t Tid, uint8_t Mask)
|
||||
cIptvSectionFilter::cIptvSectionFilter(int DeviceIndex, uint16_t Pid, uint8_t Tid, uint8_t Mask)
|
||||
: pusi_seen(0),
|
||||
feedcc(0),
|
||||
doneq(0),
|
||||
@@ -17,10 +17,9 @@ cIptvSectionFilter::cIptvSectionFilter(int DeviceIndex, int Index,
|
||||
seclen(0),
|
||||
tsfeedp(0),
|
||||
pid(Pid),
|
||||
devid(DeviceIndex),
|
||||
id(Index)
|
||||
devid(DeviceIndex)
|
||||
{
|
||||
//debug("cIptvSectionFilter::cIptvSectionFilter(%d, %d)\n", devid, id);
|
||||
//debug("cIptvSectionFilter::cIptvSectionFilter(%d, %d)\n", devid, pid);
|
||||
int i;
|
||||
|
||||
memset(secbuf_base, '\0', sizeof(secbuf_base));
|
||||
@@ -47,35 +46,30 @@ cIptvSectionFilter::cIptvSectionFilter(int DeviceIndex, int Index,
|
||||
}
|
||||
doneq = local_doneq ? 1 : 0;
|
||||
|
||||
// Create sockets
|
||||
socket[0] = socket[1] = -1;
|
||||
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, socket) != 0) {
|
||||
char tmp[64];
|
||||
error("Opening section filter sockets failed (device=%d id=%d): %s\n", devid, id, strerror_r(errno, tmp, sizeof(tmp)));
|
||||
}
|
||||
else if ((fcntl(socket[0], F_SETFL, O_NONBLOCK) != 0) || (fcntl(socket[1], F_SETFL, O_NONBLOCK) != 0)) {
|
||||
char tmp[64];
|
||||
error("Setting section filter socket to non-blocking mode failed (device=%d id=%d): %s", devid, id, strerror_r(errno, tmp, sizeof(tmp)));
|
||||
}
|
||||
// Create filtering buffer
|
||||
ringbuffer = new cRingBufferLinear(KILOBYTE(128), 0, false, *cString::sprintf("IPTV SECTION %d/%d", devid, pid));
|
||||
if (ringbuffer)
|
||||
ringbuffer->SetTimeouts(10, 10);
|
||||
else
|
||||
error("Failed to allocate buffer for section filter (device=%d pid=%d): ", devid, pid);
|
||||
}
|
||||
|
||||
cIptvSectionFilter::~cIptvSectionFilter()
|
||||
{
|
||||
//debug("cIptvSectionFilter::~cIptvSectionfilter(%d, %d)\n", devid, id);
|
||||
int tmp = socket[1];
|
||||
socket[1] = -1;
|
||||
if (tmp >= 0)
|
||||
close(tmp);
|
||||
tmp = socket[0];
|
||||
socket[0] = -1;
|
||||
if (tmp >= 0)
|
||||
close(tmp);
|
||||
//debug("cIptvSectionFilter::~cIptvSectionfilter(%d, %d)\n", devid, pid);
|
||||
DELETE_POINTER(ringbuffer);
|
||||
secbuf = NULL;
|
||||
}
|
||||
|
||||
int cIptvSectionFilter::GetReadDesc(void)
|
||||
int cIptvSectionFilter::Read(void *Data, size_t Length)
|
||||
{
|
||||
return socket[0];
|
||||
int count = 0;
|
||||
uchar *p = ringbuffer->Get(count);
|
||||
if (p && count > 0) {
|
||||
memcpy(Data, p, count);
|
||||
ringbuffer->Del(count);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
inline uint16_t cIptvSectionFilter::GetLength(const uint8_t *Data)
|
||||
@@ -105,10 +99,10 @@ int cIptvSectionFilter::Filter(void)
|
||||
if (doneq && !neq)
|
||||
return 0;
|
||||
|
||||
// There is no data in the read socket, more can be written
|
||||
if ((socket[0] >= 0) && (socket[1] >= 0) /*&& !select_single_desc(socket[0], 0, false)*/) {
|
||||
ssize_t len = write(socket[1], secbuf, seclen);
|
||||
ERROR_IF(len < 0, "write()");
|
||||
if (ringbuffer) {
|
||||
int len = ringbuffer->Put(secbuf, seclen);
|
||||
if (len != seclen)
|
||||
ringbuffer->ReportOverflow(seclen - len);
|
||||
// Update statistics
|
||||
AddSectionStatistic(len, 1);
|
||||
}
|
||||
@@ -138,7 +132,7 @@ int cIptvSectionFilter::CopyDump(const uint8_t *buf, uint8_t len)
|
||||
return 0;
|
||||
|
||||
memcpy(secbuf_base + tsfeedp, buf, len);
|
||||
tsfeedp += len;
|
||||
tsfeedp = uint16_t(tsfeedp + len);
|
||||
|
||||
limit = tsfeedp;
|
||||
if (limit > DMX_MAX_SECFEED_SIZE)
|
||||
@@ -154,7 +148,7 @@ int cIptvSectionFilter::CopyDump(const uint8_t *buf, uint8_t len)
|
||||
seclen = seclen_local;
|
||||
if (pusi_seen)
|
||||
Feed();
|
||||
secbufp += seclen_local;
|
||||
secbufp = uint16_t(secbufp + seclen_local);
|
||||
secbuf += seclen_local;
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
#ifndef __IPTV_SECTIONFILTER_H
|
||||
#define __IPTV_SECTIONFILTER_H
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
#include <sys/socket.h>
|
||||
#endif // __FreeBSD__
|
||||
#include <vdr/device.h>
|
||||
|
||||
#include "common.h"
|
||||
@@ -33,8 +36,6 @@ private:
|
||||
uint16_t pid;
|
||||
|
||||
int devid;
|
||||
int id;
|
||||
int socket[2];
|
||||
|
||||
uint8_t filter_value[DMX_MAX_FILTER_SIZE];
|
||||
uint8_t filter_mask[DMX_MAX_FILTER_SIZE];
|
||||
@@ -43,6 +44,8 @@ private:
|
||||
uint8_t maskandmode[DMX_MAX_FILTER_SIZE];
|
||||
uint8_t maskandnotmode[DMX_MAX_FILTER_SIZE];
|
||||
|
||||
cRingBufferLinear *ringbuffer;
|
||||
|
||||
inline uint16_t GetLength(const uint8_t *Data);
|
||||
void New(void);
|
||||
int Filter(void);
|
||||
@@ -51,11 +54,10 @@ private:
|
||||
|
||||
public:
|
||||
// constructor & destructor
|
||||
cIptvSectionFilter(int Index, int DeviceIndex, uint16_t Pid,
|
||||
uint8_t Tid, uint8_t Mask);
|
||||
cIptvSectionFilter(int DeviceIndex, uint16_t Pid, uint8_t Tid, uint8_t Mask);
|
||||
virtual ~cIptvSectionFilter();
|
||||
void Process(const uint8_t* Data);
|
||||
int GetReadDesc(void);
|
||||
int Read(void *Buffer, size_t Length);
|
||||
uint16_t GetPid(void) const { return pid; }
|
||||
};
|
||||
|
||||
|
||||
265
socket.c
265
socket.c
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <net/if.h>
|
||||
#include <netdb.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
@@ -18,8 +19,8 @@
|
||||
#include "socket.h"
|
||||
|
||||
cIptvSocket::cIptvSocket()
|
||||
: socketDesc(-1),
|
||||
socketPort(0),
|
||||
: socketPort(0),
|
||||
socketDesc(-1),
|
||||
isActive(false)
|
||||
{
|
||||
debug("cIptvSocket::cIptvSocket()\n");
|
||||
@@ -57,11 +58,13 @@ bool cIptvSocket::OpenSocket(const int Port, const bool isUdp)
|
||||
// Allow multiple sockets to use the same PORT number
|
||||
ERROR_IF_FUNC(setsockopt(socketDesc, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0, "setsockopt(SO_REUSEADDR)",
|
||||
CloseSocket(), return false);
|
||||
#ifndef __FreeBSD__
|
||||
// Allow packet information to be fetched
|
||||
ERROR_IF_FUNC(setsockopt(socketDesc, SOL_IP, IP_PKTINFO, &yes, sizeof(yes)) < 0, "setsockopt(IP_PKTINFO)",
|
||||
CloseSocket(), return false);
|
||||
#endif // __FreeBSD__
|
||||
// Bind socket
|
||||
memset(&sockAddr, '\0', sizeof(sockAddr));
|
||||
memset(&sockAddr, 0, sizeof(sockAddr));
|
||||
sockAddr.sin_family = AF_INET;
|
||||
sockAddr.sin_port = htons((uint16_t)(Port & 0xFFFF));
|
||||
sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
@@ -87,9 +90,33 @@ void cIptvSocket::CloseSocket(void)
|
||||
}
|
||||
}
|
||||
|
||||
bool cIptvSocket::CheckAddress(const char *Addr, in_addr_t *InAddr)
|
||||
{
|
||||
if (InAddr) {
|
||||
// First try only the IP address
|
||||
*InAddr = htonl(inet_addr(Addr));
|
||||
if (*InAddr == htonl(INADDR_NONE)) {
|
||||
debug("Cannot convert %s directly to internet address\n", Addr);
|
||||
// It may be a host name, get the name
|
||||
struct hostent *host;
|
||||
host = gethostbyname(Addr);
|
||||
if (!host) {
|
||||
char tmp[64];
|
||||
error("gethostbyname() failed: %s is not valid address: %s", Addr, strerror_r(h_errno, tmp, sizeof(tmp)));
|
||||
return false;
|
||||
}
|
||||
*InAddr = htonl(inet_addr(*host->h_addr_list));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// UDP socket class
|
||||
cIptvUdpSocket::cIptvUdpSocket()
|
||||
: sourceAddr(INADDR_ANY)
|
||||
: streamAddr(htonl(INADDR_ANY)),
|
||||
sourceAddr(htonl(INADDR_ANY)),
|
||||
useIGMPv3(false)
|
||||
{
|
||||
debug("cIptvUdpSocket::cIptvUdpSocket()\n");
|
||||
}
|
||||
@@ -99,46 +126,95 @@ cIptvUdpSocket::~cIptvUdpSocket()
|
||||
debug("cIptvUdpSocket::~cIptvUdpSocket()\n");
|
||||
}
|
||||
|
||||
bool cIptvUdpSocket::OpenSocket(const int Port, const in_addr_t SourceAddr)
|
||||
bool cIptvUdpSocket::OpenSocket(const int Port)
|
||||
{
|
||||
debug("cIptvUdpSocket::OpenSocket()\n");
|
||||
sourceAddr = SourceAddr;
|
||||
streamAddr = htonl(INADDR_ANY);
|
||||
sourceAddr = htonl(INADDR_ANY);
|
||||
useIGMPv3 = false;
|
||||
return cIptvSocket::OpenSocket(Port, true);
|
||||
}
|
||||
|
||||
bool cIptvUdpSocket::OpenSocket(const int Port, const char *StreamAddr, const char *SourceAddr, bool UseIGMPv3)
|
||||
{
|
||||
debug("cIptvUdpSocket::OpenSocket()\n");
|
||||
CheckAddress(StreamAddr, &streamAddr);
|
||||
CheckAddress(SourceAddr, &sourceAddr);
|
||||
useIGMPv3 = UseIGMPv3;
|
||||
return cIptvSocket::OpenSocket(Port, true);
|
||||
}
|
||||
|
||||
void cIptvUdpSocket::CloseSocket(void)
|
||||
{
|
||||
debug("cIptvUdpSocket::CloseSocket()\n");
|
||||
sourceAddr = INADDR_ANY;
|
||||
streamAddr = htonl(INADDR_ANY);
|
||||
sourceAddr = htonl(INADDR_ANY);
|
||||
useIGMPv3 = false;
|
||||
cIptvSocket::CloseSocket();
|
||||
}
|
||||
|
||||
bool cIptvUdpSocket::JoinMulticast(const in_addr_t StreamAddr)
|
||||
bool cIptvUdpSocket::JoinMulticast(void)
|
||||
{
|
||||
debug("cIptvUdpSocket::JoinMulticast()\n");
|
||||
// Check if socket exists
|
||||
if (!isActive && (socketDesc >= 0)) {
|
||||
// Join a new multicast group
|
||||
struct ip_mreq mreq;
|
||||
mreq.imr_multiaddr.s_addr = StreamAddr;
|
||||
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
|
||||
ERROR_IF_RET(setsockopt(socketDesc, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0, "setsockopt(IP_ADD_MEMBERSHIP)", return false);
|
||||
if (useIGMPv3) {
|
||||
// Source-specific multicast (SSM) is used
|
||||
struct group_source_req gsr;
|
||||
struct sockaddr_in *grp;
|
||||
struct sockaddr_in *src;
|
||||
gsr.gsr_interface = 0; // if_nametoindex("any") ?
|
||||
grp = (struct sockaddr_in*)&gsr.gsr_group;
|
||||
grp->sin_family = AF_INET;
|
||||
grp->sin_addr.s_addr = streamAddr;
|
||||
grp->sin_port = 0;
|
||||
src = (struct sockaddr_in*)&gsr.gsr_source;
|
||||
src->sin_family = AF_INET;
|
||||
src->sin_addr.s_addr = sourceAddr;
|
||||
src->sin_port = 0;
|
||||
ERROR_IF_RET(setsockopt(socketDesc, SOL_IP, MCAST_JOIN_SOURCE_GROUP, &gsr, sizeof(gsr)) < 0, "setsockopt(MCAST_JOIN_SOURCE_GROUP)", return false);
|
||||
}
|
||||
else {
|
||||
struct ip_mreq mreq;
|
||||
mreq.imr_multiaddr.s_addr = streamAddr;
|
||||
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
|
||||
ERROR_IF_RET(setsockopt(socketDesc, SOL_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0, "setsockopt(IP_ADD_MEMBERSHIP)", return false);
|
||||
}
|
||||
// Update multicasting flag
|
||||
isActive = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cIptvUdpSocket::DropMulticast(const in_addr_t StreamAddr)
|
||||
bool cIptvUdpSocket::DropMulticast(void)
|
||||
{
|
||||
debug("cIptvUdpSocket::DropMulticast()\n");
|
||||
// Check if socket exists
|
||||
if (isActive && (socketDesc >= 0)) {
|
||||
// Drop the existing multicast group
|
||||
struct ip_mreq mreq;
|
||||
mreq.imr_multiaddr.s_addr = StreamAddr;
|
||||
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
|
||||
ERROR_IF_RET(setsockopt(socketDesc, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0, "setsockopt(IP_DROP_MEMBERSHIP)", return false);
|
||||
if (useIGMPv3) {
|
||||
// Source-specific multicast (SSM) is used
|
||||
struct group_source_req gsr;
|
||||
struct sockaddr_in *grp;
|
||||
struct sockaddr_in *src;
|
||||
gsr.gsr_interface = 0; // if_nametoindex("any") ?
|
||||
grp = (struct sockaddr_in*)&gsr.gsr_group;
|
||||
grp->sin_family = AF_INET;
|
||||
grp->sin_addr.s_addr = streamAddr;
|
||||
grp->sin_port = 0;
|
||||
src = (struct sockaddr_in*)&gsr.gsr_source;
|
||||
src->sin_family = AF_INET;
|
||||
src->sin_addr.s_addr = sourceAddr;
|
||||
src->sin_port = 0;
|
||||
ERROR_IF_RET(setsockopt(socketDesc, SOL_IP, MCAST_LEAVE_SOURCE_GROUP, &gsr, sizeof(gsr)) < 0, "setsockopt(MCAST_LEAVE_SOURCE_GROUP)", return false);
|
||||
}
|
||||
else {
|
||||
struct ip_mreq mreq;
|
||||
mreq.imr_multiaddr.s_addr = streamAddr;
|
||||
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
|
||||
ERROR_IF_RET(setsockopt(socketDesc, SOL_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0, "setsockopt(IP_DROP_MEMBERSHIP)", return false);
|
||||
}
|
||||
// Update multicasting flag
|
||||
isActive = false;
|
||||
}
|
||||
@@ -154,72 +230,77 @@ int cIptvUdpSocket::Read(unsigned char* BufferAddr, unsigned int BufferLen)
|
||||
error("Invalid socket in %s\n", __FUNCTION__);
|
||||
return -1;
|
||||
}
|
||||
socklen_t addrlen = sizeof(sockAddr);
|
||||
int len = 0;
|
||||
struct msghdr msgh;
|
||||
struct cmsghdr *cmsg;
|
||||
struct iovec iov;
|
||||
char cbuf[256];
|
||||
// Initialize iov and msgh structures
|
||||
memset(&msgh, 0, sizeof(struct msghdr));
|
||||
iov.iov_base = BufferAddr;
|
||||
iov.iov_len = BufferLen;
|
||||
msgh.msg_control = cbuf;
|
||||
msgh.msg_controllen = sizeof(cbuf);
|
||||
msgh.msg_name = &sockAddr;
|
||||
msgh.msg_namelen = addrlen;
|
||||
msgh.msg_iov = &iov;
|
||||
msgh.msg_iovlen = 1;
|
||||
msgh.msg_flags = 0;
|
||||
// Read data from socket
|
||||
if (isActive && socketDesc && BufferAddr && (BufferLen > 0))
|
||||
len = (int)recvmsg(socketDesc, &msgh, MSG_DONTWAIT);
|
||||
if (len < 0) {
|
||||
ERROR_IF(errno != EAGAIN, "recvmsg()");
|
||||
return -1;
|
||||
}
|
||||
else if (len > 0) {
|
||||
// Process auxiliary received data and validate source address
|
||||
for (cmsg = CMSG_FIRSTHDR(&msgh); (sourceAddr != INADDR_ANY) && (cmsg != NULL); cmsg = CMSG_NXTHDR(&msgh, cmsg)) {
|
||||
if ((cmsg->cmsg_level == SOL_IP) && (cmsg->cmsg_type == IP_PKTINFO)) {
|
||||
struct in_pktinfo *i = (struct in_pktinfo *)CMSG_DATA(cmsg);
|
||||
if (i->ipi_addr.s_addr != sourceAddr) {
|
||||
//debug("Discard packet due to invalid source address: %s", inet_ntoa(i->ipi_addr));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (BufferAddr[0] == TS_SYNC_BYTE)
|
||||
return len;
|
||||
else if (len > 3) {
|
||||
// http://www.networksorcery.com/enp/rfc/rfc2250.txt
|
||||
// version
|
||||
unsigned int v = (BufferAddr[0] >> 6) & 0x03;
|
||||
// extension bit
|
||||
unsigned int x = (BufferAddr[0] >> 4) & 0x01;
|
||||
// cscr count
|
||||
unsigned int cc = BufferAddr[0] & 0x0F;
|
||||
// payload type: MPEG2 TS = 33
|
||||
//unsigned int pt = readBuffer[1] & 0x7F;
|
||||
// header lenght
|
||||
unsigned int headerlen = (3 + cc) * (unsigned int)sizeof(uint32_t);
|
||||
// check if extension
|
||||
if (x) {
|
||||
// extension header length
|
||||
unsigned int ehl = (((BufferAddr[headerlen + 2] & 0xFF) << 8) |
|
||||
(BufferAddr[headerlen + 3] & 0xFF));
|
||||
// update header length
|
||||
headerlen += (ehl + 1) * (unsigned int)sizeof(uint32_t);
|
||||
// Read data from socket in a loop
|
||||
do {
|
||||
socklen_t addrlen = sizeof(sockAddr);
|
||||
struct msghdr msgh;
|
||||
struct iovec iov;
|
||||
char cbuf[256];
|
||||
len = 0;
|
||||
// Initialize iov and msgh structures
|
||||
memset(&msgh, 0, sizeof(struct msghdr));
|
||||
iov.iov_base = BufferAddr;
|
||||
iov.iov_len = BufferLen;
|
||||
msgh.msg_control = cbuf;
|
||||
msgh.msg_controllen = sizeof(cbuf);
|
||||
msgh.msg_name = &sockAddr;
|
||||
msgh.msg_namelen = addrlen;
|
||||
msgh.msg_iov = &iov;
|
||||
msgh.msg_iovlen = 1;
|
||||
msgh.msg_flags = 0;
|
||||
|
||||
if (isActive && socketDesc && BufferAddr && (BufferLen > 0))
|
||||
len = (int)recvmsg(socketDesc, &msgh, MSG_DONTWAIT);
|
||||
else
|
||||
break;
|
||||
if (len > 0) {
|
||||
#ifndef __FreeBSD__
|
||||
// Process auxiliary received data and validate source address
|
||||
for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; cmsg = CMSG_NXTHDR(&msgh, cmsg)) {
|
||||
if ((cmsg->cmsg_level == SOL_IP) && (cmsg->cmsg_type == IP_PKTINFO)) {
|
||||
struct in_pktinfo *i = (struct in_pktinfo *)CMSG_DATA(cmsg);
|
||||
if ((i->ipi_addr.s_addr == streamAddr) || (htonl(INADDR_ANY) == streamAddr)) {
|
||||
#endif // __FreeBSD__
|
||||
if (BufferAddr[0] == TS_SYNC_BYTE)
|
||||
return len;
|
||||
else if (len > 3) {
|
||||
// http://www.networksorcery.com/enp/rfc/rfc2250.txt
|
||||
// version
|
||||
unsigned int v = (BufferAddr[0] >> 6) & 0x03;
|
||||
// extension bit
|
||||
unsigned int x = (BufferAddr[0] >> 4) & 0x01;
|
||||
// cscr count
|
||||
unsigned int cc = BufferAddr[0] & 0x0F;
|
||||
// payload type: MPEG2 TS = 33
|
||||
//unsigned int pt = readBuffer[1] & 0x7F;
|
||||
// header lenght
|
||||
unsigned int headerlen = (3 + cc) * (unsigned int)sizeof(uint32_t);
|
||||
// check if extension
|
||||
if (x) {
|
||||
// extension header length
|
||||
unsigned int ehl = (((BufferAddr[headerlen + 2] & 0xFF) << 8) |
|
||||
(BufferAddr[headerlen + 3] & 0xFF));
|
||||
// update header length
|
||||
headerlen += (ehl + 1) * (unsigned int)sizeof(uint32_t);
|
||||
}
|
||||
// Check that rtp is version 2 and payload contains multiple of TS packet data
|
||||
if ((v == 2) && (((len - headerlen) % TS_SIZE) == 0) &&
|
||||
(BufferAddr[headerlen] == TS_SYNC_BYTE)) {
|
||||
// Set argument point to payload in read buffer
|
||||
memmove(BufferAddr, &BufferAddr[headerlen], (len - headerlen));
|
||||
return (len - headerlen);
|
||||
}
|
||||
}
|
||||
#ifndef __FreeBSD__
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check that rtp is version 2 and payload contains multiple of TS packet data
|
||||
if ((v == 2) && (((len - headerlen) % TS_SIZE) == 0) &&
|
||||
(BufferAddr[headerlen] == TS_SYNC_BYTE)) {
|
||||
// Set argument point to payload in read buffer
|
||||
memmove(BufferAddr, &BufferAddr[headerlen], (len - headerlen));
|
||||
return (len - headerlen);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // __FreeBSD__
|
||||
}
|
||||
} while (len > 0);
|
||||
ERROR_IF_RET(len < 0 && errno != EAGAIN, "recvmsg()", return -1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -237,26 +318,8 @@ cIptvTcpSocket::~cIptvTcpSocket()
|
||||
bool cIptvTcpSocket::OpenSocket(const int Port, const char *StreamAddr)
|
||||
{
|
||||
debug("cIptvTcpSocket::OpenSocket()\n");
|
||||
|
||||
// First try only the IP address
|
||||
sockAddr.sin_addr.s_addr = inet_addr(StreamAddr);
|
||||
|
||||
if (sockAddr.sin_addr.s_addr == INADDR_NONE) {
|
||||
debug("Cannot convert %s directly to internet address\n", StreamAddr);
|
||||
|
||||
// It may be a host name, get the name
|
||||
struct hostent *host;
|
||||
host = gethostbyname(StreamAddr);
|
||||
if (!host) {
|
||||
char tmp[64];
|
||||
error("gethostbyname() failed: %s is not valid address: %s", StreamAddr, strerror_r(h_errno, tmp, sizeof(tmp)));
|
||||
return false;
|
||||
}
|
||||
|
||||
sockAddr.sin_addr.s_addr = inet_addr(*host->h_addr_list);
|
||||
}
|
||||
|
||||
return cIptvSocket::OpenSocket(Port, false);
|
||||
// Socket must be opened before setting the host address
|
||||
return (cIptvSocket::OpenSocket(Port, false) && CheckAddress(StreamAddr, &sockAddr.sin_addr.s_addr));
|
||||
}
|
||||
|
||||
void cIptvTcpSocket::CloseSocket(void)
|
||||
|
||||
17
socket.h
17
socket.h
@@ -9,17 +9,23 @@
|
||||
#define __IPTV_SOCKET_H
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#ifdef __FreeBSD__
|
||||
#include <netinet/in.h>
|
||||
#endif // __FreeBSD__
|
||||
|
||||
class cIptvSocket {
|
||||
private:
|
||||
int socketPort;
|
||||
|
||||
protected:
|
||||
int socketDesc;
|
||||
int socketPort;
|
||||
struct sockaddr_in sockAddr;
|
||||
bool isActive;
|
||||
|
||||
protected:
|
||||
bool OpenSocket(const int Port, const bool isUdp);
|
||||
void CloseSocket(void);
|
||||
bool CheckAddress(const char *Addr, in_addr_t *InAddr);
|
||||
|
||||
public:
|
||||
cIptvSocket();
|
||||
@@ -28,16 +34,19 @@ public:
|
||||
|
||||
class cIptvUdpSocket : public cIptvSocket {
|
||||
private:
|
||||
in_addr_t streamAddr;
|
||||
in_addr_t sourceAddr;
|
||||
bool useIGMPv3;
|
||||
|
||||
public:
|
||||
cIptvUdpSocket();
|
||||
virtual ~cIptvUdpSocket();
|
||||
virtual int Read(unsigned char* BufferAddr, unsigned int BufferLen);
|
||||
bool OpenSocket(const int Port, const in_addr_t SourceAddr = INADDR_ANY);
|
||||
bool OpenSocket(const int Port);
|
||||
bool OpenSocket(const int Port, const char *StreamAddr, const char *SourceAddr, bool UseIGMPv3);
|
||||
void CloseSocket(void);
|
||||
bool JoinMulticast(const in_addr_t StreamAddr);
|
||||
bool DropMulticast(const in_addr_t StreamAddr);
|
||||
bool JoinMulticast(void);
|
||||
bool DropMulticast(void);
|
||||
};
|
||||
|
||||
class cIptvTcpSocket : public cIptvSocket {
|
||||
|
||||
@@ -148,7 +148,7 @@ cString cIptvStreamerStatistics::GetStreamerStatistic()
|
||||
long bitrate = elapsed ? (long)(1000.0L * dataBytes / KILOBYTE(1) / elapsed) : 0L;
|
||||
if (!IptvConfig.GetUseBytes())
|
||||
bitrate *= 8;
|
||||
cString info = cString::sprintf("Stream bitrate: %ld k%s/s\n", bitrate, IptvConfig.GetUseBytes() ? "B" : "bit");
|
||||
cString info = cString::sprintf("%ld k%s/s", bitrate, IptvConfig.GetUseBytes() ? "B" : "bit");
|
||||
dataBytes = 0;
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -108,8 +108,8 @@ bool cIptvStreamer::Set(const char* Location, const int Parameter, const int Ind
|
||||
cString cIptvStreamer::GetInformation(void)
|
||||
{
|
||||
//debug("cIptvStreamer::GetInformation()");
|
||||
cString info("Stream:");
|
||||
cString info;
|
||||
if (protocol)
|
||||
info = cString::sprintf("%s %s", *info, *protocol->GetInformation());
|
||||
return cString::sprintf("%s\n", *info);
|
||||
info = protocol->GetInformation();
|
||||
return info;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user