2 Copyright 2004-2006 Chris Tallon
4 This file is part of VOMP.
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.
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.
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
21 #include "playerradio.h"
23 // ----------------------------------- Called from outside, one offs or info funcs
25 PlayerRadio::PlayerRadio(MessageQueue* tmessageQueue, void* tmessageReceiver, bool tIsRecording)
28 messageQueue = tmessageQueue;
29 messageReceiver = tmessageReceiver;
30 audio = Audio::getInstance();
31 logger = Log::getInstance();
32 vdr = VDR::getInstance();
42 isRecording = tIsRecording;
47 startupBlockSize = 20000;
48 preBufferSize = 20000;
51 PlayerRadio::~PlayerRadio()
53 if (initted) shutdown();
56 int PlayerRadio::init(ULLONG tlengthBytes, ULONG tlengthFrames)
58 if (initted) return 0;
60 pthread_mutex_init(&mutex, NULL);
62 mutex=CreateMutex(NULL,FALSE,NULL);
65 demuxer = new DemuxerVDR();
66 if (!demuxer) return 0;
68 if (!demuxer->init(this, audio, NULL))
70 logger->log("PlayerRadio", Log::ERR, "Demuxer failed to init");
78 lengthBytes = tlengthBytes;
79 lengthFrames = tlengthFrames;
81 logger->log("PlayerRadio", Log::DEBUG, "PlayerRadio has received length bytes of %llu", lengthBytes);
87 threadBuffer = vdr->getBlock(0, 10000, &thisRead);
90 logger->log("PlayerRadio", Log::ERR, "Failed to get start block");
95 success = demuxer->findPTS(threadBuffer, thisRead, &startPTS);
98 logger->log("PlayerRadio", Log::ERR, "Failed to get start PTS");
103 threadBuffer = vdr->getBlock(tlengthBytes - 10000, 10000, &thisRead);
106 logger->log("PlayerRadio", Log::ERR, "Failed to get end block");
111 success = demuxer->findPTS(threadBuffer, thisRead, &endPTS);
114 logger->log("PlayerRadio", Log::ERR, "Failed to get end PTS");
119 logger->log("PlayerRadio", Log::DEBUG, "Start: %llu, End: %llu", startPTS, endPTS);
121 if (startPTS < endPTS)
123 lengthSeconds = (endPTS - startPTS) / 90000;
127 lengthSeconds = (startPTS - endPTS) / 90000;
134 int PlayerRadio::shutdown()
136 if (!initted) return 0;
151 void PlayerRadio::setStartBytes(ULLONG startBytes)
153 streamPos = startBytes;
156 ULONG PlayerRadio::getLengthSeconds()
158 return lengthSeconds;
161 ULONG PlayerRadio::getCurrentSeconds()
163 if (startup) return 0;
167 // ----------------------------------- Externally called events
169 void PlayerRadio::play()
171 if (!initted) return;
172 if (state == S_PLAY) return;
178 void PlayerRadio::stop()
180 if (!initted) return;
181 if (state == S_STOP) return;
183 logger->log("PlayerRadio", Log::DEBUG, "Stop called lock");
188 void PlayerRadio::pause()
190 if (!initted) return;
193 if (state == S_PAUSE_P)
199 switchState(S_PAUSE_P);
205 void PlayerRadio::jumpToPercent(int percent)
208 logger->log("PlayerRadio", Log::DEBUG, "JUMP TO %i%%", percent);
209 ULONG newFrame = percent * lengthFrames / 100;
210 switchState(S_JUMP, newFrame);
214 void PlayerRadio::skipForward(int seconds)
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);
226 void PlayerRadio::skipBackward(int seconds)
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);
238 // ----------------------------------- Implementations called events
240 void PlayerRadio::switchState(UCHAR toState, ULONG jumpFrame)
242 if (!initted) return;
244 logger->log("PlayerRadio", Log::DEBUG, "Switch state from %u to %u", state, toState);
246 switch(state) // current state selector
248 case S_PLAY: // from S_PLAY -----------------------------------
252 case S_PLAY: // to S_PLAY
256 case S_PAUSE_P: // to S_PAUSE_P
262 case S_STOP: // to S_STOP
272 case S_JUMP: // to S_JUMP
274 restartAtFrame(jumpFrame);
279 case S_PAUSE_P: // from S_PAUSE_P -----------------------------------
283 case S_PLAY: // to S_PLAY
289 case S_PAUSE_P: // to S_PAUSE_P
293 case S_STOP: // to S_STOP
300 audio->systemMuteOff();
304 case S_JUMP: // to S_JUMP
308 restartAtFrame(jumpFrame);
313 case S_STOP: // from S_STOP -----------------------------------
317 case S_PLAY: // to S_PLAY
322 audio->systemMuteOff();
326 // FIXME use restartAtFrame here?
327 if (currentFrameNumber > lengthFrames) currentFrameNumber = 0;
328 demuxer->setFrameNum(currentFrameNumber);
336 logger->log("PlayerRadio", Log::DEBUG, "Immediate play");
341 else // do prebuffering
343 logger->log("PlayerRadio", Log::DEBUG, "Prebuffering...");
348 case S_PAUSE_P: // to S_PAUSE_P
352 case S_STOP: // to S_STOP
356 case S_JUMP: // to S_JUMP
362 // case S_JUMP cannot be selected as a start state because it auto flips to play
366 // ----------------------------------- Internal functions
368 void PlayerRadio::lock()
371 pthread_mutex_lock(&mutex);
372 logger->log("PlayerRadio", Log::DEBUG, "LOCKED");
375 WaitForSingleObject(mutex, INFINITE);
379 void PlayerRadio::unLock()
382 logger->log("PlayerRadio", Log::DEBUG, "UNLOCKING");
383 pthread_mutex_unlock(&mutex);
389 void PlayerRadio::restartAtFrame(ULONG newFrame)
395 currentFrameNumber = newFrame;
396 demuxer->setFrameNum(newFrame);
401 audio->systemMuteOff();
405 void PlayerRadio::doConnectionLost()
407 logger->log("PlayerRadio", Log::DEBUG, "Connection lost, sending message");
408 Message* m = new Message();
409 m->to = messageReceiver;
411 m->message = Message::PLAYER_EVENT;
412 m->parameter = PlayerRadio::CONNECTION_LOST;
413 messageQueue->postMessage(m);
416 // ----------------------------------- Callback
418 void PlayerRadio::call(void* caller)
420 threadSignalNoLock();
423 // ----------------------------------- Feed thread
425 void PlayerRadio::threadMethod()
429 if (state == S_PLAY) threadFeedPlay();
437 void PlayerRadio::threadFeedLive()
442 UINT preBufferTotal = 0;
454 askFor = startupBlockSize; // find audio streams sized block
456 askFor = blockSize; // normal
458 threadBuffer = vdr->getBlock(0, askFor, &thisRead);
460 if (!vdr->isConnected())
466 if (!threadBuffer) break;
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);
478 preBufferTotal += thisRead;
479 if (preBufferTotal >= preBufferSize)
481 logger->log("PlayerRadio", Log::DEBUG, "Got >500K, prebuffering complete");
483 preBuffering = false;
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
496 while(writeLength < thisRead)
498 thisWrite = demuxer->put(threadBuffer + writeLength, thisRead - writeLength);
499 writeLength += thisWrite;
503 // demuxer is full and can't take anymore
505 threadWaitForSignal();
517 logger->log("PlayerRadio", Log::DEBUG, "Live play failed to start or interrupted");
521 Message* m = new Message(); // Must be done after this thread finishes, and must break into master mutex
522 m->to = messageReceiver;
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...");
531 void PlayerRadio::threadFeedPlay()
534 UINT thisRead, writeLength, thisWrite, askFor;
535 time_t lastRescan = time(NULL);
537 feedPosition = vdr->positionFromFrameNumber(currentFrameNumber);
538 if (!vdr->isConnected()) { doConnectionLost(); return; }
539 logger->log("PlayerRadio", Log::DEBUG, "startFeedPlay: wantedframe %i goto %llu", currentFrameNumber, feedPosition);
550 // If we havn't rescanned for a while..
551 if ((lastRescan + 60) < time(NULL))
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);
559 if (feedPosition >= lengthBytes) break; // finished playback
563 if (startupBlockSize > lengthBytes)
564 askFor = lengthBytes; // is a very small recording!
566 askFor = startupBlockSize; // normal, but a startup sized block to detect all the audio streams
570 if ((feedPosition + blockSize) > lengthBytes) // last block of recording
571 askFor = lengthBytes - feedPosition;
576 threadBuffer = vdr->getBlock(feedPosition, askFor, &thisRead);
577 feedPosition += thisRead;
579 if (!vdr->isConnected())
585 if (!threadBuffer) break;
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);
597 while(writeLength < thisRead)
599 thisWrite = demuxer->put(threadBuffer + writeLength, thisRead - writeLength);
600 writeLength += thisWrite;
604 // demuxer is full and can't take anymore
606 threadWaitForSignal();
619 logger->log("PlayerRadio", Log::DEBUG, "Recording playback ends");
624 Message* m = new Message(); // Must be done after this thread finishes, and must break into master mutex
625 m->to = messageReceiver;
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);
633 void PlayerRadio::threadPostStopCleanup()