]> git.vomp.tv Git - vompclient.git/blob - player.cc
Preparations for dynamic mode switching
[vompclient.git] / player.cc
1 /*\r
2     Copyright 2004-2008 Chris Tallon\r
3 \r
4     This file is part of VOMP.\r
5 \r
6     VOMP is free software; you can redistribute it and/or modify\r
7     it under the terms of the GNU General Public License as published by\r
8     the Free Software Foundation; either version 2 of the License, or\r
9     (at your option) any later version.\r
10 \r
11     VOMP is distributed in the hope that it will be useful,\r
12     but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
14     GNU General Public License for more details.\r
15 \r
16     You should have received a copy of the GNU General Public License\r
17     along with VOMP; if not, write to the Free Software\r
18     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\r
19 */\r
20 \r
21 #include "player.h"\r
22 \r
23 #include "log.h"\r
24 #include "audio.h"\r
25 #include "video.h"\r
26 #include "demuxervdr.h"\r
27 #include "demuxerts.h"\r
28 #include "vdr.h"\r
29 #include "messagequeue.h"\r
30 #include "remote.h"\r
31 #include "message.h"\r
32 #include "dvbsubtitles.h"\r
33 #include "osdreceiver.h"\r
34 \r
35 #define USER_RESPONSE_TIME 500 // Milliseconds\r
36 \r
37 // ----------------------------------- Called from outside, one offs or info funcs\r
38 \r
39 Player::Player(MessageQueue* tmessageQueue, void* tmessageReceiver, OSDReceiver* tosdReceiver)\r
40 : vfeed(this), afeed(this), tfeed(this)\r
41 {\r
42   messageQueue = tmessageQueue;\r
43   messageReceiver = tmessageReceiver;\r
44   osdReceiver = tosdReceiver;\r
45   audio = Audio::getInstance();\r
46   video = Video::getInstance();\r
47   logger = Log::getInstance();\r
48   vdr = VDR::getInstance();\r
49   initted = false;\r
50   lengthBytes = 0;\r
51   lengthFrames = 0;\r
52   currentFrameNumber = 0;\r
53   state = S_STOP;\r
54   ifactor = 4;\r
55   is_pesrecording=true;\r
56 \r
57   subtitlesShowing = false;\r
58 \r
59   videoStartup = false;\r
60   threadBuffer = NULL;\r
61  \r
62   blockSize = 100000;\r
63   startupBlockSize = 250000;\r
64   video->turnVideoOn();\r
65 }\r
66 \r
67 Player::~Player()\r
68 {\r
69   if (initted) shutdown();\r
70 }\r
71 \r
72 int Player::init(bool p_isPesRecording,double framespersecond)\r
73 {\r
74   if (initted) return 0;\r
75 #ifndef WIN32\r
76   pthread_mutex_init(&mutex, NULL);\r
77 #else\r
78   mutex=CreateMutex(NULL,FALSE,NULL);\r
79 #endif\r
80   is_pesrecording = p_isPesRecording;\r
81   fps=framespersecond;\r
82   if (is_pesrecording)\r
83     demuxer = new DemuxerVDR();\r
84   else\r
85     demuxer = new DemuxerTS();\r
86   if (!demuxer) return 0;\r
87   subtitles = new DVBSubtitles(osdReceiver);\r
88   if (!subtitles) return 0;\r
89 \r
90   teletext = new TeletextDecoderVBIEBU();\r
91   if (!teletext) return 0;\r
92   teletext->setRecordigMode(true);\r
93   unsigned int demux_video_size=2097152;\r
94   unsigned int demux_audio_size=524288;\r
95   if (video->supportsh264()) {\r
96           demux_video_size*=5*2;//5;\r
97 \r
98   }\r
99   if (audio->maysupportAc3()) {\r
100           //demux_audio_size*=2;\r
101   }\r
102  \r
103   if (!demuxer->init(this, audio, video,teletext, demux_video_size,demux_audio_size,65536, framespersecond, subtitles))\r
104   {\r
105     logger->log("Player", Log::ERR, "Demuxer failed to init");\r
106     shutdown();\r
107     return 0;\r
108   }\r
109 \r
110   vfeed.init();\r
111   afeed.init();\r
112   tfeed.init();\r
113 \r
114   video->stop();\r
115   video->blank();\r
116   audio->stop();\r
117 \r
118   initted = true;\r
119   return 1;\r
120 }\r
121 \r
122 int Player::shutdown()\r
123 {\r
124   if (!initted) return 0;\r
125   switchState(S_STOP);\r
126   initted = false;\r
127 \r
128   delete demuxer;\r
129   demuxer = NULL;\r
130   delete subtitles;\r
131   subtitles = NULL;\r
132   delete teletext;\r
133   teletext = NULL;\r
134 \r
135 #ifdef WIN32\r
136   CloseHandle(mutex);\r
137 #endif\r
138 \r
139   return 1;\r
140 }\r
141 \r
142 void Player::setStartFrame(ULONG startFrame)\r
143 {\r
144   currentFrameNumber = startFrame;\r
145 }\r
146 \r
147 void Player::setLengthBytes(ULLONG length)\r
148 {\r
149   lengthBytes = length;\r
150   logger->log("Player", Log::DEBUG, "Player has received length bytes of %llu", lengthBytes);\r
151 }\r
152 \r
153 void Player::setLengthFrames(ULONG length)\r
154 {\r
155   lengthFrames = length;\r
156   logger->log("Player", Log::DEBUG, "Player has received length frames of %lu", lengthFrames);\r
157 }\r
158 \r
159 ULONG Player::getLengthFrames()\r
160 {\r
161   return lengthFrames;\r
162 }\r
163 \r
164 ULONG Player::getCurrentFrameNum()\r
165 {\r
166   if (startup) return 0;\r
167   switch(state)\r
168   {\r
169     case S_PLAY:\r
170     case S_PAUSE_P:\r
171       return demuxer->getFrameNumFromPTS(video->getCurrentTimestamp());\r
172     case S_PAUSE_I:\r
173     case S_FFWD:\r
174     case S_FBWD:\r
175       return currentFrameNumber;\r
176     default:\r
177       return 0; // shouldn't happen\r
178   }\r
179 }\r
180 \r
181 bool* Player::getDemuxerMpegAudioChannels()\r
182 {\r
183   return demuxer->getmpAudioChannels();\r
184 }\r
185 \r
186 bool* Player::getDemuxerAc3AudioChannels()\r
187 {\r
188   return demuxer->getac3AudioChannels();\r
189 }\r
190 \r
191 bool* Player::getDemuxerSubtitleChannels()\r
192 {\r
193   return demuxer->getSubtitleChannels();\r
194 }\r
195 \r
196 int Player::getCurrentAudioChannel()\r
197 {\r
198     if (is_pesrecording) {\r
199         return demuxer->getselAudioChannel();\r
200     } else {\r
201         return ((DemuxerTS*)demuxer)->getAID();\r
202     }\r
203 }\r
204 \r
205 int Player::getCurrentSubtitleChannel()\r
206 {\r
207     if (is_pesrecording) {\r
208         return demuxer->getselSubtitleChannel();\r
209     } else {\r
210         return ((DemuxerTS*)demuxer)->getSubID();\r
211     }\r
212 }\r
213 \r
214 void Player::setSubtitleChannel(int newChannel)\r
215 {\r
216     if (is_pesrecording) {\r
217          demuxer->setDVBSubtitleStream(newChannel);\r
218     } else {\r
219         ((DemuxerTS*)demuxer)->setSubID(newChannel);\r
220     }\r
221 }\r
222 \r
223 int *Player::getTeletxtSubtitlePages()\r
224 {\r
225     return teletext->getSubtitlePages();\r
226 }\r
227 \r
228 void Player::setAudioChannel(int newChannel, int type, int streamtype)\r
229 {\r
230     if (is_pesrecording) {\r
231         demuxer->setAudioStream(newChannel);\r
232         return;\r
233     } else {\r
234         ((DemuxerTS*)demuxer)->setAID(newChannel,type,streamtype);\r
235         return;\r
236     }\r
237 }\r
238 \r
239 bool Player::toggleSubtitles()\r
240 {\r
241   if (!subtitlesShowing)\r
242   {\r
243     subtitlesShowing = true;\r
244     subtitles->show();\r
245   }\r
246   else\r
247   {\r
248     subtitlesShowing = false;\r
249     subtitles->hide();\r
250   }\r
251   return subtitlesShowing;\r
252 }\r
253 \r
254 void  Player::turnSubtitlesOn(bool ison) {\r
255  if (ison)\r
256   {\r
257     subtitlesShowing = true;\r
258     subtitles->show();\r
259   }\r
260   else\r
261   {\r
262     subtitlesShowing = false;\r
263     subtitles->hide();\r
264   }\r
265 \r
266 }\r
267 \r
268 Channel * Player::getDemuxerChannel() {\r
269     if (!is_pesrecording) {\r
270         return ((DemuxerTS*) demuxer)->getChannelInfo();\r
271     } \r
272     return NULL; //Should not happen!\r
273 }\r
274 \r
275 \r
276 // ----------------------------------- Externally called events\r
277 \r
278 void Player::play()\r
279 {\r
280   if (!initted) return;\r
281   if (state == S_PLAY) return;\r
282   lock();\r
283 \r
284   bool doUnlock = false;\r
285   if (state == S_PAUSE_P) doUnlock = true;\r
286   switchState(S_PLAY);\r
287   if (doUnlock) unLock();\r
288 }\r
289 \r
290 void Player::playpause()\r
291 {\r
292   if (!initted) return;\r
293   lock();\r
294 \r
295   bool doUnlock = false;\r
296   if (state==S_PLAY) {\r
297           doUnlock=true;\r
298           switchState(S_PAUSE_P);\r
299   } else {\r
300           if (state == S_PAUSE_P) doUnlock = true;\r
301           switchState(S_PLAY);\r
302   }\r
303   if (doUnlock) unLock();\r
304 \r
305 }\r
306 \r
307 \r
308 \r
309 \r
310 void Player::stop()\r
311 {\r
312   if (!initted) return;\r
313   if (state == S_STOP) return;\r
314   lock();\r
315   logger->log("Player", Log::DEBUG, "Stop called lock");\r
316   switchState(S_STOP);\r
317   unLock();\r
318 }\r
319 \r
320 void Player::pause()\r
321 {\r
322   if (!initted) return;\r
323   lock();\r
324 \r
325   if ((state == S_FFWD) || (state == S_FBWD))\r
326   {\r
327     switchState(S_PAUSE_I);\r
328   }\r
329   else if ((state == S_PAUSE_I) || (state == S_PAUSE_P))\r
330   {\r
331     switchState(S_PLAY);\r
332   }\r
333   else\r
334   {\r
335     switchState(S_PAUSE_P);\r
336   }\r
337 \r
338   unLock();\r
339 }\r
340 \r
341 void Player::fastForward()\r
342 {\r
343   if (!initted) return;\r
344   lock();\r
345 \r
346   if (state == S_FFWD)\r
347   {\r
348     // change the rate\r
349     switch(ifactor)\r
350     {\r
351       case 4: ifactor = 8; break;\r
352       case 8: ifactor = 16; break;\r
353       case 16: ifactor = 32; break;\r
354       case 32: ifactor = 4; break;\r
355     }\r
356   }\r
357   else\r
358   {\r
359     ifactor = 4;\r
360     switchState(S_FFWD);\r
361   }\r
362   unLock();\r
363 }\r
364 \r
365 void Player::fastBackward()\r
366 {\r
367   if (!initted) return;\r
368   lock();\r
369 \r
370   if (state == S_FBWD)\r
371   {\r
372     // change the rate\r
373     switch(ifactor)\r
374     {\r
375       case 4: ifactor = 8; break;\r
376       case 8: ifactor = 16; break;\r
377       case 16: ifactor = 32; break;\r
378       case 32: ifactor = 4; break;\r
379     }\r
380   }\r
381   else\r
382   {\r
383     ifactor = 4;\r
384     switchState(S_FBWD);\r
385   }\r
386   unLock();\r
387 }\r
388 \r
389 void Player::jumpToPercent(double percent)\r
390 {\r
391   lock();\r
392   logger->log("Player", Log::DEBUG, "JUMP TO %f%%", percent);\r
393   ULONG newFrame = (ULONG)(percent * lengthFrames / 100);\r
394   switchState(S_JUMP, newFrame);\r
395 //  unLock(); - let thread unlock this\r
396 }\r
397 \r
398 void Player::jumpToMark(int mark)\r
399 {\r
400   lock();\r
401   logger->log("Player", Log::DEBUG, "JUMP TO MARK %i%%", mark);\r
402   switchState(S_JUMP, mark);\r
403 //  unLock(); - let thread unlock this\r
404 }\r
405 \r
406 void Player::jumpToFrameP(int newFrame)\r
407 {\r
408   lock();\r
409   logger->log("Player", Log::DEBUG, "JUMP TO FRAME AND PAUSE %i", newFrame);\r
410   switchState(S_JUMP_PI, newFrame);\r
411   unLock();\r
412 }\r
413 \r
414 void Player::skipForward(int seconds)\r
415 {\r
416   lock();\r
417   logger->log("Player", Log::DEBUG, "SKIP FORWARD %i SECONDS", seconds);\r
418   ULONG newFrame = getCurrentFrameNum();\r
419   if (newFrame == 0) { unLock(); return; } // Current pos from demuxer is not valid\r
420   newFrame +=(ULONG) (((double)seconds) * fps);\r
421   if (newFrame > lengthFrames) { switchState(S_PLAY); unLock(); }\r
422   else switchState(S_JUMP, newFrame);\r
423 //  unLock(); - let thread unlock this\r
424 }\r
425 \r
426 void Player::skipBackward(int seconds)\r
427 {\r
428   lock();\r
429   logger->log("Player", Log::DEBUG, "SKIP BACKWARD %i SECONDS", seconds);\r
430   long newFrame = getCurrentFrameNum();\r
431   if (newFrame == 0) { unLock(); return; } // Current pos from demuxer is not valid\r
432   newFrame -= (ULONG) (((double)seconds) * fps);\r
433   if (newFrame < 0) newFrame = 0;\r
434   switchState(S_JUMP, newFrame);\r
435 //  unLock(); - let thread unlock this\r
436 }\r
437 \r
438 // ----------------------------------- Implementations called events\r
439 \r
440 void Player::switchState(UCHAR toState, ULONG jumpFrame)\r
441 {\r
442   if (!initted) return;\r
443 \r
444   logger->log("Player", Log::DEBUG, "Switch state from %u to %u", state, toState);\r
445 \r
446   switch(state) // current state selector\r
447   {\r
448     case S_PLAY: // from S_PLAY -----------------------------------\r
449     {\r
450       switch(toState)\r
451       {\r
452         case S_PLAY: // to S_PLAY\r
453         {\r
454           return;\r
455         }\r
456         case S_PAUSE_P: // to S_PAUSE_P\r
457         {\r
458           video->pause();\r
459           audio->pause();\r
460           state = S_PAUSE_P;\r
461           return;\r
462         }\r
463         case S_PAUSE_I: // to S_PAUSE_I\r
464         {\r
465           // can't occur\r
466           return;\r
467         }\r
468         case S_FFWD: // to S_FFWD\r
469         {\r
470           currentFrameNumber = getCurrentFrameNum();\r
471           audio->systemMuteOn();\r
472           threadStop();\r
473           vfeed.stop();\r
474           afeed.stop();\r
475           tfeed.stop();\r
476           subtitles->stop();\r
477           demuxer->flush();\r
478           state = S_FFWD;\r
479           threadStart();\r
480           return;\r
481         }\r
482         case S_FBWD: // to S_FBWD\r
483         {\r
484           currentFrameNumber = getCurrentFrameNum();\r
485           audio->systemMuteOn();\r
486           threadStop();\r
487           vfeed.stop();\r
488           afeed.stop();\r
489           tfeed.stop();\r
490           subtitles->stop();\r
491           demuxer->flush();\r
492           state = S_FBWD;\r
493           threadStart();\r
494           return;\r
495         }\r
496         case S_STOP: // to S_STOP\r
497         {\r
498           vfeed.stop();\r
499           afeed.stop();\r
500           tfeed.stop();\r
501           subtitles->stop();\r
502           threadStop();\r
503           video->stop();\r
504           video->blank();\r
505           audio->stop();\r
506           audio->unPause();\r
507           video->reset();\r
508           demuxer->reset();\r
509           state = S_STOP;\r
510           return;\r
511         }\r
512         case S_JUMP: // to S_JUMP\r
513         {\r
514           restartAtFrame(jumpFrame);\r
515           return;\r
516         }\r
517         case S_JUMP_PI: // to S_JUMP_PI\r
518         {\r
519           audio->systemMuteOn();\r
520           threadStop();\r
521           vfeed.stop();\r
522           afeed.stop();\r
523           tfeed.stop();\r
524           subtitles->stop();\r
525           demuxer->flush();\r
526           state = S_PAUSE_I;\r
527           video->reset();\r
528           video->play();\r
529           restartAtFramePI(jumpFrame);\r
530           return;\r
531         }\r
532       }\r
533     }\r
534     case S_PAUSE_P: // from S_PAUSE_P -----------------------------------\r
535     {\r
536       switch(toState)\r
537       {\r
538         case S_PLAY: // to S_PLAY\r
539         {\r
540           video->unPause();\r
541           audio->unPause();\r
542           state = S_PLAY;\r
543           return;\r
544         }\r
545         case S_PAUSE_P: // to S_PAUSE_P\r
546         {\r
547           return;\r
548         }\r
549         case S_PAUSE_I: // to S_PAUSE_I\r
550         {\r
551           return;\r
552         }\r
553         case S_FFWD: // to S_FFWD\r
554         {\r
555           currentFrameNumber = getCurrentFrameNum();\r
556           audio->systemMuteOn();\r
557           vfeed.stop();\r
558           afeed.stop();\r
559           tfeed.stop();\r
560           subtitles->stop();\r
561           threadStop();\r
562           video->unPause();\r
563           audio->unPause();\r
564           state = S_FFWD;\r
565           threadStart();\r
566           return;\r
567         }\r
568         case S_FBWD: // to S_FBWD\r
569         {\r
570           currentFrameNumber = getCurrentFrameNum();\r
571           audio->systemMuteOn();\r
572           vfeed.stop();\r
573           afeed.stop();\r
574           tfeed.stop();\r
575           subtitles->stop();\r
576           threadStop();\r
577           video->unPause();\r
578           audio->unPause();\r
579           state = S_FBWD;\r
580           threadStart();\r
581           return;\r
582         }\r
583         case S_STOP: // to S_STOP\r
584         {\r
585           vfeed.stop();\r
586           afeed.stop();\r
587           tfeed.stop();\r
588           subtitles->stop();\r
589           threadStop();\r
590           video->stop();\r
591           video->blank();\r
592           audio->stop();\r
593           video->reset();\r
594           audio->unPause();\r
595           demuxer->reset();\r
596           audio->systemMuteOff();\r
597           state = S_STOP;\r
598           return;\r
599         }\r
600         case S_JUMP: // to S_JUMP\r
601         {\r
602           state = S_PLAY;\r
603           audio->systemMuteOn();\r
604           audio->unPause();\r
605           restartAtFrame(jumpFrame);\r
606           return;\r
607         }\r
608         case S_JUMP_PI: // to S_JUMP_PI\r
609         {\r
610           audio->systemMuteOn();\r
611           audio->unPause();\r
612           threadStop();\r
613           vfeed.stop();\r
614           afeed.stop();\r
615           tfeed.stop();\r
616           subtitles->stop();\r
617           demuxer->flush();\r
618           state = S_PAUSE_I;\r
619           video->reset();\r
620           video->play();\r
621           restartAtFramePI(jumpFrame);\r
622           return;\r
623         }\r
624       }\r
625     }\r
626     case S_PAUSE_I: // from S_PAUSE_I -----------------------------------\r
627     {\r
628       switch(toState)\r
629       {\r
630         case S_PLAY: // to S_PLAY\r
631         {\r
632           state = S_PLAY;\r
633           restartAtFrame(currentFrameNumber);\r
634           return;\r
635         }\r
636         case S_PAUSE_P: // to S_PAUSE_P\r
637         {\r
638           return;\r
639         }\r
640         case S_PAUSE_I: // to S_PAUSE_I\r
641         {\r
642           return;\r
643         }\r
644         case S_FFWD: // to S_FFWD\r
645         {\r
646           state = S_FFWD;\r
647           threadStart();\r
648           return;\r
649         }\r
650         case S_FBWD: // to S_FBWD\r
651         {\r
652           state = S_FBWD;\r
653           threadStart();\r
654           return;\r
655         }\r
656         case S_STOP: // to S_STOP\r
657         {\r
658           video->stop();\r
659           video->blank();\r
660           audio->stop();\r
661           video->reset();\r
662           demuxer->reset();\r
663           audio->systemMuteOff();\r
664           state = S_STOP;\r
665           return;\r
666         }\r
667         case S_JUMP: // to S_JUMP\r
668         {\r
669           state = S_PLAY;\r
670           restartAtFrame(jumpFrame);\r
671           return;\r
672         }\r
673         case S_JUMP_PI: // to S_JUMP_PI\r
674         {\r
675           restartAtFramePI(jumpFrame);\r
676           return;\r
677         }\r
678       }\r
679     }\r
680     case S_FFWD: // from S_FFWD -----------------------------------\r
681     {\r
682       switch(toState)\r
683       {\r
684         case S_PLAY: // to S_PLAY\r
685         {\r
686           state = S_PLAY;\r
687           ULONG stepback = (ULONG)(((double)USER_RESPONSE_TIME * ifactor) * fps / 1000.);\r
688           if (stepback < currentFrameNumber)\r
689             currentFrameNumber -= stepback;\r
690           else\r
691             currentFrameNumber = 0;\r
692           restartAtFrame(currentFrameNumber);\r
693           return;\r
694         }\r
695         case S_PAUSE_P: // to S_PAUSE_P\r
696         {\r
697           // can't occur\r
698           return;\r
699         }\r
700         case S_PAUSE_I: // to S_PAUSE_I\r
701         {\r
702           threadStop();\r
703           state = S_PAUSE_I;\r
704           return;\r
705         }\r
706         case S_FFWD: // to S_FFWD\r
707         {\r
708           return;\r
709         }\r
710         case S_FBWD: // to S_FBWD\r
711         {\r
712           threadStop();\r
713           state = S_FBWD;\r
714           threadStart();\r
715           return;\r
716         }\r
717         case S_STOP: // to S_STOP\r
718         {\r
719           threadStop();\r
720           video->stop();\r
721           video->blank();\r
722           audio->stop();\r
723           video->reset();\r
724           demuxer->reset();\r
725           state = S_STOP;\r
726           return;\r
727         }\r
728         case S_JUMP: // to S_JUMP\r
729         {\r
730           state = S_PLAY;\r
731           restartAtFrame(jumpFrame);\r
732           return;\r
733         }\r
734         case S_JUMP_PI: // to S_JUMP_PI\r
735         {\r
736           threadStop();\r
737           state = S_PAUSE_I;\r
738           restartAtFramePI(jumpFrame);\r
739           return;\r
740         }\r
741       }\r
742     }\r
743     case S_FBWD: // from S_FBWD -----------------------------------\r
744     {\r
745       switch(toState)\r
746       {\r
747         case S_PLAY: // to S_PLAY\r
748         {\r
749           state = S_PLAY;\r
750           restartAtFrame(currentFrameNumber);\r
751           return;\r
752         }\r
753         case S_PAUSE_P: // to S_PAUSE_P\r
754         {\r
755           // can't occur\r
756           return;\r
757         }\r
758         case S_PAUSE_I: // to S_PAUSE_I\r
759         {\r
760           threadStop();\r
761           state = S_PAUSE_I;\r
762           return;\r
763         }\r
764         case S_FFWD: // to S_FFWD\r
765         {\r
766           threadStop();\r
767           state = S_FFWD;\r
768           threadStart();\r
769           return;\r
770         }\r
771         case S_FBWD: // to S_FBWD\r
772         {\r
773           return;\r
774         }\r
775         case S_STOP: // to S_STOP\r
776         {\r
777           threadStop();\r
778           video->stop();\r
779           video->blank();\r
780           audio->stop();\r
781           video->reset();\r
782           demuxer->reset();\r
783           state = S_STOP;\r
784           return;\r
785         }\r
786         case S_JUMP: // to S_JUMP\r
787         {\r
788           state = S_PLAY;\r
789           restartAtFrame(jumpFrame);\r
790           return;\r
791         }\r
792         case S_JUMP_PI: // to S_JUMP_PI\r
793         {\r
794           threadStop();\r
795           state = S_PAUSE_I;\r
796           restartAtFramePI(jumpFrame);\r
797           return;\r
798         }\r
799       }\r
800     }\r
801     case S_STOP: // from S_STOP -----------------------------------\r
802     {\r
803       switch(toState)\r
804       {\r
805         case S_PLAY: // to S_PLAY\r
806         {\r
807           startup = true;\r
808 \r
809           audio->reset();\r
810           audio->setStreamType(Audio::MPEG2_PES);\r
811           audio->systemMuteOff();\r
812           video->reset();\r
813           demuxer->reset();\r
814           // FIXME use restartAtFrame here?\r
815           if (currentFrameNumber > lengthFrames) currentFrameNumber = 0;\r
816           demuxer->setFrameNum(currentFrameNumber);\r
817           demuxer->seek();\r
818           videoStartup = true;\r
819           state = S_PLAY;\r
820           threadStart();\r
821           logger->log("Player", Log::DEBUG, "Immediate play");\r
822           afeed.start();\r
823           vfeed.start();\r
824           tfeed.start();\r
825           subtitles->start();\r
826           video->sync();\r
827           audio->sync();\r
828           audio->play();\r
829           video->pause();\r
830           return;\r
831         }\r
832         case S_PAUSE_P: // to S_PAUSE_P\r
833         {\r
834           return;\r
835         }\r
836         case S_PAUSE_I: // to S_PAUSE_I\r
837         {\r
838           return;\r
839         }\r
840         case S_FFWD: // to S_FFWD\r
841         {\r
842           return;\r
843         }\r
844         case S_FBWD: // to S_FBWD\r
845         {\r
846           return;\r
847         }\r
848         case S_STOP: // to S_STOP\r
849         {\r
850           return;\r
851         }\r
852         case S_JUMP: // to S_JUMP\r
853         {\r
854           return;\r
855         }\r
856         case S_JUMP_PI: // to S_JUMP_PI\r
857         {\r
858           return;\r
859         }\r
860       }\r
861     }\r
862     // case S_JUMP cannot be a start state because it auto flips to play\r
863     // case S_JUMP_PI cannot be a start state because it auto flips to S_PAUSE_I\r
864   }\r
865 }\r
866 \r
867 // ----------------------------------- Internal functions\r
868 \r
869 void Player::lock()\r
870 {\r
871 #ifndef WIN32\r
872   pthread_mutex_lock(&mutex);\r
873   logger->log("Player", Log::DEBUG, "LOCKED");\r
874 \r
875 #else\r
876    WaitForSingleObject(mutex, INFINITE);\r
877 #endif\r
878 }\r
879 \r
880 void Player::unLock()\r
881 {\r
882 #ifndef WIN32\r
883   logger->log("Player", Log::DEBUG, "UNLOCKING");\r
884   pthread_mutex_unlock(&mutex);\r
885 #else\r
886    ReleaseMutex(mutex);\r
887 #endif\r
888 }\r
889 \r
890 void Player::restartAtFrame(ULONG newFrame)\r
891 {\r
892   vfeed.stop();\r
893   afeed.stop();\r
894   tfeed.stop();\r
895   subtitles->stop();\r
896   threadStop();\r
897   video->stop();\r
898   video->reset();\r
899   audio->reset();\r
900   audio->setStreamType(Audio::MPEG2_PES);\r
901   demuxer->flush();\r
902   demuxer->seek();\r
903   currentFrameNumber = newFrame;\r
904   demuxer->setFrameNum(newFrame);\r
905   videoStartup = true;\r
906   afeed.start();\r
907   tfeed.start();\r
908   vfeed.start();\r
909   subtitles->start();\r
910   threadStart();\r
911   audio->play();\r
912   video->sync();\r
913   audio->sync();\r
914   audio->systemMuteOff();\r
915   audio->doMuting();\r
916 }\r
917 \r
918 \r
919 void Player::restartAtFramePI(ULONG newFrame)\r
920 {\r
921   ULLONG filePos;\r
922   ULONG nextiframeNumber;\r
923   ULONG iframeLength;\r
924   ULONG iframeNumber;\r
925 \r
926   UCHAR* buffer;\r
927   UINT amountReceived;\r
928   UINT videoLength;\r
929 \r
930   // newFrame could be anywhere, go forwards to next I-Frame\r
931   if (!vdr->getNextIFrame(newFrame, 1, &filePos, &nextiframeNumber, &iframeLength)) return;\r
932 \r
933   // Now step back a GOP. This ensures we go to the greatest I-Frame equal to or less than the requested frame\r
934   vdr->getNextIFrame(nextiframeNumber, 0, &filePos, &iframeNumber, &iframeLength);\r
935 \r
936   buffer = vdr->getBlock(filePos, iframeLength, &amountReceived);\r
937   if (!vdr->isConnected())\r
938   {\r
939     if (buffer) free(buffer);\r
940     doConnectionLost();\r
941   }\r
942   else\r
943   {\r
944     videoLength = demuxer->stripAudio(buffer, amountReceived);\r
945     video->displayIFrame(buffer, videoLength);\r
946     video->displayIFrame(buffer, videoLength); // If you do it twice, it works :)\r
947     free(buffer);\r
948     currentFrameNumber = iframeNumber;\r
949   }\r
950 }\r
951 \r
952 void Player::doConnectionLost()\r
953 {\r
954   logger->log("Player", Log::DEBUG, "Connection lost, sending message");\r
955   Message* m = new Message();\r
956   m->to = messageReceiver;\r
957   m->from = this;\r
958   m->message = Message::PLAYER_EVENT;\r
959   m->parameter = Player::CONNECTION_LOST;\r
960   messageQueue->postMessage(m);\r
961 }\r
962 \r
963 // ----------------------------------- Callback\r
964 \r
965 void Player::call(void* caller)\r
966 {\r
967   if (caller == demuxer)\r
968   {\r
969     logger->log("Player", Log::DEBUG, "Callback from demuxer");\r
970 \r
971     if (video->getTVsize() == Video::ASPECT4X3)\r
972     {\r
973       logger->log("Player", Log::DEBUG, "TV is 4:3, ignoring aspect switching");\r
974       return;\r
975     }\r
976 \r
977     int dxCurrentAspect = demuxer->getAspectRatio();\r
978     if (dxCurrentAspect == Demuxer::ASPECT_4_3)\r
979     {\r
980       logger->log("Player", Log::DEBUG, "Demuxer said video is 4:3 aspect, switching TV");\r
981       video->setAspectRatio(Video::ASPECT4X3);\r
982 \r
983       Message* m = new Message();\r
984       m->from = this;\r
985       m->to = messageReceiver;\r
986       m->message = Message::PLAYER_EVENT;\r
987       m->parameter = Player::ASPECT43;\r
988       messageQueue->postMessageFromOuterSpace(m);\r
989     }\r
990     else if (dxCurrentAspect == Demuxer::ASPECT_16_9)\r
991     {\r
992       logger->log("Player", Log::DEBUG, "Demuxer said video is 16:9 aspect, switching TV");\r
993       video->setAspectRatio(Video::ASPECT16X9);\r
994 \r
995       Message* m = new Message();\r
996       m->from = this;\r
997       m->to = messageReceiver;\r
998       m->message = Message::PLAYER_EVENT;\r
999       m->parameter = Player::ASPECT169;\r
1000       messageQueue->postMessageFromOuterSpace(m);\r
1001     }\r
1002     else\r
1003     {\r
1004       logger->log("Player", Log::DEBUG, "Demuxer said video is something else... ignoring");\r
1005     }\r
1006 \r
1007   }\r
1008   else\r
1009   {\r
1010     if (videoStartup)\r
1011     {\r
1012       videoStartup = false;\r
1013       video->reset();\r
1014       video->play();\r
1015       video->sync();\r
1016       vfeed.release();\r
1017       unLock();\r
1018     }\r
1019 \r
1020     threadSignalNoLock();\r
1021   }\r
1022 }\r
1023 \r
1024 // ----------------------------------- Feed thread\r
1025 \r
1026 void Player::threadMethod()\r
1027 {\r
1028   // this method used to be simple, the only thing it does\r
1029   // is farm out to threadFeed Live/Play/Scan\r
1030   // All the guff is to support scan hitting one end\r
1031 \r
1032   if ((state == S_FFWD) || (state == S_FBWD))\r
1033   {\r
1034     if (video->PTSIFramePlayback()) threadPTSFeedScan();\r
1035     else threadFeedScan();\r
1036     // if this returns then scan hit one end\r
1037     if (state == S_FFWD) // scan hit the end. stop\r
1038     {\r
1039       threadCheckExit();\r
1040       Message* m = new Message(); // Must be done after this thread finishes, and must break into master mutex\r
1041       m->to = messageReceiver;\r
1042       m->from = this;\r
1043       m->message = Message::PLAYER_EVENT;\r
1044       m->parameter = STOP_PLAYBACK;\r
1045       logger->log("Player", Log::DEBUG, "Posting message to %p...", messageQueue);\r
1046       messageQueue->postMessage(m);\r
1047       logger->log("Player", Log::DEBUG, "Message posted...");\r
1048       return;\r
1049     }\r
1050     // if execution gets to here, threadFeedScan hit the start, go to play mode\r
1051     state = S_PLAY;\r
1052     audio->reset();\r
1053     audio->setStreamType(Audio::MPEG2_PES);\r
1054     demuxer->flush();\r
1055     demuxer->seek();\r
1056     demuxer->setFrameNum(currentFrameNumber);\r
1057     videoStartup = true;\r
1058     afeed.start();\r
1059     tfeed.start();\r
1060     vfeed.start();\r
1061     subtitles->start();\r
1062     audio->play();\r
1063     audio->sync();\r
1064     audio->systemMuteOff();\r
1065     audio->doMuting();\r
1066   }\r
1067 \r
1068   if (state == S_PLAY) threadFeedPlay();\r
1069 }\r
1070 \r
1071 void Player::threadFeedPlay()\r
1072 {\r
1073   ULLONG feedPosition;\r
1074   UINT thisRead, writeLength, thisWrite, askFor;\r
1075   time_t lastRescan = time(NULL);\r
1076 \r
1077   feedPosition = vdr->positionFromFrameNumber(currentFrameNumber);\r
1078   if (!vdr->isConnected()) { doConnectionLost(); return; }\r
1079   logger->log("Player", Log::DEBUG, "startFeedPlay: wantedframe %i goto %llu", currentFrameNumber, feedPosition);\r
1080 \r
1081 \r
1082   while(1)\r
1083   {\r
1084     thisRead = 0;\r
1085     writeLength = 0;\r
1086     thisWrite = 0;\r
1087 \r
1088     threadCheckExit();\r
1089 \r
1090     // If we havn't rescanned for a while..\r
1091     if ((lastRescan + 60) < time(NULL))\r
1092     {\r
1093       lengthBytes = vdr->rescanRecording(&lengthFrames);\r
1094       if (!vdr->isConnected()) { doConnectionLost(); return; }\r
1095       logger->log("Player", Log::DEBUG, "Rescanned and reset length: %llu", lengthBytes);\r
1096       lastRescan = time(NULL);\r
1097     }\r
1098 \r
1099     if (feedPosition >= lengthBytes) break;  // finished playback\r
1100 \r
1101     if (startup)\r
1102     {\r
1103       if (startupBlockSize > lengthBytes)\r
1104         askFor = lengthBytes; // is a very small recording!\r
1105       else\r
1106         askFor = startupBlockSize; // normal, but a startup sized block to detect all the audio streams\r
1107     }\r
1108     else\r
1109     {\r
1110       if ((feedPosition + blockSize) > lengthBytes) // last block of recording\r
1111         askFor = lengthBytes - feedPosition;\r
1112       else // normal\r
1113         askFor = blockSize;\r
1114     }\r
1115     //logger->log("Player", Log::DEBUG, "Get Block in");\r
1116 \r
1117     threadBuffer = vdr->getBlock(feedPosition, askFor, &thisRead);\r
1118     //logger->log("Player", Log::DEBUG, "Get Block out");\r
1119 \r
1120     feedPosition += thisRead;\r
1121 \r
1122     if (!vdr->isConnected())\r
1123     {\r
1124       doConnectionLost();\r
1125       return;\r
1126     }\r
1127 \r
1128     if (!threadBuffer) break;\r
1129 \r
1130     if (startup)\r
1131     {\r
1132       int a_stream = demuxer->scan(threadBuffer, thisRead);\r
1133       demuxer->setAudioStream(a_stream);\r
1134       logger->log("Player", Log::DEBUG, "Startup Audio stream chosen %x", a_stream);\r
1135       startup = false;\r
1136     }\r
1137 \r
1138     threadCheckExit();\r
1139 \r
1140     while(writeLength < thisRead)\r
1141     {\r
1142       //logger->log("Player", Log::DEBUG, "Put in");\r
1143       thisWrite = demuxer->put(threadBuffer + writeLength, thisRead - writeLength);\r
1144       //logger->log("Player", Log::DEBUG, "Put out");\r
1145       writeLength += thisWrite;\r
1146 \r
1147       if (!thisWrite)\r
1148       {\r
1149         // demuxer is full and can't take anymore\r
1150         threadLock();\r
1151         threadWaitForSignal();\r
1152         threadUnlock();\r
1153       }\r
1154 \r
1155       threadCheckExit();\r
1156     }\r
1157 \r
1158     free(threadBuffer);\r
1159     threadBuffer = NULL;\r
1160 \r
1161   }\r
1162 \r
1163   // end of recording\r
1164   logger->log("Player", Log::DEBUG, "Recording playback ends");\r
1165 \r
1166   if (videoStartup) // oh woe. there never was a stream, I was conned!\r
1167   {\r
1168     videoStartup = false;\r
1169     unLock();\r
1170     MILLISLEEP(500); // I think this will solve a race\r
1171   }\r
1172 \r
1173   threadCheckExit();\r
1174 \r
1175 \r
1176   Message* m = new Message(); // Must be done after this thread finishes, and must break into master mutex\r
1177   m->to = messageReceiver;\r
1178   m->from = this;\r
1179   m->message = Message::PLAYER_EVENT;\r
1180   m->parameter = Player::STOP_PLAYBACK;\r
1181   logger->log("Player", Log::DEBUG, "Posting message to %p...", messageQueue);\r
1182   messageQueue->postMessage(m);\r
1183 }\r
1184 \r
1185 \r
1186 void Player::threadPTSFeedScan()\r
1187 {\r
1188   // This method manipulates the PTS instead of waiting, this is for the android devices, maybe later for windows?\r
1189 \r
1190   ULONG direction = 0;\r
1191   int dir_fac=-1;\r
1192   ULONG baseFrameNumber = 0;\r
1193   ULONG iframeNumber = 0;\r
1194   ULONG iframeLength = 0;\r
1195   ULONG currentfeedFrameNumber=currentFrameNumber;\r
1196   ULONG firstFrameNumber=currentFrameNumber;\r
1197   ULLONG filePos;\r
1198   UINT amountReceived;\r
1199   UINT videoLength;\r
1200 \r
1201   UINT playtime=0;\r
1202 \r
1203 #ifndef WIN32\r
1204   struct timeval clock0 = {0,0};  // Time stamp after fetching I-frame info\r
1205   struct timeval clock1 = {0,0};  // Time stamp after fetching I-frame data\r
1206   struct timeval clock2 = {0,0} ; // Time stamp after displaying I-frame\r
1207 #else\r
1208   DWORD clock0 = 0, clock1 = 0, clock2 = 0;\r
1209 #endif\r
1210 \r
1211   int frameTimeOffset = 0; // Time in msec between frames\r
1212   int disp_msec = 0;  // Time taken to display data\r
1213   int total_msec = 0; // Time taken to fetch data and display it\r
1214   int sleepTime = 0;\r
1215 \r
1216   if (state == S_FFWD) {\r
1217           direction = 1; // and 0 for backward\r
1218           dir_fac=1;\r
1219   }\r
1220   video->EnterIframePlayback();\r
1221 \r
1222   while(1)\r
1223   {\r
1224     // Fetch I-frames until we get one that can be displayed in good time\r
1225     // Repeat while clock0 + total_msec > clock2 + frameTimeOffset\r
1226 \r
1227     baseFrameNumber = currentfeedFrameNumber;\r
1228 \r
1229     threadCheckExit();\r
1230     if (!vdr->getNextIFrame(baseFrameNumber, direction, &filePos, &iframeNumber, &iframeLength))\r
1231        return;\r
1232 \r
1233     if (iframeNumber >= lengthFrames) return;\r
1234         // scan has got to the end of what we knew to be there before we started scanning\r
1235 \r
1236     baseFrameNumber = iframeNumber;\r
1237 \r
1238     frameTimeOffset =(int) ((double)(abs((int)iframeNumber - (int)currentfeedFrameNumber) * 1000) / (fps * (double)ifactor));\r
1239 \r
1240     logger->log("Player", Log::DEBUG, "XXX Got frame");\r
1241 \r
1242     threadBuffer = vdr->getBlock(filePos, iframeLength, &amountReceived);\r
1243 \r
1244     if (!vdr->isConnected())\r
1245     {\r
1246       if (threadBuffer) free(threadBuffer);\r
1247       doConnectionLost();\r
1248       break;\r
1249     }\r
1250 \r
1251 \r
1252     threadCheckExit();\r
1253 \r
1254 \r
1255     videoLength = demuxer->stripAudio(threadBuffer, amountReceived);\r
1256     demuxer->changeTimes(threadBuffer,videoLength,playtime);\r
1257     int count=0;\r
1258     while (!video->displayIFrame(threadBuffer, videoLength)) // the device might block\r
1259     {\r
1260         MILLISLEEP(20);\r
1261         threadCheckExit();\r
1262         count++;\r
1263         if (count%300==0) {\r
1264                 ULLONG cur_time=video->getCurrentTimestamp();\r
1265         //       logger->log("Player", Log::ERR, "FFN: %d CFN: %d dirfac %d time %lld ifac %d fps %g",\r
1266                 //              firstFrameNumber, currentFrameNumber,dir_fac,cur_time,ifactor,fps);\r
1267                 if (cur_time!=0) {\r
1268                     currentFrameNumber=firstFrameNumber+(int)(((double)(dir_fac*((long long)cur_time)*((int)ifactor))*fps/90000.));\r
1269                 }\r
1270         }\r
1271 \r
1272     }\r
1273     playtime +=frameTimeOffset;\r
1274     currentfeedFrameNumber = iframeNumber;\r
1275     {\r
1276                 ULLONG cur_time=video->getCurrentTimestamp();\r
1277         //       logger->log("Player", Log::ERR, "FFN: %d CFN: %d dirfac %d time %lld ifac %d fps %g",\r
1278                 //              firstFrameNumber, currentFrameNumber,dir_fac,cur_time,ifactor,fps);\r
1279                 if (cur_time!=0) {\r
1280                     currentFrameNumber=firstFrameNumber+(int)(((double)(dir_fac*((long long)cur_time)*((int)ifactor))*fps/90000.));\r
1281                 }\r
1282         }\r
1283 \r
1284     free(threadBuffer);\r
1285     threadBuffer = NULL;\r
1286   }\r
1287 }\r
1288 \r
1289 \r
1290 \r
1291 void Player::threadFeedScan()\r
1292 {\r
1293   // This method is actually really simple - get frame from vdr,\r
1294   // spit it at the video chip, wait for a time. Most of the code here\r
1295   // is to get the wait right so that the scan occurs at the correct rate.\r
1296 \r
1297   ULONG direction = 0;\r
1298   ULONG baseFrameNumber = 0;\r
1299   ULONG iframeNumber = 0;\r
1300   ULONG iframeLength = 0;\r
1301   ULLONG filePos;\r
1302   UINT amountReceived;\r
1303   UINT videoLength;\r
1304 \r
1305 #ifndef WIN32\r
1306   struct timeval clock0 = {0,0};  // Time stamp after fetching I-frame info\r
1307   struct timeval clock1 = {0,0};  // Time stamp after fetching I-frame data\r
1308   struct timeval clock2 = {0,0} ; // Time stamp after displaying I-frame\r
1309 #else\r
1310   DWORD clock0 = 0, clock1 = 0, clock2 = 0;\r
1311 #endif\r
1312 \r
1313   int frameTimeOffset = 0; // Time in msec between frames\r
1314   int disp_msec = 0;  // Time taken to display data\r
1315   int total_msec = 0; // Time taken to fetch data and display it\r
1316   int sleepTime = 0;\r
1317 \r
1318   if (state == S_FFWD) direction = 1; // and 0 for backward\r
1319 \r
1320   while(1)\r
1321   {\r
1322     // Fetch I-frames until we get one that can be displayed in good time\r
1323     // Repeat while clock0 + total_msec > clock2 + frameTimeOffset\r
1324 \r
1325     baseFrameNumber = currentFrameNumber;\r
1326     do\r
1327     {\r
1328       threadCheckExit();\r
1329       if (!vdr->getNextIFrame(baseFrameNumber, direction, &filePos, &iframeNumber, &iframeLength))\r
1330         return;\r
1331 \r
1332       if (iframeNumber >= lengthFrames) return;\r
1333         // scan has got to the end of what we knew to be there before we started scanning\r
1334 \r
1335       baseFrameNumber = iframeNumber;\r
1336       frameTimeOffset =(int) ((double)(abs((int)iframeNumber - (int)currentFrameNumber) * 1000) / (fps * (double)ifactor));\r
1337 #ifndef WIN32\r
1338       gettimeofday(&clock0, NULL);\r
1339 #else\r
1340       clock0 = timeGetTime();\r
1341 #endif\r
1342     }\r
1343 #ifndef WIN32\r
1344     while (clock2.tv_sec != 0 &&\r
1345           (clock0.tv_sec - clock2.tv_sec) * 1000 +\r
1346           (clock0.tv_usec - clock2.tv_usec) / 1000 > frameTimeOffset - total_msec);\r
1347 #else\r
1348     while (clock2 != 0 && clock0 + total_msec > clock2 + frameTimeOffset);\r
1349 #endif\r
1350     logger->log("Player", Log::DEBUG, "XXX Got frame");\r
1351 \r
1352     threadBuffer = vdr->getBlock(filePos, iframeLength, &amountReceived);\r
1353     \r
1354     if (!vdr->isConnected())\r
1355     {\r
1356       if (threadBuffer) free(threadBuffer);\r
1357       doConnectionLost();\r
1358       break;\r
1359     }\r
1360     \r
1361 #ifndef WIN32\r
1362     gettimeofday(&clock1, NULL);\r
1363     if (clock2.tv_sec != 0)\r
1364       sleepTime = (clock2.tv_sec - clock1.tv_sec) * 1000\r
1365                 + (clock2.tv_usec - clock1.tv_usec) / 1000\r
1366                 + frameTimeOffset - disp_msec;\r
1367 #else\r
1368     clock1 = timeGetTime();\r
1369     if (clock2 != 0)\r
1370       sleepTime = clock2 + frameTimeOffset - disp_msec - clock1;\r
1371 #endif\r
1372     if (sleepTime < 0) sleepTime = 0;\r
1373     threadCheckExit();\r
1374     MILLISLEEP(sleepTime);\r
1375    logger->log("Player", Log::DEBUG, "XXX Slept for %d", sleepTime);\r
1376 \r
1377     videoLength = demuxer->stripAudio(threadBuffer, amountReceived);\r
1378     video->displayIFrame(threadBuffer, videoLength);\r
1379     currentFrameNumber = iframeNumber;\r
1380     free(threadBuffer);\r
1381     threadBuffer = NULL;\r
1382 \r
1383 #ifndef WIN32\r
1384     gettimeofday(&clock2, NULL);\r
1385     total_msec = (clock2.tv_sec - clock0.tv_sec) * 1000\r
1386                + (clock2.tv_usec - clock0.tv_usec) / 1000\r
1387                - sleepTime;\r
1388     disp_msec = (clock2.tv_sec - clock1.tv_sec) * 1000\r
1389               + (clock2.tv_usec - clock1.tv_usec) / 1000\r
1390               - sleepTime;\r
1391 #else\r
1392     clock2 = timeGetTime();\r
1393     total_msec = clock2 - clock0 - sleepTime;\r
1394     disp_msec = clock2 - clock1 - sleepTime;\r
1395 #endif\r
1396     logger->log("Player", Log::DEBUG, "XXX disp_msec = %4d total_msec = %4d", disp_msec, total_msec);\r
1397   }\r
1398 }\r
1399 \r
1400 void Player::threadPostStopCleanup()\r
1401 {\r
1402   if (threadBuffer)\r
1403   {\r
1404     free(threadBuffer);\r
1405     threadBuffer = NULL;\r
1406   }\r
1407 }\r
1408 \r
1409 // ----------------------------------- Dev\r
1410 \r
1411 #ifdef DEV\r
1412 void Player::test1()\r
1413 {\r
1414   logger->log("Player", Log::DEBUG, "PLAYER TEST 1");\r
1415 }\r
1416 \r
1417 void Player::test2()\r
1418 {\r
1419   logger->log("Player", Log::DEBUG, "PLAYER TEST 2");\r
1420 }\r
1421 #endif\r