Added Partnerbox Plugin (Remote Timer and Remote TV Player)
[enigma2-plugins.git] / partnerbox / src / servicewebts / servicewebts.cpp
1
2 /*******************************************************************************
3  VLC Player Plugin by A. Lätsch 2007
4
5  Modified by Dr. Best
6
7  This is free software; you can redistribute it and/or modify it under
8  the terms of the GNU General Public License as published by the Free
9  Software Foundation; either version 2, or (at your option) any later
10  version.
11 ********************************************************************************/
12
13 #include <sys/types.h>
14 #include <sys/stat.h>
15 #include <fcntl.h>
16 #include <string>
17 #include <sys/socket.h>
18 #include <netdb.h>
19 #include <signal.h>
20 #include <time.h>
21 #include "servicewebts.h"
22 #include <lib/base/eerror.h>
23 #include <lib/base/object.h>
24 #include <lib/base/ebase.h>
25 #include <lib/service/service.h>
26 #include <lib/base/init_num.h>
27 #include <lib/base/init.h>
28 #include <lib/dvb/decoder.h>
29
30 #include <lib/dvb/pmt.h>
31
32 #define MAX(a,b) ((a) > (b) ? (a) : (b))
33
34 int VPID = 0;
35 int PID_SET = 0;
36 int APID = 0;
37
38
39 /********************************************************************/
40 /* eServiceFactoryWebTS                                                */
41 /********************************************************************/
42
43 eServiceFactoryWebTS::eServiceFactoryWebTS()
44 {
45         ePtr<eServiceCenter> sc;
46
47         eServiceCenter::getPrivInstance(sc);
48         if (sc)
49         {
50                 std::list<std::string> extensions;
51                 sc->addServiceFactory(eServiceFactoryWebTS::id, this, extensions);
52         }
53 }
54
55 eServiceFactoryWebTS::~eServiceFactoryWebTS()
56 {
57         ePtr<eServiceCenter> sc;
58
59         eServiceCenter::getPrivInstance(sc);
60         if (sc)
61                 sc->removeServiceFactory(eServiceFactoryWebTS::id);
62 }
63
64 DEFINE_REF(eServiceFactoryWebTS)
65
66 // iServiceHandler
67 RESULT eServiceFactoryWebTS::play(const eServiceReference &ref, ePtr<iPlayableService> &ptr)
68 {
69         ptr = new eServiceWebTS(ref);
70         return 0;
71 }
72
73 RESULT eServiceFactoryWebTS::record(const eServiceReference &ref, ePtr<iRecordableService> &ptr)
74 {
75         ptr=0;
76         return -1;
77 }
78
79 RESULT eServiceFactoryWebTS::list(const eServiceReference &, ePtr<iListableService> &ptr)
80 {
81         ptr=0;
82         return -1;
83 }
84
85 RESULT eServiceFactoryWebTS::info(const eServiceReference &ref, ePtr<iStaticServiceInformation> &ptr)
86 {
87         ptr = 0;
88         return -1;
89 }
90
91 RESULT eServiceFactoryWebTS::offlineOperations(const eServiceReference &, ePtr<iServiceOfflineOperations> &ptr)
92 {
93         ptr = 0;
94         return -1;
95 }
96
97
98 /********************************************************************/
99 /* TSAudioInfoWeb                                            */
100 /********************************************************************/
101 DEFINE_REF(TSAudioInfoWeb);
102
103 void TSAudioInfoWeb::addAudio(int pid, std::string lang, std::string desc, int type) {
104         StreamInfo as;
105         as.description = desc;
106         as.language = lang;
107         as.pid = pid;
108         as.type = type;
109         audioStreams.push_back(as);
110 }
111
112
113 /********************************************************************/
114 /* eServiceWebTS                                                       */
115 /********************************************************************/
116
117 eServiceWebTS::eServiceWebTS(const eServiceReference &url): m_pump(eApp, 1)
118 {
119         eDebug("ServiceWebTS construct!");
120         m_filename = url.path.c_str();
121         m_vpid = url.getData(0) == 0 ? 0x44 : url.getData(0);
122         m_apid = url.getData(1) == 0 ? 0x45 : url.getData(1);
123         m_state = stIdle;
124         m_audioInfo = 0;
125 }
126
127 eServiceWebTS::~eServiceWebTS()
128 {
129         eDebug("ServiceWebTS destruct!");
130         if (m_state == stRunning)
131                 stop();
132 }
133
134 DEFINE_REF(eServiceWebTS);
135
136 size_t crop(char *buf)
137 {
138         size_t len = strlen(buf) - 1;
139         while (len > 0 && (buf[len] == '\r' || buf[len] == '\n')) {
140                 buf[len--] = '\0';
141         }
142         return len;
143 }
144
145 static int getline(char** pbuffer, size_t* pbufsize, int fd)
146 {
147         size_t i = 0;
148         int rc;
149         while (true) {
150                 if (i >= *pbufsize) {
151                         char *newbuf = (char*)realloc(*pbuffer, (*pbufsize)+1024);
152                         if (newbuf == NULL)
153                                 return -ENOMEM;
154                         *pbuffer = newbuf;
155                         *pbufsize = (*pbufsize)+1024;
156                 }
157                 rc = ::read(fd, (*pbuffer)+i, 1);
158                 if (rc <= 0 || (*pbuffer)[i] == '\n')
159                 {
160                         (*pbuffer)[i] = '\0';
161                         return rc <= 0 ? -1 : i;
162                 }
163                 if ((*pbuffer)[i] != '\r') i++;
164         }
165 }
166
167
168
169 int eServiceWebTS::openHttpConnection(std::string url)
170 {
171         std::string host;
172         int port = 80;
173         std::string uri;
174
175         int slash = url.find("/", 7);
176         if (slash > 0) {
177                 host = url.substr(7, slash-7);
178                 uri = url.substr(slash, url.length()-slash);
179         } else {
180                 host = url.substr(7, url.length()-7);
181                 uri = "";
182         }
183         int dp = host.find(":");
184         if (dp == 0) {
185                 port = atoi(host.substr(1, host.length()-1).c_str());
186                 host = "localhost";
187         } else if (dp > 0) {
188                 port = atoi(host.substr(dp+1, host.length()-dp-1).c_str());
189                 host = host.substr(0, dp);
190         }
191
192         struct hostent* h = gethostbyname(host.c_str());
193         if (h == NULL || h->h_addr_list == NULL)
194                 return -1;
195         int fd = socket(PF_INET, SOCK_STREAM, 0);
196         if (fd == -1)
197                 return -1;
198
199         struct sockaddr_in addr;
200         addr.sin_family = AF_INET;
201         addr.sin_addr.s_addr = *((in_addr_t*)h->h_addr_list[0]);
202         addr.sin_port = htons(port);
203
204         eDebug("connecting to %s", url.c_str());
205
206         if (connect(fd, (sockaddr*)&addr, sizeof(addr)) == -1) {
207                 std::string msg = "connect failed for: " + url;
208                 eDebug(msg.c_str());
209                 return -1;
210         }
211
212         std::string request = "GET ";
213         request.append(uri).append(" HTTP/1.1\n");
214         request.append("Host: ").append(host).append("\n");
215         request.append("Accept: */*\n");
216         request.append("Connection: close\n");
217         request.append("\n");
218         //eDebug(request.c_str());
219         write(fd, request.c_str(), request.length());
220
221         int rc;
222         size_t buflen = 1000;
223         char* linebuf = (char*)malloc(1000);
224
225         rc = getline(&linebuf, &buflen, fd);
226         //eDebug("RECV(%d): %s", rc, linebuf);
227         if (rc <= 0)
228         {
229                 close(fd);
230                 free(linebuf);
231                 return -1;
232         }
233
234         char proto[100];
235         int statuscode = 0;
236         char statusmsg[100];
237         rc = sscanf(linebuf, "%99s %d %99s", proto, &statuscode, statusmsg);
238         if (rc != 3 || statuscode != 200) {
239                 eDebug("wrong response: \"200 OK\" expected.");
240                 free(linebuf);
241                 close(fd);
242                 return -1;
243         }
244         eDebug("proto=%s, code=%d, msg=%s", proto, statuscode, statusmsg);
245         while (rc > 0)
246         {
247                 rc = getline(&linebuf, &buflen, fd);
248                 //eDebug("RECV(%d): %s", rc, linebuf);
249         }
250         free(linebuf);
251
252         return fd;
253 }
254
255 RESULT eServiceWebTS::connectEvent(const Slot2<void,iPlayableService*,int> &event, ePtr<eConnection> &connection)
256 {
257         connection = new eConnection((iPlayableService*)this, m_event.connect(event));
258         return 0;
259 }
260
261 RESULT eServiceWebTS::start()
262 {
263         ePtr<eDVBResourceManager> rmgr;
264         eDVBResourceManager::getInstance(rmgr);
265         eDVBChannel dvbChannel(rmgr, 0);
266         if (dvbChannel.getDemux(m_decodedemux, iDVBChannel::capDecode) != 0) {
267                 eDebug("Cannot allocate decode-demux");
268                 return 1;
269         }
270         if (m_decodedemux->getMPEGDecoder(m_decoder, 1) != 0) {
271                 eDebug("Cannot allocate MPEGDecoder");
272                 return 1;
273         }
274         //m_decoder->setVideoPID(m_vpid, eDVBVideo::MPEG2);
275         //m_decoder->setAudioPID(m_apid, eDVBAudio::aMPEG);
276         m_streamthread = new eStreamThreadWeb();
277         CONNECT(m_streamthread->m_event, eServiceWebTS::recv_event);
278         //m_decoder->freeze(0);
279         //m_decoder->preroll();
280         if (unpause() != 0) return -1;
281         //m_state = stRunning;
282         //m_event(this, evStart);
283         return 0;
284 }
285
286 RESULT eServiceWebTS::stop()
287 {
288         if (m_state != stRunning)
289                 return -1;
290         printf("TS: %s stop\n", m_filename.c_str());
291         m_streamthread->stop();
292         m_decodedemux->flush();
293         m_state = stStopped;
294         m_audioInfo = 0;
295         APID = 0;
296         VPID = 0;
297         PID_SET = 0;
298         return 0;
299 }
300
301 void eServiceWebTS::recv_event(int evt)
302 {
303         eDebug("eServiceWebTS::recv_event: %d", evt);
304         switch (evt) {
305         case eStreamThreadWeb::evtEOS:
306                 m_decodedemux->flush();
307                 m_state = stStopped;
308                 m_event((iPlayableService*)this, evEOF);
309                 break;
310         case eStreamThreadWeb::evtReadError:
311         case eStreamThreadWeb::evtWriteError:
312                 m_decoder->freeze(0);
313                 m_state = stStopped;
314                 m_event((iPlayableService*)this, evEOF);
315                 break;
316         case eStreamThreadWeb::evtSOS:
317                 m_event((iPlayableService*)this, evSOF);
318                 break;
319         case eStreamThreadWeb::evtStreamInfo:
320                 if (VPID != 0 && PID_SET == 0 && APID != 0)
321                 {
322                         PID_SET = 1;
323                         m_decodedemux->flush();
324                         m_decoder->setVideoPID(VPID, eDVBVideo::MPEG2);
325                         m_decoder->setAudioPID(APID, eDVBAudio::aMPEG);
326                         m_decoder->freeze(0);
327                         m_decoder->preroll();
328                         m_state = stRunning;
329                         m_event(this, evStart);
330                         m_decoder->unfreeze();
331                         
332                 }       
333                 bool wasnull = !m_audioInfo;
334                 m_streamthread->getAudioInfo(m_audioInfo);
335                 //if (m_audioInfo)
336                 //      eDebug("[ServiceWebTS] %d audiostreams found", m_audioInfo->audioStreams.size());
337                 if (m_audioInfo && wasnull) {
338                         eDebug("[ServiceWebTS] %d audiostreams found", m_audioInfo->audioStreams.size());
339                         int sel = getCurrentTrack();
340                         if (sel < 0)
341                                 selectTrack(0);
342                         else if (m_audioInfo->audioStreams[sel].type != eDVBAudio::aMPEG)
343                                 selectTrack(sel);
344                 }
345                 break;
346         }
347 }
348
349 RESULT eServiceWebTS::pause(ePtr<iPauseableService> &ptr)
350 {
351         ptr = this;
352         return 0;
353 }
354
355 // iPausableService
356 RESULT eServiceWebTS::pause()
357 {
358         m_streamthread->stop();
359         m_decoder->freeze(0);
360         return 0;
361 }
362
363 RESULT eServiceWebTS::unpause()
364 {
365         int is_streaming = !strncmp(m_filename.c_str(), "http://", 7);
366         int srcfd = -1;
367         if (is_streaming) {
368                 srcfd = openHttpConnection(m_filename);
369         } else {
370                 srcfd = ::open(m_filename.c_str(), O_RDONLY);
371         }
372         if (srcfd < 0) {
373                 eDebug("Cannot open source stream: %s", m_filename.c_str());
374                 return 1;
375         }
376
377         int destfd = ::open("/dev/misc/pvr", O_WRONLY);
378         if (destfd < 0) {
379                 eDebug("Cannot open /dev/misc/pvr");
380                 ::close(srcfd);
381                 return 1;
382         }
383         //m_decodedemux->flush();
384         m_streamthread->start(srcfd, destfd);
385         //m_decoder->unfreeze();
386         return 0;
387 }
388
389 // iSeekableService
390 RESULT eServiceWebTS::seek(ePtr<iSeekableService> &ptr)
391 {
392         ptr = this;
393         return 0;
394 }
395
396 RESULT eServiceWebTS::getLength(pts_t &pts)
397 {
398         return 0;
399 }
400
401 RESULT eServiceWebTS::seekTo(pts_t to)
402 {
403         return 0;
404 }
405
406 RESULT eServiceWebTS::seekRelative(int direction, pts_t to)
407 {
408         return 0;
409 }
410
411 RESULT eServiceWebTS::getPlayPosition(pts_t &pts)
412 {
413         return 0;
414 }
415
416 RESULT eServiceWebTS::setTrickmode(int trick)
417 {
418         return -1;
419 }
420
421 RESULT eServiceWebTS::isCurrentlySeekable()
422 {
423         return 1;
424 }
425
426 RESULT eServiceWebTS::info(ePtr<iServiceInformation>&i)
427 {
428         i = this;
429         return 0;
430 }
431
432 RESULT eServiceWebTS::getName(std::string &name)
433 {
434         name = m_filename;
435         size_t n = name.rfind('/');
436         if (n != std::string::npos)
437                 name = name.substr(n + 1);
438         return 0;
439 }
440
441 int eServiceWebTS::getInfo(int w)
442 {
443         return resNA;
444 }
445
446 std::string eServiceWebTS::getInfoString(int w)
447 {
448         return "";
449 }
450
451 int eServiceWebTS::getNumberOfTracks() {
452         if (m_audioInfo)
453                 return (int)m_audioInfo->audioStreams.size();
454         else
455                 return 0;
456 }
457
458 RESULT eServiceWebTS::selectTrack(unsigned int i) {
459         if (m_audioInfo) {
460                 m_apid = m_audioInfo->audioStreams[i].pid;
461                 eDebug("[ServiceWebTS] audio track %d PID 0x%02x type %d\n", i, m_apid, m_audioInfo->audioStreams[i].type);
462                 m_decoder->setAudioPID(m_apid, m_audioInfo->audioStreams[i].type);
463                 if (m_state == stRunning)
464                         m_decoder->preroll();
465                 return 0;
466         } else {
467                 return -1;
468         }
469 }
470
471 RESULT eServiceWebTS::getTrackInfo(struct iAudioTrackInfo &info, unsigned int n) {
472         if (m_audioInfo) {
473                 info.m_pid = m_audioInfo->audioStreams[n].pid;
474                 info.m_description = m_audioInfo->audioStreams[n].description;
475                 info.m_language = m_audioInfo->audioStreams[n].language;
476                 return 0;
477         } else {
478                 return -1;
479         }
480 }
481
482 int eServiceWebTS::getCurrentTrack() {
483         if (m_audioInfo) {
484                 for (size_t i = 0; i < m_audioInfo->audioStreams.size(); i++) {
485                         if (m_apid == m_audioInfo->audioStreams[i].pid) {
486                                 return i;
487                         }
488                 }
489         }
490         return -1;
491 }
492
493 /********************************************************************/
494 /* eStreamThreadWeb                                                       */
495 /********************************************************************/
496
497 DEFINE_REF(eStreamThreadWeb)
498
499 eStreamThreadWeb::eStreamThreadWeb(): m_messagepump(eApp, 0) {
500         CONNECT(m_messagepump.recv_msg, eStreamThreadWeb::recvEvent);
501 }
502 eStreamThreadWeb::~eStreamThreadWeb() {
503 }
504
505 void eStreamThreadWeb::start(int srcfd, int destfd) {
506         m_srcfd = srcfd;
507         m_destfd = destfd;
508         m_stop = false;
509         m_audioInfo = 0;
510         run(IOPRIO_CLASS_RT);
511 }
512 void eStreamThreadWeb::stop() {
513         m_stop = true;
514         kill();
515 }
516
517 void eStreamThreadWeb::recvEvent(const int &evt)
518 {
519         m_event(evt);
520 }
521
522 RESULT eStreamThreadWeb::getAudioInfo(ePtr<TSAudioInfoWeb> &ptr)
523 {
524         ptr = m_audioInfo;
525         return 0;
526 }
527
528 #define REGISTRATION_DESCRIPTOR 5
529 #define LANGUAGE_DESCRIPTOR 10
530
531 std::string eStreamThreadWeb::getDescriptor(unsigned char buf[], int buflen, int type)
532 {
533         int desc_len;
534         while (buflen > 1) {
535                 desc_len = buf[1];
536                 if (buf[0] == type) {
537                         char str[21];
538                         if (desc_len > 20) desc_len = 20;
539                         strncpy(str, (char*)buf+2, desc_len);
540                         str[desc_len] = '\0';
541                         return std::string(str);
542                 } else {
543                         buflen -= desc_len+2;
544                         buf += desc_len+2;
545                 }
546         }
547         return "";
548 }
549
550 bool eStreamThreadWeb::scanAudioInfo(unsigned char buf[], int len)
551 {
552         if (len < 1880)
553                 return false;
554
555         int adaptfield, pmtpid, offset;
556         unsigned char pmt[1188];
557         int pmtsize = 0;
558
559         for (int a=0; a < len - 188*4; a++) {
560                 if ( buf[a] != 0x47 || buf[a + 188] != 0x47 || buf[a + 376] != 0x47 )
561                         continue; // TS Header
562
563                 if ((0x40 & buf[a + 1]) == 0) // start
564                         continue;
565
566                 if ((0xC0 & buf[a + 3]) != 0) // scrambling
567                         continue;
568
569                 adaptfield = (0x30 & buf[a + 3]) >> 4;
570
571                 if ((adaptfield & 1) == 0) // adapt - no payload
572                         continue;
573
574                 offset = adaptfield == 3 ? 1 + (0xFF & buf[a + 4]) : 0; //adaptlength
575
576                 if (buf[a + offset + 4] != 0 || buf[a + offset + 5] != 2 || (0xF0 & buf[a + offset + 6]) != 0xB0)
577                 {
578                         a += 187;
579                         continue;
580                 }
581
582                 pmtpid = (0x1F & buf[a + 1])<<8 | (0xFF & buf[a + 2]);
583                 memcpy(pmt + pmtsize, buf + a + 4 + offset, 184 - offset);
584                 pmtsize += 184 - offset;
585
586                 if (pmtsize >= 1000)
587                         break;
588         }
589
590         if (pmtsize == 0) return false;
591
592         int pmtlen = (0x0F & pmt[2]) << 8 | (0xFF & pmt[3]);
593         std::string lang;
594         std::string pd_type;
595         ePtr<TSAudioInfoWeb> ainfo = new TSAudioInfoWeb();
596
597         for (int b=8; b < pmtlen-4 && b < pmtsize-6; b++)
598         {
599                 if ( (0xe0 & pmt[b+1]) != 0xe0 )
600                         continue;
601
602                 int pid = (0x1F & pmt[b+1])<<8 | (0xFF & pmt[b+2]);
603                 switch(pmt[b])
604                 {
605                 case 1:
606                 case 2: // MPEG Video
607                         //addVideo(pid, "MPEG2");
608                         if (VPID == 0)
609                                 VPID= pid;
610                         break;
611
612                 case 0x1B: // H.264 Video
613                         //addVideo(pid, "H.264");
614                         break;
615                 case 3:
616                 case 4: // MPEG Audio
617                         if (APID == 0)
618                                 APID =pid;
619                         lang = getDescriptor(pmt+b+5, pmt[b+4], LANGUAGE_DESCRIPTOR);
620                         ainfo->addAudio(pid, lang, "MPEG", eDVBAudio::aMPEG);
621                         break;
622
623                 case 0x80:
624                 case 0x81:  //private data of AC3 in ATSC
625                 case 0x82:
626                 case 0x83:
627                 case 6:
628                         lang = getDescriptor(pmt+b+5, pmt[b+4], LANGUAGE_DESCRIPTOR);
629                         pd_type = getDescriptor(pmt+b+5, pmt[b+4], REGISTRATION_DESCRIPTOR);
630                         //if (pd_type == "AC-3")
631                         // dirty dirty :-) Aber es funktioniert...
632                         if (lang.length() != 0)
633                                 ainfo->addAudio(pid, lang, "AC-3", eDVBAudio::aAC3);
634                         break;
635                 }
636                 b += 4 + pmt[b+4];
637         }
638         if (ainfo->audioStreams.size() > 0) {
639                 m_audioInfo = ainfo;
640                 return true;
641         } else {
642                 return false;
643         }
644 }
645
646 void eStreamThreadWeb::thread() {
647         const int bufsize = 40000;
648         unsigned char buf[bufsize];
649         bool eof = false;
650         fd_set rfds;
651         fd_set wfds;
652         struct timeval timeout;
653         int rc,r,w,maxfd;
654         time_t next_scantime = 0;
655         bool sosSend = false;
656
657         r = w = 0;
658         hasStarted();
659         eDebug("eStreamThreadWeb started");
660         while (!m_stop) {
661                 pthread_testcancel();
662                 FD_ZERO(&rfds);
663                 FD_ZERO(&wfds);
664                 maxfd = 0;
665                 timeout.tv_sec = 1;
666                 timeout.tv_usec = 0;
667                 if (r < bufsize) {
668                         FD_SET(m_srcfd, &rfds);
669                         maxfd = MAX(maxfd, m_srcfd);
670                 }
671                 if (w < r) {
672                         FD_SET(m_destfd, &wfds);
673                         maxfd = MAX(maxfd, m_destfd);
674                 }
675                 rc = select(maxfd+1, &rfds, &wfds, NULL, &timeout);
676                 if (rc == 0) {
677                         eDebug("eStreamThreadWeb::thread: timeout!");
678                         continue;
679                 }
680                 if (rc < 0) {
681                         eDebug("eStreamThreadWeb::thread: error in select (%d)", errno);
682                         break;
683                 }
684                 if (FD_ISSET(m_srcfd, &rfds)) {
685                         rc = ::read(m_srcfd, buf+r, bufsize - r);
686                         if (rc < 0) {
687                                 eDebug("eStreamThreadWeb::thread: error in read (%d)", errno);
688                                 m_messagepump.send(evtReadError);
689                                 break;
690                         } else if (rc == 0) {
691                                 eof = true;
692                         } else {
693                                 if (!sosSend) {
694                                         sosSend = true;
695                                         m_messagepump.send(evtSOS);
696                                 }
697                                 r += rc;
698                                 if (r == bufsize) eDebug("eStreamThreadWeb::thread: buffer full");
699                         }
700                 }
701                 if (FD_ISSET(m_destfd, &wfds) && (w < r) && ((r > bufsize/4) || eof)) {
702                         rc = ::write(m_destfd, buf+w, r-w);
703                         if (rc < 0) {
704                                 eDebug("eStreamThreadWeb::thread: error in write (%d)", errno);
705                                 m_messagepump.send(evtWriteError);
706                                 break;
707                         }
708                         w += rc;
709                         //eDebug("eStreamThreadWeb::thread: buffer r=%d w=%d",r,w);
710                         if (w == r) {
711                                 if (time(0) >= next_scantime) {
712                                         if (scanAudioInfo(buf, r)) {
713                                                 m_messagepump.send(evtStreamInfo);
714                                                 next_scantime = time(0) + 1;
715                                         }
716                                 }
717                                 w = r = 0;
718                         }
719                 }
720                 if (eof && (r==w)) {
721                         ::close(m_destfd);
722                         m_destfd = -1;
723                         ::close(m_srcfd);
724                         m_srcfd = -1;
725                         m_messagepump.send(evtEOS);
726                         break;
727                 }
728         }
729         eDebug("eStreamThreadWeb end");
730 }
731
732 void eStreamThreadWeb::thread_finished() {
733         if (m_srcfd >= 0) ::close(m_srcfd);
734         if (m_destfd >= 0) ::close(m_destfd);
735         eDebug("eStreamThreadWeb closed");
736 }
737
738 eAutoInitPtr<eServiceFactoryWebTS> init_eServiceFactoryWebTS(eAutoInitNumbers::service+1, "eServiceFactoryWebTS");
739
740 PyMODINIT_FUNC
741 initservicewebts(void)
742 {
743         Py_InitModule("servicewebts", NULL);
744 }