needed changed for dm800/dm8000 drivers > 20090720
[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         m_destfd = -1;
126 }
127
128 eServiceWebTS::~eServiceWebTS()
129 {
130         eDebug("ServiceWebTS destruct!");
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 (m_destfd == -1)
267         {
268                 m_destfd = ::open("/dev/misc/pvr", O_WRONLY);
269                 if (m_destfd < 0)
270                 {
271                         eDebug("Cannot open /dev/misc/pvr");
272                         return -1;
273                 }
274         }
275         if (dvbChannel.getDemux(m_decodedemux, iDVBChannel::capDecode) != 0) {
276                 eDebug("Cannot allocate decode-demux");
277                 return -1;
278         }
279         if (m_decodedemux->getMPEGDecoder(m_decoder, 1) != 0) {
280                 eDebug("Cannot allocate MPEGDecoder");
281                 return -1;
282         }
283         //m_decoder->setVideoPID(m_vpid, eDVBVideo::MPEG2);
284         //m_decoder->setAudioPID(m_apid, eDVBAudio::aMPEG);
285         m_streamthread = new eStreamThreadWeb();
286         CONNECT(m_streamthread->m_event, eServiceWebTS::recv_event);
287         //m_decoder->freeze(0);
288         //m_decoder->preroll();
289         if (unpause() != 0) return -1;
290         //m_state = stRunning;
291         //m_event(this, evStart);
292         return 0;
293 }
294
295 RESULT eServiceWebTS::stop()
296 {
297         if (m_destfd >= 0)
298         {
299                 ::close(m_destfd);
300                 m_destfd = -1;
301         }
302         if (m_state != stRunning)
303                 return -1;
304         printf("TS: %s stop\n", m_filename.c_str());
305         m_streamthread->stop();
306         m_decodedemux->flush();
307         m_state = stStopped;
308         m_audioInfo = 0;
309         APID = 0;
310         VPID = 0;
311         PID_SET = 0;
312         return 0;
313 }
314
315 void eServiceWebTS::recv_event(int evt)
316 {
317         eDebug("eServiceWebTS::recv_event: %d", evt);
318         switch (evt) {
319         case eStreamThreadWeb::evtEOS:
320                 m_decodedemux->flush();
321                 m_state = stStopped;
322                 m_event((iPlayableService*)this, evEOF);
323                 break;
324         case eStreamThreadWeb::evtReadError:
325         case eStreamThreadWeb::evtWriteError:
326                 m_decoder->pause();
327                 m_state = stStopped;
328                 m_event((iPlayableService*)this, evEOF);
329                 break;
330         case eStreamThreadWeb::evtSOS:
331                 m_event((iPlayableService*)this, evSOF);
332                 break;
333         case eStreamThreadWeb::evtStreamInfo:
334                 if (VPID != 0 && PID_SET == 0 && APID != 0)
335                 {
336                         PID_SET = 1;
337                         m_decodedemux->flush();
338                         m_decoder->setVideoPID(VPID, eDVBVideo::MPEG2);
339                         m_decoder->setAudioPID(APID, eDVBAudio::aMPEG);
340                         m_decoder->pause();
341                         m_state = stRunning;
342                         m_event(this, evStart);
343                         m_decoder->play();
344                         
345                 }       
346                 bool wasnull = !m_audioInfo;
347                 m_streamthread->getAudioInfo(m_audioInfo);
348                 //if (m_audioInfo)
349                 //      eDebug("[ServiceWebTS] %d audiostreams found", m_audioInfo->audioStreams.size());
350                 if (m_audioInfo && wasnull) {
351                         eDebug("[ServiceWebTS] %d audiostreams found", m_audioInfo->audioStreams.size());
352                         int sel = getCurrentTrack();
353                         if (sel < 0)
354                                 selectTrack(0);
355                         else if (m_audioInfo->audioStreams[sel].type != eDVBAudio::aMPEG)
356                                 selectTrack(sel);
357                 }
358                 break;
359         }
360 }
361
362 RESULT eServiceWebTS::pause(ePtr<iPauseableService> &ptr)
363 {
364         ptr = this;
365         return 0;
366 }
367
368 // iPausableService
369 RESULT eServiceWebTS::pause()
370 {
371         m_streamthread->stop();
372         m_decoder->pause();
373         return 0;
374 }
375
376 RESULT eServiceWebTS::unpause()
377 {
378         int is_streaming = !strncmp(m_filename.c_str(), "http://", 7);
379         int srcfd = -1;
380         if (is_streaming) {
381                 srcfd = openHttpConnection(m_filename);
382         } else {
383                 srcfd = ::open(m_filename.c_str(), O_RDONLY);
384         }
385         if (srcfd < 0) {
386                 eDebug("Cannot open source stream: %s", m_filename.c_str());
387                 return 1;
388         }
389         //m_decodedemux->flush();
390         m_streamthread->start(srcfd, m_destfd);
391         //m_decoder->unfreeze();
392         return 0;
393 }
394
395 // iSeekableService
396 RESULT eServiceWebTS::seek(ePtr<iSeekableService> &ptr)
397 {
398         ptr = this;
399         return 0;
400 }
401
402 RESULT eServiceWebTS::getLength(pts_t &pts)
403 {
404         return 0;
405 }
406
407 RESULT eServiceWebTS::seekTo(pts_t to)
408 {
409         return 0;
410 }
411
412 RESULT eServiceWebTS::seekRelative(int direction, pts_t to)
413 {
414         return 0;
415 }
416
417 RESULT eServiceWebTS::getPlayPosition(pts_t &pts)
418 {
419         return 0;
420 }
421
422 RESULT eServiceWebTS::setTrickmode(int trick)
423 {
424         return -1;
425 }
426
427 RESULT eServiceWebTS::isCurrentlySeekable()
428 {
429         return 1;
430 }
431
432 RESULT eServiceWebTS::info(ePtr<iServiceInformation>&i)
433 {
434         i = this;
435         return 0;
436 }
437
438 RESULT eServiceWebTS::getName(std::string &name)
439 {
440         name = m_filename;
441         size_t n = name.rfind('/');
442         if (n != std::string::npos)
443                 name = name.substr(n + 1);
444         return 0;
445 }
446
447 int eServiceWebTS::getInfo(int w)
448 {
449         return resNA;
450 }
451
452 std::string eServiceWebTS::getInfoString(int w)
453 {
454         return "";
455 }
456
457 int eServiceWebTS::getNumberOfTracks() {
458         if (m_audioInfo)
459                 return (int)m_audioInfo->audioStreams.size();
460         else
461                 return 0;
462 }
463
464 RESULT eServiceWebTS::selectTrack(unsigned int i) {
465         if (m_audioInfo) {
466                 m_apid = m_audioInfo->audioStreams[i].pid;
467                 eDebug("[ServiceWebTS] audio track %d PID 0x%02x type %d\n", i, m_apid, m_audioInfo->audioStreams[i].type);
468                 m_decoder->setAudioPID(m_apid, m_audioInfo->audioStreams[i].type);
469                 if (m_state == stRunning)
470                         m_decoder->set();
471                 return 0;
472         } else {
473                 return -1;
474         }
475 }
476
477 RESULT eServiceWebTS::getTrackInfo(struct iAudioTrackInfo &info, unsigned int n) {
478         if (m_audioInfo) {
479                 info.m_pid = m_audioInfo->audioStreams[n].pid;
480                 info.m_description = m_audioInfo->audioStreams[n].description;
481                 info.m_language = m_audioInfo->audioStreams[n].language;
482                 return 0;
483         } else {
484                 return -1;
485         }
486 }
487
488 int eServiceWebTS::getCurrentTrack() {
489         if (m_audioInfo) {
490                 for (size_t i = 0; i < m_audioInfo->audioStreams.size(); i++) {
491                         if (m_apid == m_audioInfo->audioStreams[i].pid) {
492                                 return i;
493                         }
494                 }
495         }
496         return -1;
497 }
498
499 /********************************************************************/
500 /* eStreamThreadWeb                                                       */
501 /********************************************************************/
502
503 DEFINE_REF(eStreamThreadWeb)
504
505 eStreamThreadWeb::eStreamThreadWeb(): m_messagepump(eApp, 0) {
506         CONNECT(m_messagepump.recv_msg, eStreamThreadWeb::recvEvent);
507 }
508 eStreamThreadWeb::~eStreamThreadWeb() {
509 }
510
511 void eStreamThreadWeb::start(int srcfd, int destfd) {
512         m_srcfd = srcfd;
513         m_destfd = destfd;
514         m_stop = false;
515         m_audioInfo = 0;
516         run(IOPRIO_CLASS_RT);
517 }
518
519 void eStreamThreadWeb::stop() {
520         m_stop = true;
521         kill();
522 }
523
524 void eStreamThreadWeb::recvEvent(const int &evt)
525 {
526         m_event(evt);
527 }
528
529 RESULT eStreamThreadWeb::getAudioInfo(ePtr<TSAudioInfoWeb> &ptr)
530 {
531         ptr = m_audioInfo;
532         return 0;
533 }
534
535 #define REGISTRATION_DESCRIPTOR 5
536 #define LANGUAGE_DESCRIPTOR 10
537
538 std::string eStreamThreadWeb::getDescriptor(unsigned char buf[], int buflen, int type)
539 {
540         int desc_len;
541         while (buflen > 1) {
542                 desc_len = buf[1];
543                 if (buf[0] == type) {
544                         char str[21];
545                         if (desc_len > 20) desc_len = 20;
546                         strncpy(str, (char*)buf+2, desc_len);
547                         str[desc_len] = '\0';
548                         return std::string(str);
549                 } else {
550                         buflen -= desc_len+2;
551                         buf += desc_len+2;
552                 }
553         }
554         return "";
555 }
556
557 bool eStreamThreadWeb::scanAudioInfo(unsigned char buf[], int len)
558 {
559         if (len < 1880)
560                 return false;
561
562         int adaptfield, pmtpid, offset;
563         unsigned char pmt[1188];
564         int pmtsize = 0;
565
566         for (int a=0; a < len - 188*4; a++) {
567                 if ( buf[a] != 0x47 || buf[a + 188] != 0x47 || buf[a + 376] != 0x47 )
568                         continue; // TS Header
569
570                 if ((0x40 & buf[a + 1]) == 0) // start
571                         continue;
572
573                 if ((0xC0 & buf[a + 3]) != 0) // scrambling
574                         continue;
575
576                 adaptfield = (0x30 & buf[a + 3]) >> 4;
577
578                 if ((adaptfield & 1) == 0) // adapt - no payload
579                         continue;
580
581                 offset = adaptfield == 3 ? 1 + (0xFF & buf[a + 4]) : 0; //adaptlength
582
583                 if (buf[a + offset + 4] != 0 || buf[a + offset + 5] != 2 || (0xF0 & buf[a + offset + 6]) != 0xB0)
584                 {
585                         a += 187;
586                         continue;
587                 }
588
589                 pmtpid = (0x1F & buf[a + 1])<<8 | (0xFF & buf[a + 2]);
590                 memcpy(pmt + pmtsize, buf + a + 4 + offset, 184 - offset);
591                 pmtsize += 184 - offset;
592
593                 if (pmtsize >= 1000)
594                         break;
595         }
596
597         if (pmtsize == 0) return false;
598
599         int pmtlen = (0x0F & pmt[2]) << 8 | (0xFF & pmt[3]);
600         std::string lang;
601         std::string pd_type;
602         ePtr<TSAudioInfoWeb> ainfo = new TSAudioInfoWeb();
603
604         for (int b=8; b < pmtlen-4 && b < pmtsize-6; b++)
605         {
606                 if ( (0xe0 & pmt[b+1]) != 0xe0 )
607                         continue;
608
609                 int pid = (0x1F & pmt[b+1])<<8 | (0xFF & pmt[b+2]);
610                 switch(pmt[b])
611                 {
612                 case 1:
613                 case 2: // MPEG Video
614                         //addVideo(pid, "MPEG2");
615                         if (VPID == 0)
616                                 VPID= pid;
617                         break;
618
619                 case 0x1B: // H.264 Video
620                         //addVideo(pid, "H.264");
621                         break;
622                 case 3:
623                 case 4: // MPEG Audio
624                         if (APID == 0)
625                                 APID =pid;
626                         lang = getDescriptor(pmt+b+5, pmt[b+4], LANGUAGE_DESCRIPTOR);
627                         ainfo->addAudio(pid, lang, "MPEG", eDVBAudio::aMPEG);
628                         break;
629
630                 case 0x80:
631                 case 0x81:  //private data of AC3 in ATSC
632                 case 0x82:
633                 case 0x83:
634                 case 6:
635                         lang = getDescriptor(pmt+b+5, pmt[b+4], LANGUAGE_DESCRIPTOR);
636                         pd_type = getDescriptor(pmt+b+5, pmt[b+4], REGISTRATION_DESCRIPTOR);
637                         //if (pd_type == "AC-3")
638                         // dirty dirty :-) Aber es funktioniert...
639                         if (lang.length() != 0)
640                                 ainfo->addAudio(pid, lang, "AC-3", eDVBAudio::aAC3);
641                         break;
642                 }
643                 b += 4 + pmt[b+4];
644         }
645         if (ainfo->audioStreams.size() > 0) {
646                 m_audioInfo = ainfo;
647                 return true;
648         } else {
649                 return false;
650         }
651 }
652
653 void eStreamThreadWeb::thread() {
654         const int bufsize = 40000;
655         unsigned char buf[bufsize];
656         bool eof = false;
657         fd_set rfds;
658         fd_set wfds;
659         struct timeval timeout;
660         int rc,r,w,maxfd;
661         time_t next_scantime = 0;
662         bool sosSend = false;
663
664         r = w = 0;
665         hasStarted();
666         eDebug("eStreamThreadWeb started");
667         while (!m_stop) {
668                 pthread_testcancel();
669                 FD_ZERO(&rfds);
670                 FD_ZERO(&wfds);
671                 maxfd = 0;
672                 timeout.tv_sec = 1;
673                 timeout.tv_usec = 0;
674                 if (r < bufsize) {
675                         FD_SET(m_srcfd, &rfds);
676                         maxfd = MAX(maxfd, m_srcfd);
677                 }
678                 if (w < r) {
679                         FD_SET(m_destfd, &wfds);
680                         maxfd = MAX(maxfd, m_destfd);
681                 }
682                 rc = select(maxfd+1, &rfds, &wfds, NULL, &timeout);
683                 if (rc == 0) {
684                         eDebug("eStreamThreadWeb::thread: timeout!");
685                         continue;
686                 }
687                 if (rc < 0) {
688                         eDebug("eStreamThreadWeb::thread: error in select (%d)", errno);
689                         break;
690                 }
691                 if (FD_ISSET(m_srcfd, &rfds)) {
692                         rc = ::read(m_srcfd, buf+r, bufsize - r);
693                         if (rc < 0) {
694                                 eDebug("eStreamThreadWeb::thread: error in read (%d)", errno);
695                                 m_messagepump.send(evtReadError);
696                                 break;
697                         } else if (rc == 0) {
698                                 eof = true;
699                         } else {
700                                 if (!sosSend) {
701                                         sosSend = true;
702                                         m_messagepump.send(evtSOS);
703                                 }
704                                 r += rc;
705                                 if (r == bufsize) eDebug("eStreamThreadWeb::thread: buffer full");
706                         }
707                 }
708                 if (FD_ISSET(m_destfd, &wfds) && (w < r) && ((r > bufsize/4) || eof)) {
709                         rc = ::write(m_destfd, buf+w, r-w);
710                         if (rc < 0) {
711                                 eDebug("eStreamThreadWeb::thread: error in write (%d)", errno);
712                                 m_messagepump.send(evtWriteError);
713                                 break;
714                         }
715                         w += rc;
716                         //eDebug("eStreamThreadWeb::thread: buffer r=%d w=%d",r,w);
717                         if (w == r) {
718                                 if (time(0) >= next_scantime) {
719                                         if (scanAudioInfo(buf, r)) {
720                                                 m_messagepump.send(evtStreamInfo);
721                                                 next_scantime = time(0) + 1;
722                                         }
723                                 }
724                                 w = r = 0;
725                         }
726                 }
727                 if (eof && (r==w)) {
728                         ::close(m_srcfd);
729                         m_srcfd = -1;
730                         m_messagepump.send(evtEOS);
731                         break;
732                 }
733         }
734         eDebug("eStreamThreadWeb end");
735 }
736
737 void eStreamThreadWeb::thread_finished() {
738         if (m_srcfd >= 0) ::close(m_srcfd);
739         eDebug("eStreamThreadWeb closed");
740 }
741
742 eAutoInitPtr<eServiceFactoryWebTS> init_eServiceFactoryWebTS(eAutoInitNumbers::service+1, "eServiceFactoryWebTS");
743
744 PyMODINIT_FUNC
745 initservicewebts(void)
746 {
747         Py_InitModule("servicewebts", NULL);
748 }