enable h.264 streaming
[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 int H264=0;
38
39
40 /********************************************************************/
41 /* eServiceFactoryWebTS                                                */
42 /********************************************************************/
43
44 eServiceFactoryWebTS::eServiceFactoryWebTS()
45 {
46         ePtr<eServiceCenter> sc;
47
48         eServiceCenter::getPrivInstance(sc);
49         if (sc)
50         {
51                 std::list<std::string> extensions;
52                 sc->addServiceFactory(eServiceFactoryWebTS::id, this, extensions);
53         }
54 }
55
56 eServiceFactoryWebTS::~eServiceFactoryWebTS()
57 {
58         ePtr<eServiceCenter> sc;
59
60         eServiceCenter::getPrivInstance(sc);
61         if (sc)
62                 sc->removeServiceFactory(eServiceFactoryWebTS::id);
63 }
64
65 DEFINE_REF(eServiceFactoryWebTS)
66
67 // iServiceHandler
68 RESULT eServiceFactoryWebTS::play(const eServiceReference &ref, ePtr<iPlayableService> &ptr)
69 {
70         ptr = new eServiceWebTS(ref);
71         return 0;
72 }
73
74 RESULT eServiceFactoryWebTS::record(const eServiceReference &ref, ePtr<iRecordableService> &ptr)
75 {
76         ptr=0;
77         return -1;
78 }
79
80 RESULT eServiceFactoryWebTS::list(const eServiceReference &, ePtr<iListableService> &ptr)
81 {
82         ptr=0;
83         return -1;
84 }
85
86 RESULT eServiceFactoryWebTS::info(const eServiceReference &ref, ePtr<iStaticServiceInformation> &ptr)
87 {
88         ptr = 0;
89         return -1;
90 }
91
92 RESULT eServiceFactoryWebTS::offlineOperations(const eServiceReference &, ePtr<iServiceOfflineOperations> &ptr)
93 {
94         ptr = 0;
95         return -1;
96 }
97
98
99 /********************************************************************/
100 /* TSAudioInfoWeb                                            */
101 /********************************************************************/
102 DEFINE_REF(TSAudioInfoWeb);
103
104 void TSAudioInfoWeb::addAudio(int pid, std::string lang, std::string desc, int type) {
105         StreamInfo as;
106         as.description = desc;
107         as.language = lang;
108         as.pid = pid;
109         as.type = type;
110         audioStreams.push_back(as);
111 }
112
113
114 /********************************************************************/
115 /* eServiceWebTS                                                       */
116 /********************************************************************/
117
118 eServiceWebTS::eServiceWebTS(const eServiceReference &url): m_pump(eApp, 1)
119 {
120         eDebug("ServiceWebTS construct!");
121         m_filename = url.path.c_str();
122         m_vpid = url.getData(0) == 0 ? 0x44 : url.getData(0);
123         m_apid = url.getData(1) == 0 ? 0x45 : url.getData(1);
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.\n %d --- %d",rc,statuscode);
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)
290                 return -1;
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         printf("TS: %s stop\n", m_filename.c_str());
303         m_streamthread->stop();
304         m_decodedemux->flush();
305         m_audioInfo = 0;
306         APID = 0;
307         VPID = 0;
308         PID_SET = 0;
309         H264 = 0;
310         return 0;
311 }
312
313 void eServiceWebTS::recv_event(int evt)
314 {
315         eDebug("eServiceWebTS::recv_event: %d", evt);
316         switch (evt) {
317         case eStreamThreadWeb::evtEOS:
318                 m_decodedemux->flush();
319                 m_event((iPlayableService*)this, evEOF);
320                 break;
321         case eStreamThreadWeb::evtReadError:
322         case eStreamThreadWeb::evtWriteError:
323                 m_decoder->pause();
324                 m_event((iPlayableService*)this, evEOF);
325                 break;
326         case eStreamThreadWeb::evtSOS:
327                 m_event((iPlayableService*)this, evSOF);
328                 break;
329         case eStreamThreadWeb::evtStreamInfo:
330                 if (VPID != 0 && PID_SET == 0 && APID != 0)
331                 {
332                         PID_SET = 1;
333                         m_decodedemux->flush();
334                         if (H264)
335                                 m_decoder->setVideoPID(VPID, eDVBVideo::MPEG4_H264);
336                         else
337                                 m_decoder->setVideoPID(VPID, eDVBVideo::MPEG2);
338                         m_decoder->setAudioPID(APID, eDVBAudio::aMPEG);
339                         m_decoder->pause();
340                         m_event(this, evStart);
341                         m_decoder->play();
342                         
343                 }
344                 bool wasnull = !m_audioInfo;
345                 m_streamthread->getAudioInfo(m_audioInfo);
346                 //if (m_audioInfo)
347                 //      eDebug("[ServiceWebTS] %d audiostreams found", m_audioInfo->audioStreams.size());
348                 if (m_audioInfo && wasnull) {
349                         eDebug("[ServiceWebTS] %d audiostreams found", m_audioInfo->audioStreams.size());
350                         int sel = getCurrentTrack();
351                         if (sel < 0)
352                                 selectTrack(0);
353                         else if (m_audioInfo->audioStreams[sel].type != eDVBAudio::aMPEG)
354                                 selectTrack(sel);
355                 }
356                 break;
357         }
358 }
359
360 RESULT eServiceWebTS::pause(ePtr<iPauseableService> &ptr)
361 {
362         ptr = this;
363         return 0;
364 }
365
366 // iPausableService
367 RESULT eServiceWebTS::pause()
368 {
369         m_streamthread->stop();
370         m_decoder->pause();
371         return 0;
372 }
373
374 RESULT eServiceWebTS::unpause()
375 {
376         if (!m_streamthread->running())
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                 if (srcfd < 0) {
385                         eDebug("Cannot open source stream: %s", m_filename.c_str());
386                         return 1;
387                 }
388                 //m_decodedemux->flush();
389                 m_streamthread->start(srcfd, m_destfd);
390                 //m_decoder->unfreeze();
391         }
392         else
393                 eDebug("unpause but thread already running!");
394         return 0;
395 }
396
397 // iSeekableService
398 RESULT eServiceWebTS::seek(ePtr<iSeekableService> &ptr)
399 {
400         ptr = this;
401         return 0;
402 }
403
404 RESULT eServiceWebTS::getLength(pts_t &pts)
405 {
406         return 0;
407 }
408
409 RESULT eServiceWebTS::seekTo(pts_t to)
410 {
411         return 0;
412 }
413
414 RESULT eServiceWebTS::seekRelative(int direction, pts_t to)
415 {
416         return 0;
417 }
418
419 RESULT eServiceWebTS::getPlayPosition(pts_t &pts)
420 {
421         return 0;
422 }
423
424 RESULT eServiceWebTS::setTrickmode(int trick)
425 {
426         return -1;
427 }
428
429 RESULT eServiceWebTS::isCurrentlySeekable()
430 {
431         return 1;
432 }
433
434 RESULT eServiceWebTS::info(ePtr<iServiceInformation>&i)
435 {
436         i = this;
437         return 0;
438 }
439
440 RESULT eServiceWebTS::getName(std::string &name)
441 {
442         name = m_filename;
443         size_t n = name.rfind('/');
444         if (n != std::string::npos)
445                 name = name.substr(n + 1);
446         return 0;
447 }
448
449 int eServiceWebTS::getInfo(int w)
450 {
451         return resNA;
452 }
453
454 std::string eServiceWebTS::getInfoString(int w)
455 {
456         return "";
457 }
458
459 int eServiceWebTS::getNumberOfTracks() {
460         if (m_audioInfo)
461                 return (int)m_audioInfo->audioStreams.size();
462         else
463                 return 0;
464 }
465
466 RESULT eServiceWebTS::selectTrack(unsigned int i) {
467         if (m_audioInfo) {
468                 m_apid = m_audioInfo->audioStreams[i].pid;
469                 eDebug("[ServiceWebTS] audio track %d PID 0x%02x type %d\n", i, m_apid, m_audioInfo->audioStreams[i].type);
470                 m_decoder->setAudioPID(m_apid, m_audioInfo->audioStreams[i].type);
471                 m_decoder->set();
472                 return 0;
473         } else {
474                 return -1;
475         }
476 }
477
478 RESULT eServiceWebTS::getTrackInfo(struct iAudioTrackInfo &info, unsigned int n) {
479         if (m_audioInfo) {
480                 info.m_pid = m_audioInfo->audioStreams[n].pid;
481                 info.m_description = m_audioInfo->audioStreams[n].description;
482                 info.m_language = m_audioInfo->audioStreams[n].language;
483                 return 0;
484         } else {
485                 return -1;
486         }
487 }
488
489 int eServiceWebTS::getCurrentTrack() {
490         if (m_audioInfo) {
491                 for (size_t i = 0; i < m_audioInfo->audioStreams.size(); i++) {
492                         if (m_apid == m_audioInfo->audioStreams[i].pid) {
493                                 return i;
494                         }
495                 }
496         }
497         return -1;
498 }
499
500 /********************************************************************/
501 /* eStreamThreadWeb                                                       */
502 /********************************************************************/
503
504 DEFINE_REF(eStreamThreadWeb)
505
506 eStreamThreadWeb::eStreamThreadWeb(): m_messagepump(eApp, 0) {
507         CONNECT(m_messagepump.recv_msg, eStreamThreadWeb::recvEvent);
508         m_running = false;
509 }
510
511 eStreamThreadWeb::~eStreamThreadWeb() {
512 }
513
514 void eStreamThreadWeb::start(int srcfd, int destfd) {
515         m_srcfd = srcfd;
516         m_destfd = destfd;
517         m_stop = false;
518         m_audioInfo = 0;
519         run(IOPRIO_CLASS_RT);
520 }
521
522 void eStreamThreadWeb::stop() {
523         m_stop = true;
524         kill();
525 }
526
527 void eStreamThreadWeb::recvEvent(const int &evt)
528 {
529         m_event(evt);
530 }
531
532 RESULT eStreamThreadWeb::getAudioInfo(ePtr<TSAudioInfoWeb> &ptr)
533 {
534         ptr = m_audioInfo;
535         return 0;
536 }
537
538 #define REGISTRATION_DESCRIPTOR 5
539 #define LANGUAGE_DESCRIPTOR 10
540
541 std::string eStreamThreadWeb::getDescriptor(unsigned char buf[], int buflen, int type)
542 {
543         int desc_len;
544         while (buflen > 1) {
545                 desc_len = buf[1];
546                 if (buf[0] == type) {
547                         char str[21];
548                         if (desc_len > 20) desc_len = 20;
549                         strncpy(str, (char*)buf+2, desc_len);
550                         str[desc_len] = '\0';
551                         return std::string(str);
552                 } else {
553                         buflen -= desc_len+2;
554                         buf += desc_len+2;
555                 }
556         }
557         return "";
558 }
559
560 bool eStreamThreadWeb::scanAudioInfo(unsigned char buf[], int len)
561 {
562         if (len < 1880)
563                 return false;
564
565         int adaptfield, pmtpid, offset;
566         unsigned char pmt[1188];
567         int pmtsize = 0;
568
569         for (int a=0; a < len - 188*4; a++) {
570                 if ( buf[a] != 0x47 || buf[a + 188] != 0x47 || buf[a + 376] != 0x47 )
571                         continue; // TS Header
572
573                 if ((0x40 & buf[a + 1]) == 0) // start
574                         continue;
575
576                 if ((0xC0 & buf[a + 3]) != 0) // scrambling
577                         continue;
578
579                 adaptfield = (0x30 & buf[a + 3]) >> 4;
580
581                 if ((adaptfield & 1) == 0) // adapt - no payload
582                         continue;
583
584                 offset = adaptfield == 3 ? 1 + (0xFF & buf[a + 4]) : 0; //adaptlength
585
586                 if (buf[a + offset + 4] != 0 || buf[a + offset + 5] != 2 || (0xF0 & buf[a + offset + 6]) != 0xB0)
587                 {
588                         a += 187;
589                         continue;
590                 }
591
592                 pmtpid = (0x1F & buf[a + 1])<<8 | (0xFF & buf[a + 2]);
593                 memcpy(pmt + pmtsize, buf + a + 4 + offset, 184 - offset);
594                 pmtsize += 184 - offset;
595
596                 if (pmtsize >= 1000)
597                         break;
598         }
599
600         if (pmtsize == 0) return false;
601
602         int pmtlen = (0x0F & pmt[2]) << 8 | (0xFF & pmt[3]);
603         std::string lang;
604         std::string pd_type;
605         ePtr<TSAudioInfoWeb> ainfo = new TSAudioInfoWeb();
606
607         for (int b=8; b < pmtlen-4 && b < pmtsize-6; b++)
608         {
609                 if ( (0xe0 & pmt[b+1]) != 0xe0 )
610                         continue;
611
612                 int pid = (0x1F & pmt[b+1])<<8 | (0xFF & pmt[b+2]);
613                 switch(pmt[b])
614                 {
615                 case 1:
616                 case 2: // MPEG Video
617                         //addVideo(pid, "MPEG2");
618                         H264 = 0;
619                         if (VPID == 0)
620                                 VPID= pid;
621                         break;
622
623                 case 0x1B: // H.264 Video
624                         //addVideo(pid, "H.264");
625                         H264 = 1;
626                         if (VPID == 0)
627                                 VPID= pid;
628                         break;
629                 case 3:
630                 case 4: // MPEG Audio
631                         if (APID == 0)
632                                 APID =pid;
633                         lang = getDescriptor(pmt+b+5, pmt[b+4], LANGUAGE_DESCRIPTOR);
634                         ainfo->addAudio(pid, lang, "MPEG", eDVBAudio::aMPEG);
635                         break;
636
637                 case 0x80:
638                 case 0x81:  //private data of AC3 in ATSC
639                 case 0x82:
640                 case 0x83:
641                 case 6:
642                         lang = getDescriptor(pmt+b+5, pmt[b+4], LANGUAGE_DESCRIPTOR);
643                         pd_type = getDescriptor(pmt+b+5, pmt[b+4], REGISTRATION_DESCRIPTOR);
644                         //if (pd_type == "AC-3")
645                         // dirty dirty :-) Aber es funktioniert...
646                         if (lang.length() != 0)
647                         {
648                                 ainfo->addAudio(pid, lang, "AC-3", eDVBAudio::aAC3);
649                                 if (APID == 0)
650                                         APID =pid;
651                         }
652                         break;
653                 }
654                 b += 4 + pmt[b+4];
655         }
656         if (ainfo->audioStreams.size() > 0) {
657                 m_audioInfo = ainfo;
658                 return true;
659         } else {
660                 return false;
661         }
662 }
663
664 void eStreamThreadWeb::thread() {
665         const int bufsize = 40000;
666         unsigned char buf[bufsize];
667         bool eof = false;
668         fd_set rfds;
669         fd_set wfds;
670         struct timeval timeout;
671         int rc,r,w,maxfd;
672         time_t next_scantime = 0;
673         bool sosSend = false;
674         m_running = true;
675
676         r = w = 0;
677         hasStarted();
678         eDebug("eStreamThreadWeb started");
679         while (!m_stop) {
680                 pthread_testcancel();
681                 FD_ZERO(&rfds);
682                 FD_ZERO(&wfds);
683                 maxfd = 0;
684                 timeout.tv_sec = 1;
685                 timeout.tv_usec = 0;
686                 if (r < bufsize) {
687                         FD_SET(m_srcfd, &rfds);
688                         maxfd = MAX(maxfd, m_srcfd);
689                 }
690                 if (w < r) {
691                         FD_SET(m_destfd, &wfds);
692                         maxfd = MAX(maxfd, m_destfd);
693                 }
694                 rc = select(maxfd+1, &rfds, &wfds, NULL, &timeout);
695                 if (rc == 0) {
696                         eDebug("eStreamThreadWeb::thread: timeout!");
697                         continue;
698                 }
699                 if (rc < 0) {
700                         eDebug("eStreamThreadWeb::thread: error in select (%d)", errno);
701                         break;
702                 }
703                 if (FD_ISSET(m_srcfd, &rfds)) {
704                         rc = ::read(m_srcfd, buf+r, bufsize - r);
705                         if (rc < 0) {
706                                 eDebug("eStreamThreadWeb::thread: error in read (%d)", errno);
707                                 m_messagepump.send(evtReadError);
708                                 break;
709                         } else if (rc == 0) {
710                                 eof = true;
711                         } else {
712                                 if (!sosSend) {
713                                         sosSend = true;
714                                         m_messagepump.send(evtSOS);
715                                 }
716                                 r += rc;
717                                 if (r == bufsize) eDebug("eStreamThreadWeb::thread: buffer full");
718                         }
719                 }
720                 if (FD_ISSET(m_destfd, &wfds) && (w < r) && ((r > bufsize/4) || eof)) {
721                         rc = ::write(m_destfd, buf+w, r-w);
722                         if (rc < 0) {
723                                 eDebug("eStreamThreadWeb::thread: error in write (%d)", errno);
724                                 m_messagepump.send(evtWriteError);
725                                 break;
726                         }
727                         w += rc;
728                         //eDebug("eStreamThreadWeb::thread: buffer r=%d w=%d",r,w);
729                         if (w == r) {
730                                 if (time(0) >= next_scantime) {
731                                         if (scanAudioInfo(buf, r)) {
732                                                 m_messagepump.send(evtStreamInfo);
733                                                 next_scantime = time(0) + 1;
734                                         }
735                                 }
736                                 w = r = 0;
737                         }
738                 }
739                 if (eof && (r==w)) {
740                         m_messagepump.send(evtEOS);
741                         break;
742                 }
743         }
744         eDebug("eStreamThreadWeb end");
745 }
746
747 void eStreamThreadWeb::thread_finished() {
748         if (m_srcfd >= 0)
749                 ::close(m_srcfd);
750         eDebug("eStreamThreadWeb closed");
751         m_running = false;
752 }
753
754 eAutoInitPtr<eServiceFactoryWebTS> init_eServiceFactoryWebTS(eAutoInitNumbers::service+1, "eServiceFactoryWebTS");
755
756 PyMODINIT_FUNC
757 initservicewebts(void)
758 {
759         Py_InitModule("servicewebts", NULL);
760 }