From d35c4c18dd83e0e1dde57d382cf4c56665608254 Mon Sep 17 00:00:00 2001 From: Chris Tallon Date: Sun, 19 Nov 2006 18:27:43 +0000 Subject: [PATCH] Some radio playback support --- command.cc | 5 +- demuxer.cc | 2 +- objects.mk | 3 +- player.h | 1 - playerradio.cc | 640 ++++++++++++++++++++++++++++++++++++++++++++++ playerradio.h | 161 ++++++++++++ vradiorec.cc | 505 ++++++++++++++++++++++++++++++++++++ vradiorec.h | 76 ++++++ vrecordinglist.cc | 21 +- vrecordinglist.h | 1 + vvideorec.h | 1 + 11 files changed, 1405 insertions(+), 11 deletions(-) create mode 100644 playerradio.cc create mode 100644 playerradio.h create mode 100644 vradiorec.cc create mode 100644 vradiorec.h diff --git a/command.cc b/command.cc index 2eced78..e7715f8 100644 --- a/command.cc +++ b/command.cc @@ -770,11 +770,10 @@ void Command::doJustConnected(VConnect* vconnect) // Enter pre-keys here // handleCommand(Remote::THREE); -// handleCommand(Remote::SKIPFORWARD); +// handleCommand(Remote::UP); // handleCommand(Remote::OK); -// handleCommand(Remote::PLAY); // handleCommand(Remote::OK); -// handleCommand(Remote::DOWN); +// handleCommand(Remote::PLAY); // handleCommand(Remote::DOWN); // handleCommand(Remote::DOWN); // handleCommand(Remote::OK); diff --git a/demuxer.cc b/demuxer.cc index f03b279..aff0f7f 100644 --- a/demuxer.cc +++ b/demuxer.cc @@ -138,7 +138,7 @@ void Demuxer::setAspectRatio(enum AspectRatio ar) { arcnt = 0; aspect_ratio = ar; - callback->call(this); + if (callback) callback->call(this); } } else diff --git a/objects.mk b/objects.mk index d5805b2..50b10d4 100644 --- a/objects.mk +++ b/objects.mk @@ -2,7 +2,7 @@ OBJECTS1 = command.o log.o tcp.o dsock.o thread.o timers.o i18n.o mutex.o \ message.o messagequeue.o udp.o \ vdr.o recman.o recording.o recinfo.o channel.o rectimer.o event.o \ directory.o \ - player.o vfeed.o afeed.o \ + player.o playerradio.o vfeed.o afeed.o \ demuxer.o demuxervdr.o stream.o draintarget.o \ viewman.o box.o region.o colour.o view.o \ vinfo.o vquestion.o vwallpaper.o vrecordinglist.o vlivebanner.o \ @@ -10,6 +10,7 @@ OBJECTS1 = command.o log.o tcp.o dsock.o thread.o timers.o i18n.o mutex.o \ vtimerlist.o vtimeredit.o voptionsmenu.o vrecordingmenu.o \ vchannellist.o vwelcome.o vvideolive.o vvideorec.o vepgsettimer.o \ vchannelselect.o vserverselect.o vconnect.o vepg.o vrecmove.o \ + vradiorec.o \ widget.o wselectlist.o wjpeg.o wsymbol.o wbutton.o \ woptionbox.o wtextbox.o wwss.o \ fonts/helvB24.o fonts/helvB18.o \ diff --git a/player.h b/player.h index 39a4d87..2e920fd 100644 --- a/player.h +++ b/player.h @@ -107,7 +107,6 @@ class Player : public Thread_TYPE, public Callback void threadFeedPlay(); void threadFeedScan(); - void setEndTS(); void doConnectionLost(); void restartAtFrame(ULONG newFrame); diff --git a/playerradio.cc b/playerradio.cc new file mode 100644 index 0000000..417c7d2 --- /dev/null +++ b/playerradio.cc @@ -0,0 +1,640 @@ +/* + Copyright 2004-2006 Chris Tallon + + This file is part of VOMP. + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "playerradio.h" + +// ----------------------------------- Called from outside, one offs or info funcs + +PlayerRadio::PlayerRadio(MessageQueue* tmessageQueue, void* tmessageReceiver, bool tIsRecording) +: afeed(this) +{ + messageQueue = tmessageQueue; + messageReceiver = tmessageReceiver; + audio = Audio::getInstance(); + logger = Log::getInstance(); + vdr = VDR::getInstance(); + initted = false; + lengthBytes = 0; + lengthFrames = 0; + streamPos = 0; + state = S_STOP; + + startPTS = 0; + lengthSeconds = 0; + + isRecording = tIsRecording; + + threadBuffer = NULL; + + blockSize = 10000; + startupBlockSize = 20000; + preBufferSize = 20000; +} + +PlayerRadio::~PlayerRadio() +{ + if (initted) shutdown(); +} + +int PlayerRadio::init(ULLONG tlengthBytes, ULONG tlengthFrames) +{ + if (initted) return 0; +#ifndef WIN32 + pthread_mutex_init(&mutex, NULL); +#else + mutex=CreateMutex(NULL,FALSE,NULL); +#endif + + demuxer = new DemuxerVDR(); + if (!demuxer) return 0; + + if (!demuxer->init(this, audio, NULL)) + { + logger->log("PlayerRadio", Log::ERR, "Demuxer failed to init"); + shutdown(); + return 0; + } + + afeed.init(); + audio->stop(); + + lengthBytes = tlengthBytes; + lengthFrames = tlengthFrames; + + logger->log("PlayerRadio", Log::DEBUG, "PlayerRadio has received length bytes of %llu", lengthBytes); + + UINT thisRead = 0; + ULLONG endPTS = 0; + int success; + + threadBuffer = vdr->getBlock(0, 10000, &thisRead); + if (!threadBuffer) + { + logger->log("PlayerRadio", Log::ERR, "Failed to get start block"); + shutdown(); + return 0; + } + + success = demuxer->findPTS(threadBuffer, thisRead, &startPTS); + if (!success) + { + logger->log("PlayerRadio", Log::ERR, "Failed to get start PTS"); + shutdown(); + return 0; + } + + threadBuffer = vdr->getBlock(tlengthBytes - 10000, 10000, &thisRead); + if (!threadBuffer) + { + logger->log("PlayerRadio", Log::ERR, "Failed to get end block"); + shutdown(); + return 0; + } + + success = demuxer->findPTS(threadBuffer, thisRead, &endPTS); + if (!success) + { + logger->log("PlayerRadio", Log::ERR, "Failed to get end PTS"); + shutdown(); + return 0; + } + + logger->log("PlayerRadio", Log::DEBUG, "Start: %llu, End: %llu", startPTS, endPTS); + + if (startPTS < endPTS) + { + lengthSeconds = (endPTS - startPTS) / 90000; + } + else + { + lengthSeconds = (startPTS - endPTS) / 90000; + } + + initted = true; + return 1; +} + +int PlayerRadio::shutdown() +{ + if (!initted) return 0; + switchState(S_STOP); + initted = false; + + delete demuxer; + demuxer = NULL; + +#ifdef WIN32 + CloseHandle(mutex); +#endif + + return 1; +} + + +void PlayerRadio::setStartBytes(ULLONG startBytes) +{ + streamPos = startBytes; +} + +ULONG PlayerRadio::getLengthSeconds() +{ + return lengthSeconds; +} + +ULONG PlayerRadio::getCurrentSeconds() +{ + if (startup) return 0; + return 0; +} + +// ----------------------------------- Externally called events + +void PlayerRadio::play() +{ + if (!initted) return; + if (state == S_PLAY) return; + lock(); + switchState(S_PLAY); + unLock(); +} + +void PlayerRadio::stop() +{ + if (!initted) return; + if (state == S_STOP) return; + lock(); + logger->log("PlayerRadio", Log::DEBUG, "Stop called lock"); + switchState(S_STOP); + unLock(); +} + +void PlayerRadio::pause() +{ + if (!initted) return; + lock(); + + if (state == S_PAUSE_P) + { + switchState(S_PLAY); + } + else + { + switchState(S_PAUSE_P); + } + + unLock(); +} + +void PlayerRadio::jumpToPercent(int percent) +{ + lock(); + logger->log("PlayerRadio", Log::DEBUG, "JUMP TO %i%%", percent); + ULONG newFrame = percent * lengthFrames / 100; + switchState(S_JUMP, newFrame); + unLock(); +} + +void PlayerRadio::skipForward(int seconds) +{ + lock(); + logger->log("PlayerRadio", Log::DEBUG, "SKIP FORWARD %i SECONDS", seconds); + ULONG newFrame = getCurrentSeconds(); + if (newFrame == 0) { unLock(); return; } // Current pos from demuxer is not valid +// newFrame += seconds * video->getFPS(); + if (newFrame > lengthFrames) { switchState(S_PLAY); unLock(); } + else switchState(S_JUMP, newFrame); + unLock(); +} + +void PlayerRadio::skipBackward(int seconds) +{ + lock(); + logger->log("PlayerRadio", Log::DEBUG, "SKIP BACKWARD %i SECONDS", seconds); + long newFrame = getCurrentSeconds(); + if (newFrame == 0) { unLock(); return; } // Current pos from demuxer is not valid +// newFrame -= seconds * video->getFPS(); + if (newFrame < 0) newFrame = 0; + switchState(S_JUMP, newFrame); + unLock(); +} + +// ----------------------------------- Implementations called events + +void PlayerRadio::switchState(UCHAR toState, ULONG jumpFrame) +{ + if (!initted) return; + + logger->log("PlayerRadio", Log::DEBUG, "Switch state from %u to %u", state, toState); + + switch(state) // current state selector + { + case S_PLAY: // from S_PLAY ----------------------------------- + { + switch(toState) + { + case S_PLAY: // to S_PLAY + { + return; + } + case S_PAUSE_P: // to S_PAUSE_P + { + audio->pause(); + state = S_PAUSE_P; + return; + } + case S_STOP: // to S_STOP + { + afeed.stop(); + threadStop(); + audio->stop(); + audio->unPause(); + demuxer->reset(); + state = S_STOP; + return; + } + case S_JUMP: // to S_JUMP + { + restartAtFrame(jumpFrame); + return; + } + } + } + case S_PAUSE_P: // from S_PAUSE_P ----------------------------------- + { + switch(toState) + { + case S_PLAY: // to S_PLAY + { + audio->unPause(); + state = S_PLAY; + return; + } + case S_PAUSE_P: // to S_PAUSE_P + { + return; + } + case S_STOP: // to S_STOP + { + afeed.stop(); + threadStop(); + audio->stop(); + audio->unPause(); + demuxer->reset(); + audio->systemMuteOff(); + state = S_STOP; + return; + } + case S_JUMP: // to S_JUMP + { + state = S_PLAY; + audio->unPause(); + restartAtFrame(jumpFrame); + return; + } + } + } + case S_STOP: // from S_STOP ----------------------------------- + { + switch(toState) + { + case S_PLAY: // to S_PLAY + { + startup = true; + + audio->reset(); + audio->systemMuteOff(); + demuxer->reset(); + if (isRecording) + { + // FIXME use restartAtFrame here? + if (currentFrameNumber > lengthFrames) currentFrameNumber = 0; + demuxer->setFrameNum(currentFrameNumber); + } + + state = S_PLAY; + threadStart(); + + if (isRecording) + { + logger->log("PlayerRadio", Log::DEBUG, "Immediate play"); + afeed.start(); + audio->sync(); + audio->play(); + } + else // do prebuffering + { + logger->log("PlayerRadio", Log::DEBUG, "Prebuffering..."); + preBuffering = true; + } + return; + } + case S_PAUSE_P: // to S_PAUSE_P + { + return; + } + case S_STOP: // to S_STOP + { + return; + } + case S_JUMP: // to S_JUMP + { + return; + } + } + } + // case S_JUMP cannot be selected as a start state because it auto flips to play + } +} + +// ----------------------------------- Internal functions + +void PlayerRadio::lock() +{ +#ifndef WIN32 + pthread_mutex_lock(&mutex); + logger->log("PlayerRadio", Log::DEBUG, "LOCKED"); + +#else + WaitForSingleObject(mutex, INFINITE); +#endif +} + +void PlayerRadio::unLock() +{ +#ifndef WIN32 + logger->log("PlayerRadio", Log::DEBUG, "UNLOCKING"); + pthread_mutex_unlock(&mutex); +#else + ReleaseMutex(mutex); +#endif +} + +void PlayerRadio::restartAtFrame(ULONG newFrame) +{ + afeed.stop(); + threadStop(); + audio->reset(); + demuxer->flush(); + currentFrameNumber = newFrame; + demuxer->setFrameNum(newFrame); + afeed.start(); + threadStart(); + audio->play(); + audio->sync(); + audio->systemMuteOff(); + audio->doMuting(); +} + +void PlayerRadio::doConnectionLost() +{ + logger->log("PlayerRadio", Log::DEBUG, "Connection lost, sending message"); + Message* m = new Message(); + m->to = messageReceiver; + m->from = this; + m->message = Message::PLAYER_EVENT; + m->parameter = PlayerRadio::CONNECTION_LOST; + messageQueue->postMessage(m); +} + +// ----------------------------------- Callback + +void PlayerRadio::call(void* caller) +{ + threadSignalNoLock(); +} + +// ----------------------------------- Feed thread + +void PlayerRadio::threadMethod() +{ + if (isRecording) + { + if (state == S_PLAY) threadFeedPlay(); + } + else + { + threadFeedLive(); + } +} + +void PlayerRadio::threadFeedLive() +{ + UINT thisRead; + UINT writeLength; + UINT thisWrite; + UINT preBufferTotal = 0; + + UINT askFor; + while(1) + { + thisRead = 0; + writeLength = 0; + thisWrite = 0; + + threadCheckExit(); + + if (startup) + askFor = startupBlockSize; // find audio streams sized block + else + askFor = blockSize; // normal + + threadBuffer = vdr->getBlock(0, askFor, &thisRead); + + if (!vdr->isConnected()) + { + doConnectionLost(); + return; + } + + if (!threadBuffer) break; + + if (startup) + { + int a_stream = demuxer->scan(threadBuffer, thisRead); + demuxer->setAudioStream(a_stream); + logger->log("PlayerRadio", Log::DEBUG, "Startup Audio stream chosen %x", a_stream); + startup = false; + } + + if (preBuffering) + { + preBufferTotal += thisRead; + if (preBufferTotal >= preBufferSize) + { + logger->log("PlayerRadio", Log::DEBUG, "Got >500K, prebuffering complete"); + + preBuffering = false; + preBufferTotal = 0; + + audio->sync(); + audio->play(); + afeed.start(); +// unLock(); // thread will be locked by play until here + // FIXME - see if this can segfault because it is starting threads out of the master mutex + } + } + + threadCheckExit(); + + while(writeLength < thisRead) + { + thisWrite = demuxer->put(threadBuffer + writeLength, thisRead - writeLength); + writeLength += thisWrite; + + if (!thisWrite) + { + // demuxer is full and can't take anymore + threadLock(); + threadWaitForSignal(); + threadUnlock(); + } + + threadCheckExit(); + } + + free(threadBuffer); + threadBuffer = NULL; + + } + + logger->log("PlayerRadio", Log::DEBUG, "Live play failed to start or interrupted"); + + threadCheckExit(); + + Message* m = new Message(); // Must be done after this thread finishes, and must break into master mutex + m->to = messageReceiver; + m->from = this; + m->message = Message::PLAYER_EVENT; + m->parameter = PlayerRadio::STREAM_END; + logger->log("PlayerRadio", Log::DEBUG, "Posting message to %p...", messageQueue); + messageQueue->postMessage(m); + logger->log("PlayerRadio", Log::DEBUG, "Message posted..."); +} + +void PlayerRadio::threadFeedPlay() +{ + ULLONG feedPosition; + UINT thisRead, writeLength, thisWrite, askFor; + time_t lastRescan = time(NULL); + + feedPosition = vdr->positionFromFrameNumber(currentFrameNumber); + if (!vdr->isConnected()) { doConnectionLost(); return; } + logger->log("PlayerRadio", Log::DEBUG, "startFeedPlay: wantedframe %i goto %llu", currentFrameNumber, feedPosition); + + + while(1) + { + thisRead = 0; + writeLength = 0; + thisWrite = 0; + + threadCheckExit(); + + // If we havn't rescanned for a while.. + if ((lastRescan + 60) < time(NULL)) + { + lengthBytes = vdr->rescanRecording(&lengthFrames); + if (!vdr->isConnected()) { doConnectionLost(); return; } + logger->log("PlayerRadio", Log::DEBUG, "Rescanned and reset length: %llu", lengthBytes); + lastRescan = time(NULL); + } + + if (feedPosition >= lengthBytes) break; // finished playback + + if (startup) + { + if (startupBlockSize > lengthBytes) + askFor = lengthBytes; // is a very small recording! + else + askFor = startupBlockSize; // normal, but a startup sized block to detect all the audio streams + } + else + { + if ((feedPosition + blockSize) > lengthBytes) // last block of recording + askFor = lengthBytes - feedPosition; + else // normal + askFor = blockSize; + } + + threadBuffer = vdr->getBlock(feedPosition, askFor, &thisRead); + feedPosition += thisRead; + + if (!vdr->isConnected()) + { + doConnectionLost(); + return; + } + + if (!threadBuffer) break; + + if (startup) + { + int a_stream = demuxer->scan(threadBuffer, thisRead); + demuxer->setAudioStream(a_stream); + logger->log("PlayerRadio", Log::DEBUG, "Startup Audio stream chosen %x", a_stream); + startup = false; + } + + threadCheckExit(); + + while(writeLength < thisRead) + { + thisWrite = demuxer->put(threadBuffer + writeLength, thisRead - writeLength); + writeLength += thisWrite; + + if (!thisWrite) + { + // demuxer is full and can't take anymore + threadLock(); + threadWaitForSignal(); + threadUnlock(); + } + + threadCheckExit(); + } + + free(threadBuffer); + threadBuffer = NULL; + + } + + // end of recording + logger->log("PlayerRadio", Log::DEBUG, "Recording playback ends"); + + threadCheckExit(); + + + Message* m = new Message(); // Must be done after this thread finishes, and must break into master mutex + m->to = messageReceiver; + m->from = this; + m->message = Message::PLAYER_EVENT; + m->parameter = PlayerRadio::STOP_PLAYBACK; + logger->log("PlayerRadio", Log::DEBUG, "Posting message to %p...", messageQueue); + messageQueue->postMessage(m); +} + +void PlayerRadio::threadPostStopCleanup() +{ + if (threadBuffer) + { + free(threadBuffer); + threadBuffer = NULL; + } +} diff --git a/playerradio.h b/playerradio.h new file mode 100644 index 0000000..26f204d --- /dev/null +++ b/playerradio.h @@ -0,0 +1,161 @@ +/* + Copyright 2004-2006 Chris Tallon + + This file is part of VOMP. + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef PLAYERRADIO_H +#define PLAYERRADIO_H + +#include +#include +#ifndef WIN32 +#include +#endif +#include + +#include "audio.h" +#include "demuxervdr.h" +#include "afeed.h" +#include "remote.h" +#include "vdr.h" +#include "callback.h" +#include "message.h" +#include "messagequeue.h" + +#ifdef WIN32 +#include "threadwin.h" +#else +#include "threadp.h" +#endif + +class PlayerRadio : public Thread_TYPE, public Callback +{ + public: + PlayerRadio(MessageQueue* messageQueue, void* messageReceiver, bool isRecording); + virtual ~PlayerRadio(); + + int init(ULLONG lengthBytes, ULONG lengthFrames); + int shutdown(); + void setStartBytes(ULLONG startBytes); + + void play(); + void stop(); + void pause(); + void jumpToPercent(int percent); + void skipForward(int seconds); + void skipBackward(int seconds); + + UCHAR getState() { return state; } + ULONG getCurrentSeconds(); + ULONG getLengthSeconds(); + + void call(void*); // for callback interface + + const static UCHAR S_PLAY = 1; + const static UCHAR S_PAUSE_P = 2; + const static UCHAR S_STOP = 6; + const static UCHAR S_JUMP = 7; + + // Player events + + const static UCHAR CONNECTION_LOST = 1; + const static UCHAR STOP_PLAYBACK = 2; + const static UCHAR STREAM_END = 3; + + protected: + void threadMethod(); + void threadPostStopCleanup(); + + private: + void switchState(UCHAR newState, ULONG jumpFrame=0); + + void threadFeedLive(); + void threadFeedPlay(); + void threadFeedScan(); + + void doConnectionLost(); + void restartAtFrame(ULONG newFrame); + + MessageQueue* messageQueue; + void* messageReceiver; + Log* logger; + Audio* audio; + Demuxer* demuxer; + VDR* vdr; + AFeed afeed; + + bool initted; + bool startup; + bool isRecording; + bool preBuffering; + + ULLONG startPTS; + ULONG lengthSeconds; + +#ifndef WIN32 + pthread_mutex_t mutex; +#else + HANDLE mutex; +#endif + void lock(); + void unLock(); + + ULLONG lengthBytes; + ULLONG streamPos; + ULONG lengthFrames; + ULONG currentFrameNumber; + UINT blockSize; + UINT startupBlockSize; + UINT preBufferSize; + UCHAR* threadBuffer; + UCHAR state; +}; + +#endif + + +/* + +Possible states: + +Play, Pause, FFwd, FBwd, (Stop), [Jump] + + Possible Working + +Play -> PauseP * * + -> Stop * * + -> Jump * * + +PauseP -> Play * * + -> Stop * * + -> Jump * * + +PauseI -> Play * * + -> PauseP + -> Stop * * + -> Jump * * + +Stop -> Play * * + -> PauseP + -> Jump + +Jump -> Play + -> PauseP + -> Stop + +*/ diff --git a/vradiorec.cc b/vradiorec.cc new file mode 100644 index 0000000..c31cd38 --- /dev/null +++ b/vradiorec.cc @@ -0,0 +1,505 @@ +/* + Copyright 2004-2006 Chris Tallon + + This file is part of VOMP. + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "vradiorec.h" + +VRadioRec::VRadioRec(Recording* rec) +{ + vdr = VDR::getInstance(); + video = Video::getInstance(); + timers = Timers::getInstance(); + myRec = rec; + playing = false; + startMargin = 0; + endMargin = 0; + + player = new PlayerRadio(Command::getInstance(), this, true); + + char* cstartMargin = vdr->configLoad("Timers", "Start margin"); + char* cendMargin = vdr->configLoad("Timers", "End margin"); + if (!cstartMargin) + { + startMargin = 300; // 5 mins default + } + else + { + startMargin = atoi(cstartMargin) * 60; + delete[] cstartMargin; + } + + if (!cendMargin) + { + endMargin = 300; // 5 mins default + } + else + { + endMargin = atoi(cendMargin) * 60; + delete[] cendMargin; + } + + Log::getInstance()->log("VRadioRec", Log::DEBUG, "SM: %u EM: %u", startMargin, endMargin); + + create(video->getScreenWidth(), video->getScreenHeight()); + setBackgroundColour(Colour::BLACK); + + barRegion.x = 0; + barRegion.y = video->getScreenHeight() - 58; // FIXME, need to be - 1? and below? + barRegion.w = video->getScreenWidth(); + barRegion.h = 58; + + clocksRegion.x = barRegion.x + 140; + clocksRegion.y = barRegion.y + 12; + clocksRegion.w = 170; + clocksRegion.h = surface->getFontHeight(); + + + barBlue.set(0, 0, 150, 150); + + barShowing = false; +} + +VRadioRec::~VRadioRec() +{ + if (playing) stopPlay(); + + timers->cancelTimer(this, 1); + timers->cancelTimer(this, 2); + + // kill recInfo in case resumePoint has changed (likely) + myRec->dropRecInfo(); + // FIXME - do this properly - save the resume point back to the server manually and update + // rec->recInfo->resumePoint - this will fix the ~10s offset problem as well +} + +void VRadioRec::draw() +{ + View::draw(); +} + +void VRadioRec::go() +{ + Log::getInstance()->log("VRadioRec", Log::DEBUG, "Starting stream: %s", myRec->getFileName()); + ULONG lengthFrames = 0; + ULLONG lengthBytes = vdr->streamRecording(myRec->getFileName(), &lengthFrames); + + bool cantStart = false; + + if (!lengthBytes) cantStart = true; + else if (!player->init(lengthBytes, lengthFrames)) cantStart = true; + else + { + doBar(0); + // player->setStartBytes(startBytes); + player->play(); + playing = true; + } + + if (cantStart) + { + stopPlay(); // clean up + + if (!vdr->isConnected()) + { + Command::getInstance()->connectionLost(); + return; + } + + ViewMan* viewman = ViewMan::getInstance(); + + Message* m = new Message(); + m->message = Message::CLOSE_ME; + m->from = this; + m->to = viewman; + Command::getInstance()->postMessageNoLock(m); + + VInfo* vi = new VInfo(); + vi->create(400, 150); + if (video->getFormat() == Video::PAL) + vi->setScreenPos(170, 200); + else + vi->setScreenPos(160, 150); + vi->setExitable(); + vi->setBorderOn(1); + vi->setTitleBarOn(0); + vi->setOneLiner(tr("Error playing recording")); + vi->draw(); + + m = new Message(); + m->message = Message::ADD_VIEW; + m->to = viewman; + m->parameter = (ULONG)vi; + Command::getInstance()->postMessageNoLock(m); + } +} + +int VRadioRec::handleCommand(int command) +{ + switch(command) + { + case Remote::PLAY: + { + player->play(); + doBar(0); + return 2; + } + + case Remote::STOP: + case Remote::BACK: + case Remote::MENU: + { + if (playing) stopPlay(); + return 4; + } + case Remote::PAUSE: + { + player->pause(); + doBar(0); + return 2; + } + case Remote::SKIPFORWARD: + { + doBar(3); + player->skipForward(60); + return 2; + } + case Remote::SKIPBACK: + { + doBar(4); + player->skipBackward(60); + return 2; + } + case Remote::YELLOW: + { + doBar(2); + player->skipBackward(10); + return 2; + } + case Remote::BLUE: + { + doBar(1); + player->skipForward(10); + return 2; + } + case Remote::OK: + { + if (barShowing) removeBar(); + else doBar(0); + return 2; + } + + case Remote::ZERO: player->jumpToPercent(0); doBar(0); return 2; + case Remote::ONE: player->jumpToPercent(10); doBar(0); return 2; + case Remote::TWO: player->jumpToPercent(20); doBar(0); return 2; + case Remote::THREE: player->jumpToPercent(30); doBar(0); return 2; + case Remote::FOUR: player->jumpToPercent(40); doBar(0); return 2; + case Remote::FIVE: player->jumpToPercent(50); doBar(0); return 2; + case Remote::SIX: player->jumpToPercent(60); doBar(0); return 2; + case Remote::SEVEN: player->jumpToPercent(70); doBar(0); return 2; + case Remote::EIGHT: player->jumpToPercent(80); doBar(0); return 2; + case Remote::NINE: player->jumpToPercent(90); doBar(0); return 2; + +#ifdef DEV + case Remote::RED: + { + //player->test1(); + + + /* + // for testing EPG in NTSC with a NTSC test video + Video::getInstance()->setMode(Video::QUARTER); + Video::getInstance()->setPosition(170, 5); + VEpg* vepg = new VEpg(NULL, 0); + vepg->draw(); + ViewMan::getInstance()->add(vepg); + ViewMan::getInstance()->updateView(vepg); + */ + + return 2; + } + case Remote::GREEN: + { + //player->test2(); + return 2; + } +#endif + + } + + return 1; +} + +void VRadioRec::processMessage(Message* m) +{ + if (m->from != player) return; + if (m->message != Message::PLAYER_EVENT) return; + + Log::getInstance()->log("VRadioRec", Log::DEBUG, "Message received"); + + switch(m->parameter) + { + case Player::CONNECTION_LOST: // connection lost detected + { + // I can't handle this, send it to command + Message* m = new Message(); + m->to = Command::getInstance(); + m->message = Message::CONNECTION_LOST; + Command::getInstance()->postMessageNoLock(m); + break; + } + case Player::STOP_PLAYBACK: + { + // FIXME Obselete ish - improve this + Message* m = new Message(); // Must be done after this thread finishes, and must break into master mutex + m->to = Command::getInstance(); + m->message = Message::STOP_PLAYBACK; + Command::getInstance()->postMessageNoLock(m); + break; + } + } +} + +void VRadioRec::stopPlay() +{ + Log::getInstance()->log("VRadioRec", Log::DEBUG, "Pre stopPlay"); + + // FIXME work out a better soln for this + // Fix a problem to do with thread sync here + // because the bar gets a timer every 0.2s and it seems to take up to 0.1s, + // (or maybe just the wrong thread being selected?) for the main loop to lock and process + // the video stop message it is possible for a bar message to stack up after a stop message + // when the bar message is finally processed the prog crashes because this is deleted by then + removeBar(); + // + + player->stop(); + vdr->stopStreaming(); + delete player; + + playing = false; + + if (!vdr->isConnected()) { Command::getInstance()->connectionLost(); return; } + Log::getInstance()->log("VRadioRec", Log::DEBUG, "Post stopPlay"); +} + +void VRadioRec::doBar(int action) +{ + barShowing = true; + + rectangle(barRegion, barBlue); + + /* Work out what to display - choices: + + Playing > + Paused || + + Specials, informed by parameter + + Skip forward 10s >| + Skip backward 10s |< + Skip forward 1m >>| + Skip backward 1m |<< + + */ + + WSymbol w; + w.setSurface(surface); + w.nextSymbol = 0; + w.setSurfaceOffset(barRegion.x + 66, barRegion.y + 16); + + UCHAR playerState = 0; + + if (action) + { + if (action == 1) w.nextSymbol = WSymbol::SKIPFORWARD; + else if (action == 2) w.nextSymbol = WSymbol::SKIPBACK; + else if (action == 3) w.nextSymbol = WSymbol::SKIPFORWARD2; + else if (action == 4) w.nextSymbol = WSymbol::SKIPBACK2; + } + else + { + playerState = player->getState(); + if (playerState == Player::S_PAUSE_P) w.nextSymbol = WSymbol::PAUSE; + else if (playerState == Player::S_PAUSE_I) w.nextSymbol = WSymbol::PAUSE; + else w.nextSymbol = WSymbol::PLAY; + } + + w.draw(); + + drawBarClocks(); + + ViewMan::getInstance()->updateView(this, &barRegion); + + timers->setTimerD(this, 1, 4); // only set the getridofbar timer if not ffwd/fbwd + timers->setTimerD(this, 2, 0, 200000000); +} + +void VRadioRec::timercall(int clientReference) +{ + switch(clientReference) + { + case 1: + { + // Remove bar + removeBar(); + break; + } + case 2: + { + // Update clock + if (!barShowing) break; + drawBarClocks(); + ViewMan::getInstance()->updateView(this, &barRegion); + timers->setTimerD(this, 2, 0, 200000000); + break; + } + } +} + +void VRadioRec::drawBarClocks() +{ + Log* logger = Log::getInstance(); + logger->log("VRadioRec", Log::DEBUG, "Draw bar clocks"); + + // Draw RTC + // Blank the area first + rectangle(barRegion.x + 624, barRegion.y + 12, 60, 30, barBlue); + char timeString[20]; + time_t t; + time(&t); + struct tm* tms = localtime(&t); + strftime(timeString, 19, "%H:%M", tms); + drawText(timeString, barRegion.x + 624, barRegion.y + 12, Colour::LIGHTTEXT); + + // Draw clocks + + rectangle(clocksRegion, barBlue); + +/* + ULONG currentFrameNum = player->getCurrentFrameNum(); + ULONG lengthFrames; + if (myRec->recInfo->timerEnd > time(NULL)) + { + // chasing playback + // Work out an approximate length in frames (good to 1s...) + lengthFrames = (myRec->recInfo->timerEnd - myRec->recInfo->timerStart) * video->getFPS(); + } + else + { +// lengthFrames = player->getLengthFrames(); + lengthFrames = 0; + } + + hmsf currentFrameHMSF = video->framesToHMSF(currentFrameNum); + hmsf lengthHMSF = video->framesToHMSF(lengthFrames); + + char buffer[100]; + if (currentFrameNum >= lengthFrames) + { + strcpy(buffer, "-:--:-- / -:--:--"); + } + else + { + SNPRINTF(buffer, 99, "%01i:%02i:%02i / %01i:%02i:%02i", currentFrameHMSF.hours, currentFrameHMSF.minutes, currentFrameHMSF.seconds, lengthHMSF.hours, lengthHMSF.minutes, lengthHMSF.seconds); + logger->log("VRadioRec", Log::DEBUG, buffer); + } +*/ + + ULONG currentSeconds = player->getCurrentSeconds(); + ULONG lengthSeconds = player->getLengthSeconds(); + char buffer[100]; + + if (lengthSeconds && (currentSeconds < lengthSeconds)) + { + ULONG dcurrentSeconds = currentSeconds; + ULONG dlengthSeconds = lengthSeconds; + + ULONG currentHours = dcurrentSeconds / 3600; + dcurrentSeconds %= 3600; + ULONG currentMinutes = dcurrentSeconds / 60; + dcurrentSeconds %= 60; + + ULONG lengthHours = dlengthSeconds / 3600; + dlengthSeconds %= 3600; + ULONG lengthMinutes = dlengthSeconds / 60; + dlengthSeconds %= 60; + + SNPRINTF(buffer, 99, "%01lu:%02lu:%02lu / %01lu:%02lu:%02lu", currentHours, currentMinutes, dcurrentSeconds, lengthHours, lengthMinutes, dlengthSeconds); + logger->log("VRadioRec", Log::DEBUG, buffer); + } + else + { + strcpy(buffer, "-:--:-- / -:--:--"); + } + + drawText(buffer, clocksRegion.x, clocksRegion.y, Colour::LIGHTTEXT); + + + + + // Draw progress bar + int progBarXbase = barRegion.x + 300; + + rectangle(barRegion.x + progBarXbase, barRegion.y + 12, 310, 24, Colour::LIGHTTEXT); + rectangle(barRegion.x + progBarXbase + 2, barRegion.y + 14, 306, 20, barBlue); + + if (currentSeconds > lengthSeconds) return; + if (lengthSeconds == 0) return; + + // Draw yellow portion + int progressWidth = 302 * currentSeconds / lengthSeconds; + rectangle(barRegion.x + progBarXbase + 4, barRegion.y + 16, progressWidth, 16, Colour::SELECTHIGHLIGHT); +/* + + if (myRec->recInfo->timerEnd > time(NULL)) // if chasing + { + int nrWidth = (int)(302 * ((double)(lengthFrames - 0) / lengthFrames)); // 0 inserted instead of getlengthframes + + Log::getInstance()->log("GVASDF", Log::DEBUG, "Length Frames: %lu", lengthFrames); +// Log::getInstance()->log("GVASDF", Log::DEBUG, "Player lf: %lu", player->getLengthFrames()); + Log::getInstance()->log("GVASDF", Log::DEBUG, "NR WDITH: %i", nrWidth); + rectangle(barRegion.x + progBarXbase + 4 + 302 - nrWidth, barRegion.y + 16, nrWidth, 16, Colour::RED); + } +*/ + + logger->log("VRadioRec", Log::DEBUG, "blips"); + + // Now calc position for start margin blips + int posPix; + + posPix = 302 * startMargin / lengthSeconds; + logger->log("VRadioRec", Log::DEBUG, "posPix %i", posPix); + + rectangle(barRegion.x + progBarXbase + 2 + posPix, barRegion.y + 12 - 2, 2, 2, Colour::LIGHTTEXT); + rectangle(barRegion.x + progBarXbase + 2 + posPix, barRegion.y + 12 + 24, 2, 2, Colour::LIGHTTEXT); + + posPix = 302 * (lengthSeconds - endMargin) / lengthSeconds; + + rectangle(barRegion.x + progBarXbase + 2 + posPix, barRegion.y + 12 - 2, 2, 2, Colour::LIGHTTEXT); + rectangle(barRegion.x + progBarXbase + 2 + posPix, barRegion.y + 12 + 24, 2, 2, Colour::LIGHTTEXT); +} + +void VRadioRec::removeBar() +{ + if (!barShowing) return; + timers->cancelTimer(this, 2); + barShowing = false; + rectangle(barRegion, Colour::BLACK); + ViewMan::getInstance()->updateView(this, &barRegion); +} diff --git a/vradiorec.h b/vradiorec.h new file mode 100644 index 0000000..d7f9aa6 --- /dev/null +++ b/vradiorec.h @@ -0,0 +1,76 @@ +/* + Copyright 2004-2006 Chris Tallon + + This file is part of VOMP. + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef VRADIOREC_H +#define VRADIOREC_H + +#include + +#include "video.h" +#include "view.h" +#include "playerradio.h" +#include "vdr.h" +#include "recording.h" +#include "command.h" +#include "colour.h" +#include "osd.h" +#include "timers.h" +#include "timerreceiver.h" +#include "message.h" + +class Timers; + +class VRadioRec : public View, public TimerReceiver +{ + public: + VRadioRec(Recording* rec); + ~VRadioRec(); + void draw(); + int handleCommand(int command); + void go(); + + void timercall(int clientReference); + void processMessage(Message* m); + + private: + VDR* vdr; + Video* video; + Timers* timers; + PlayerRadio* player; + Recording* myRec; + + Colour barBlue; + + bool playing; + bool barShowing; + bool stickyBar; + + void doBar(int action); + void drawBarClocks(); + void stopPlay(); + void removeBar(); + Region barRegion; + Region clocksRegion; + + UINT startMargin; + UINT endMargin; +}; + +#endif diff --git a/vrecordinglist.cc b/vrecordinglist.cc index 822de8b..c3edd7b 100644 --- a/vrecordinglist.cc +++ b/vrecordinglist.cc @@ -297,11 +297,22 @@ int VRecordingList::doPlay(bool resume) if (toPlay) { toPlay->loadRecInfo(); - VVideoRec* vidrec = new VVideoRec(toPlay); - vidrec->draw(); - viewman->add(vidrec); - viewman->updateView(vidrec); - vidrec->go(resume); + if (toPlay->recInfo->hasVideo()) + { + VVideoRec* vidrec = new VVideoRec(toPlay); + vidrec->draw(); + viewman->add(vidrec); + viewman->updateView(vidrec); + vidrec->go(resume); + } + else + { + VRadioRec* radrec = new VRadioRec(toPlay); + radrec->draw(); + viewman->add(radrec); + viewman->updateView(radrec); + radrec->go(); + } return 1; } // should not get to here diff --git a/vrecordinglist.h b/vrecordinglist.h index 007c8e9..b5355cc 100644 --- a/vrecordinglist.h +++ b/vrecordinglist.h @@ -38,6 +38,7 @@ #include "vrecordingmenu.h" #include "vdr.h" #include "vvideorec.h" +#include "vradiorec.h" #include "colour.h" #include "video.h" #include "i18n.h" diff --git a/vvideorec.h b/vvideorec.h index bebf4e9..0259dee 100644 --- a/vvideorec.h +++ b/vvideorec.h @@ -23,6 +23,7 @@ #include +#include "video.h" #include "view.h" #include "player.h" #include "vdr.h" -- 2.39.5