]> git.vomp.tv Git - vompclient.git/blob - playerradio.cc
Some radio playback support
[vompclient.git] / playerradio.cc
1 /*
2     Copyright 2004-2006 Chris Tallon
3
4     This file is part of VOMP.
5
6     VOMP is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     VOMP is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with VOMP; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "playerradio.h"
22
23 // ----------------------------------- Called from outside, one offs or info funcs
24
25 PlayerRadio::PlayerRadio(MessageQueue* tmessageQueue, void* tmessageReceiver, bool tIsRecording)
26 : afeed(this)
27 {
28   messageQueue = tmessageQueue;
29   messageReceiver = tmessageReceiver;
30   audio = Audio::getInstance();
31   logger = Log::getInstance();
32   vdr = VDR::getInstance();
33   initted = false;
34   lengthBytes = 0;
35   lengthFrames = 0;
36   streamPos = 0;
37   state = S_STOP;
38
39   startPTS = 0;
40   lengthSeconds = 0;
41
42   isRecording = tIsRecording;
43
44   threadBuffer = NULL;
45
46   blockSize = 10000;
47   startupBlockSize = 20000;
48   preBufferSize = 20000;
49 }
50
51 PlayerRadio::~PlayerRadio()
52 {
53   if (initted) shutdown();
54 }
55
56 int PlayerRadio::init(ULLONG tlengthBytes, ULONG tlengthFrames)
57 {
58   if (initted) return 0;
59 #ifndef WIN32
60   pthread_mutex_init(&mutex, NULL);
61 #else
62   mutex=CreateMutex(NULL,FALSE,NULL);
63 #endif
64
65   demuxer = new DemuxerVDR();
66   if (!demuxer) return 0;
67
68   if (!demuxer->init(this, audio, NULL))
69   {
70     logger->log("PlayerRadio", Log::ERR, "Demuxer failed to init");
71     shutdown();
72     return 0;
73   }
74
75   afeed.init();
76   audio->stop();
77
78   lengthBytes = tlengthBytes;
79   lengthFrames = tlengthFrames;
80
81   logger->log("PlayerRadio", Log::DEBUG, "PlayerRadio has received length bytes of %llu", lengthBytes);
82
83   UINT thisRead = 0;
84   ULLONG endPTS = 0;
85   int success;
86
87   threadBuffer = vdr->getBlock(0, 10000, &thisRead);
88   if (!threadBuffer)
89   {
90     logger->log("PlayerRadio", Log::ERR, "Failed to get start block");
91     shutdown();
92     return 0;
93   }
94
95   success = demuxer->findPTS(threadBuffer, thisRead, &startPTS);
96   if (!success)
97   {
98     logger->log("PlayerRadio", Log::ERR, "Failed to get start PTS");
99     shutdown();
100     return 0;
101   }
102
103   threadBuffer = vdr->getBlock(tlengthBytes - 10000, 10000, &thisRead);
104   if (!threadBuffer)
105   {
106     logger->log("PlayerRadio", Log::ERR, "Failed to get end block");
107     shutdown();
108     return 0;
109   }
110
111   success = demuxer->findPTS(threadBuffer, thisRead, &endPTS);
112   if (!success)
113   {
114     logger->log("PlayerRadio", Log::ERR, "Failed to get end PTS");
115     shutdown();
116     return 0;
117   }
118
119   logger->log("PlayerRadio", Log::DEBUG, "Start: %llu, End: %llu", startPTS, endPTS);
120
121   if (startPTS < endPTS)
122   {
123     lengthSeconds = (endPTS - startPTS) / 90000;
124   }
125   else
126   {
127     lengthSeconds = (startPTS - endPTS) / 90000;
128   }
129
130   initted = true;
131   return 1;
132 }
133
134 int PlayerRadio::shutdown()
135 {
136   if (!initted) return 0;
137   switchState(S_STOP);
138   initted = false;
139
140   delete demuxer;
141   demuxer = NULL;
142
143 #ifdef WIN32
144   CloseHandle(mutex);
145 #endif
146
147   return 1;
148 }
149
150
151 void PlayerRadio::setStartBytes(ULLONG startBytes)
152 {
153   streamPos = startBytes;
154 }
155
156 ULONG PlayerRadio::getLengthSeconds()
157 {
158   return lengthSeconds;
159 }
160
161 ULONG PlayerRadio::getCurrentSeconds()
162 {
163   if (startup) return 0;
164   return 0;
165 }
166
167 // ----------------------------------- Externally called events
168
169 void PlayerRadio::play()
170 {
171   if (!initted) return;
172   if (state == S_PLAY) return;
173   lock();
174   switchState(S_PLAY);
175   unLock();
176 }
177
178 void PlayerRadio::stop()
179 {
180   if (!initted) return;
181   if (state == S_STOP) return;
182   lock();
183   logger->log("PlayerRadio", Log::DEBUG, "Stop called lock");
184   switchState(S_STOP);
185   unLock();
186 }
187
188 void PlayerRadio::pause()
189 {
190   if (!initted) return;
191   lock();
192
193   if (state == S_PAUSE_P)
194   {
195     switchState(S_PLAY);
196   }
197   else
198   {
199     switchState(S_PAUSE_P);
200   }
201
202   unLock();
203 }
204
205 void PlayerRadio::jumpToPercent(int percent)
206 {
207   lock();
208   logger->log("PlayerRadio", Log::DEBUG, "JUMP TO %i%%", percent);
209   ULONG newFrame = percent * lengthFrames / 100;
210   switchState(S_JUMP, newFrame);
211   unLock();
212 }
213
214 void PlayerRadio::skipForward(int seconds)
215 {
216   lock();
217   logger->log("PlayerRadio", Log::DEBUG, "SKIP FORWARD %i SECONDS", seconds);
218   ULONG newFrame = getCurrentSeconds();
219   if (newFrame == 0) { unLock(); return; } // Current pos from demuxer is not valid
220 //  newFrame += seconds * video->getFPS();
221   if (newFrame > lengthFrames) { switchState(S_PLAY); unLock(); }
222   else switchState(S_JUMP, newFrame);
223   unLock();
224 }
225
226 void PlayerRadio::skipBackward(int seconds)
227 {
228   lock();
229   logger->log("PlayerRadio", Log::DEBUG, "SKIP BACKWARD %i SECONDS", seconds);
230   long newFrame = getCurrentSeconds();
231   if (newFrame == 0) { unLock(); return; } // Current pos from demuxer is not valid
232 //  newFrame -= seconds * video->getFPS();
233   if (newFrame < 0) newFrame = 0;
234   switchState(S_JUMP, newFrame);
235   unLock();
236 }
237
238 // ----------------------------------- Implementations called events
239
240 void PlayerRadio::switchState(UCHAR toState, ULONG jumpFrame)
241 {
242   if (!initted) return;
243
244   logger->log("PlayerRadio", Log::DEBUG, "Switch state from %u to %u", state, toState);
245
246   switch(state) // current state selector
247   {
248     case S_PLAY: // from S_PLAY -----------------------------------
249     {
250       switch(toState)
251       {
252         case S_PLAY: // to S_PLAY
253         {
254           return;
255         }
256         case S_PAUSE_P: // to S_PAUSE_P
257         {
258           audio->pause();
259           state = S_PAUSE_P;
260           return;
261         }
262         case S_STOP: // to S_STOP
263         {
264           afeed.stop();
265           threadStop();
266           audio->stop();
267           audio->unPause();
268           demuxer->reset();
269           state = S_STOP;
270           return;
271         }
272         case S_JUMP: // to S_JUMP
273         {
274           restartAtFrame(jumpFrame);
275           return;
276         }
277       }
278     }
279     case S_PAUSE_P: // from S_PAUSE_P -----------------------------------
280     {
281       switch(toState)
282       {
283         case S_PLAY: // to S_PLAY
284         {
285           audio->unPause();
286           state = S_PLAY;
287           return;
288         }
289         case S_PAUSE_P: // to S_PAUSE_P
290         {
291           return;
292         }
293         case S_STOP: // to S_STOP
294         {
295           afeed.stop();
296           threadStop();
297           audio->stop();
298           audio->unPause();
299           demuxer->reset();
300           audio->systemMuteOff();
301           state = S_STOP;
302           return;
303         }
304         case S_JUMP: // to S_JUMP
305         {
306           state = S_PLAY;
307           audio->unPause();
308           restartAtFrame(jumpFrame);
309           return;
310         }
311       }
312     }
313     case S_STOP: // from S_STOP -----------------------------------
314     {
315       switch(toState)
316       {
317         case S_PLAY: // to S_PLAY
318         {
319           startup = true;
320
321           audio->reset();
322           audio->systemMuteOff();
323           demuxer->reset();
324           if (isRecording)
325           {
326             // FIXME use restartAtFrame here?
327             if (currentFrameNumber > lengthFrames) currentFrameNumber = 0;
328             demuxer->setFrameNum(currentFrameNumber);
329           }
330
331           state = S_PLAY;
332           threadStart();
333
334           if (isRecording)
335           {
336             logger->log("PlayerRadio", Log::DEBUG, "Immediate play");
337             afeed.start();
338             audio->sync();
339             audio->play();
340           }
341           else // do prebuffering
342           {
343             logger->log("PlayerRadio", Log::DEBUG, "Prebuffering...");
344             preBuffering = true;
345           }
346           return;
347         }
348         case S_PAUSE_P: // to S_PAUSE_P
349         {
350           return;
351         }
352         case S_STOP: // to S_STOP
353         {
354           return;
355         }
356         case S_JUMP: // to S_JUMP
357         {
358           return;
359         }
360       }
361     }
362     // case S_JUMP cannot be selected as a start state because it auto flips to play
363   }
364 }
365
366 // ----------------------------------- Internal functions
367
368 void PlayerRadio::lock()
369 {
370 #ifndef WIN32
371   pthread_mutex_lock(&mutex);
372   logger->log("PlayerRadio", Log::DEBUG, "LOCKED");
373
374 #else
375    WaitForSingleObject(mutex, INFINITE);
376 #endif
377 }
378
379 void PlayerRadio::unLock()
380 {
381 #ifndef WIN32
382   logger->log("PlayerRadio", Log::DEBUG, "UNLOCKING");
383   pthread_mutex_unlock(&mutex);
384 #else
385    ReleaseMutex(mutex);
386 #endif
387 }
388
389 void PlayerRadio::restartAtFrame(ULONG newFrame)
390 {
391   afeed.stop();
392   threadStop();
393   audio->reset();
394   demuxer->flush();
395   currentFrameNumber = newFrame;
396   demuxer->setFrameNum(newFrame);
397   afeed.start();
398   threadStart();
399   audio->play();
400   audio->sync();
401   audio->systemMuteOff();
402   audio->doMuting();
403 }
404
405 void PlayerRadio::doConnectionLost()
406 {
407   logger->log("PlayerRadio", Log::DEBUG, "Connection lost, sending message");
408   Message* m = new Message();
409   m->to = messageReceiver;
410   m->from = this;
411   m->message = Message::PLAYER_EVENT;
412   m->parameter = PlayerRadio::CONNECTION_LOST;
413   messageQueue->postMessage(m);
414 }
415
416 // ----------------------------------- Callback
417
418 void PlayerRadio::call(void* caller)
419 {
420   threadSignalNoLock();
421 }
422
423 // ----------------------------------- Feed thread
424
425 void PlayerRadio::threadMethod()
426 {
427   if (isRecording)
428   {
429     if (state == S_PLAY) threadFeedPlay();
430   }
431   else
432   {
433     threadFeedLive();
434   }
435 }
436
437 void PlayerRadio::threadFeedLive()
438 {
439   UINT thisRead;
440   UINT writeLength;
441   UINT thisWrite;
442   UINT preBufferTotal = 0;
443
444   UINT askFor;
445   while(1)
446   {
447     thisRead = 0;
448     writeLength = 0;
449     thisWrite = 0;
450
451     threadCheckExit();
452
453     if (startup)
454       askFor = startupBlockSize; // find audio streams sized block
455     else
456       askFor = blockSize; // normal
457
458     threadBuffer = vdr->getBlock(0, askFor, &thisRead);
459
460     if (!vdr->isConnected())
461     {
462       doConnectionLost();
463       return;
464     }
465
466     if (!threadBuffer) break;
467
468     if (startup)
469     {
470       int a_stream = demuxer->scan(threadBuffer, thisRead);
471       demuxer->setAudioStream(a_stream);
472       logger->log("PlayerRadio", Log::DEBUG, "Startup Audio stream chosen %x", a_stream);
473       startup = false;
474     }
475
476     if (preBuffering)
477     {
478       preBufferTotal += thisRead;
479       if (preBufferTotal >= preBufferSize)
480       {
481         logger->log("PlayerRadio", Log::DEBUG, "Got >500K, prebuffering complete");
482
483         preBuffering = false;
484         preBufferTotal = 0;
485
486         audio->sync();
487         audio->play();
488         afeed.start();
489 //        unLock(); // thread will be locked by play until here
490         // FIXME - see if this can segfault because it is starting threads out of the master mutex
491       }
492     }
493
494     threadCheckExit();
495
496     while(writeLength < thisRead)
497     {
498       thisWrite = demuxer->put(threadBuffer + writeLength, thisRead - writeLength);
499       writeLength += thisWrite;
500
501       if (!thisWrite)
502       {
503         // demuxer is full and can't take anymore
504         threadLock();
505         threadWaitForSignal();
506         threadUnlock();
507       }
508
509       threadCheckExit();
510     }
511
512     free(threadBuffer);
513     threadBuffer = NULL;
514
515   }
516
517   logger->log("PlayerRadio", Log::DEBUG, "Live play failed to start or interrupted");
518
519   threadCheckExit();
520
521   Message* m = new Message(); // Must be done after this thread finishes, and must break into master mutex
522   m->to = messageReceiver;
523   m->from = this;
524   m->message = Message::PLAYER_EVENT;
525   m->parameter = PlayerRadio::STREAM_END;
526   logger->log("PlayerRadio", Log::DEBUG, "Posting message to %p...", messageQueue);
527   messageQueue->postMessage(m);
528   logger->log("PlayerRadio", Log::DEBUG, "Message posted...");
529 }
530
531 void PlayerRadio::threadFeedPlay()
532 {
533   ULLONG feedPosition;
534   UINT thisRead, writeLength, thisWrite, askFor;
535   time_t lastRescan = time(NULL);
536
537   feedPosition = vdr->positionFromFrameNumber(currentFrameNumber);
538   if (!vdr->isConnected()) { doConnectionLost(); return; }
539   logger->log("PlayerRadio", Log::DEBUG, "startFeedPlay: wantedframe %i goto %llu", currentFrameNumber, feedPosition);
540
541
542   while(1)
543   {
544     thisRead = 0;
545     writeLength = 0;
546     thisWrite = 0;
547
548     threadCheckExit();
549
550     // If we havn't rescanned for a while..
551     if ((lastRescan + 60) < time(NULL))
552     {
553       lengthBytes = vdr->rescanRecording(&lengthFrames);
554       if (!vdr->isConnected()) { doConnectionLost(); return; }
555       logger->log("PlayerRadio", Log::DEBUG, "Rescanned and reset length: %llu", lengthBytes);
556       lastRescan = time(NULL);
557     }
558
559     if (feedPosition >= lengthBytes) break;  // finished playback
560
561     if (startup)
562     {
563       if (startupBlockSize > lengthBytes)
564         askFor = lengthBytes; // is a very small recording!
565       else
566         askFor = startupBlockSize; // normal, but a startup sized block to detect all the audio streams
567     }
568     else
569     {
570       if ((feedPosition + blockSize) > lengthBytes) // last block of recording
571         askFor = lengthBytes - feedPosition;
572       else // normal
573         askFor = blockSize;
574     }
575
576     threadBuffer = vdr->getBlock(feedPosition, askFor, &thisRead);
577     feedPosition += thisRead;
578
579     if (!vdr->isConnected())
580     {
581       doConnectionLost();
582       return;
583     }
584
585     if (!threadBuffer) break;
586
587     if (startup)
588     {
589       int a_stream = demuxer->scan(threadBuffer, thisRead);
590       demuxer->setAudioStream(a_stream);
591       logger->log("PlayerRadio", Log::DEBUG, "Startup Audio stream chosen %x", a_stream);
592       startup = false;
593     }
594
595     threadCheckExit();
596
597     while(writeLength < thisRead)
598     {
599       thisWrite = demuxer->put(threadBuffer + writeLength, thisRead - writeLength);
600       writeLength += thisWrite;
601
602       if (!thisWrite)
603       {
604         // demuxer is full and can't take anymore
605         threadLock();
606         threadWaitForSignal();
607         threadUnlock();
608       }
609
610       threadCheckExit();
611     }
612
613     free(threadBuffer);
614     threadBuffer = NULL;
615
616   }
617
618   // end of recording
619   logger->log("PlayerRadio", Log::DEBUG, "Recording playback ends");
620
621   threadCheckExit();
622
623
624   Message* m = new Message(); // Must be done after this thread finishes, and must break into master mutex
625   m->to = messageReceiver;
626   m->from = this;
627   m->message = Message::PLAYER_EVENT;
628   m->parameter = PlayerRadio::STOP_PLAYBACK;
629   logger->log("PlayerRadio", Log::DEBUG, "Posting message to %p...", messageQueue);
630   messageQueue->postMessage(m);
631 }
632
633 void PlayerRadio::threadPostStopCleanup()
634 {
635   if (threadBuffer)
636   {
637     free(threadBuffer);
638     threadBuffer = NULL;
639   }
640 }