22#include "mythlivetvplayback.h"
23#include "private/debug.h"
24#include "private/socket.h"
25#include "private/ringbuffer.h"
26#include "private/os/threads/latch.h"
27#include "private/os/threads/timeout.h"
28#include "private/builtin.h"
34#define MIN_TUNE_DELAY 5
35#define MAX_TUNE_DELAY 60
36#define TICK_USEC 100000
37#define START_TIMEOUT 2000
38#define AHEAD_TIMEOUT 10000
39#define BREAK_TIMEOUT 4000
40#define BUFFER_CAPACITY 2
51, m_eventHandler(handler)
52, m_eventSubscriberId(0)
53, m_tuneDelay(MIN_TUNE_DELAY)
54, m_limitTuneAttempts(true)
58, m_chunk(MYTH_LIVETV_CHUNK_SIZE)
60 m_buffer.rbuf =
new RingBuffer(BUFFER_CAPACITY);
61 m_buffer.packet =
nullptr;
62 m_buffer.consumed = 0;
64 m_eventSubscriberId = m_eventHandler.CreateSubscription(
this);
65 m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_SIGNAL);
66 m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_LIVETV_CHAIN);
67 m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_LIVETV_WATCH);
68 m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_DONE_RECORDING);
69 m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_UPDATE_FILE_SIZE);
73LiveTVPlayback::LiveTVPlayback(
const std::string& server,
unsigned port)
75, m_eventHandler(server, port)
76, m_eventSubscriberId(0)
77, m_tuneDelay(MIN_TUNE_DELAY)
81, m_chunk(MYTH_LIVETV_CHUNK_SIZE)
83 m_buffer.rbuf =
new RingBuffer(BUFFER_CAPACITY);
84 m_buffer.packet =
nullptr;
85 m_buffer.consumed = 0;
88 m_eventSubscriberId = m_eventHandler.CreateSubscription(
this);
89 m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_SIGNAL);
90 m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_LIVETV_CHAIN);
91 m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_LIVETV_WATCH);
92 m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_DONE_RECORDING);
93 m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_UPDATE_FILE_SIZE);
97LiveTVPlayback::~LiveTVPlayback()
99 if (m_eventSubscriberId)
100 m_eventHandler.RevokeSubscription(m_eventSubscriberId);
103 m_buffer.rbuf->freePacket(m_buffer.packet);
104 delete m_buffer.rbuf;
107bool LiveTVPlayback::Open()
110 OS::WriteLock lock(*m_latch);
111 if (ProtoMonitor::IsOpen())
113 if (ProtoMonitor::Open())
115 if (!m_eventHandler.IsRunning())
117 OS::Timeout timeout(START_TIMEOUT);
118 m_eventHandler.Start();
123 while (!m_eventHandler.IsConnected() && timeout.time_left() > 0);
124 if (!m_eventHandler.IsConnected())
125 DBG(DBG_WARN,
"%s: event handler is not connected in time\n", __FUNCTION__);
127 DBG(DBG_DEBUG,
"%s: event handler is connected\n", __FUNCTION__);
134void LiveTVPlayback::Close()
137 OS::WriteLock lock(*m_latch);
139 ProtoMonitor::Close();
142void LiveTVPlayback::SetTuneDelay(
unsigned delay)
144 if (delay < MIN_TUNE_DELAY)
145 m_tuneDelay = MIN_TUNE_DELAY;
146 else if (delay > MAX_TUNE_DELAY)
147 m_tuneDelay = MAX_TUNE_DELAY;
152void LiveTVPlayback::SetLimitTuneAttempts(
bool limit)
156 m_limitTuneAttempts = limit;
159bool LiveTVPlayback::SpawnLiveTV(
const std::string& chanNum,
const ChannelList& channels)
162 OS::WriteLock lock(*m_latch);
163 if (!ProtoMonitor::IsOpen() || !m_eventHandler.IsConnected())
165 DBG(DBG_ERROR,
"%s: not connected\n", __FUNCTION__);
170 preferredCards_t preferredCards = FindTunableCardIds(chanNum, channels);
171 preferredCards_t::const_iterator card = preferredCards.begin();
172 while (card != preferredCards.end())
175 const CardInputPtr& input = card->second.first;
176 const ChannelPtr& channel = card->second.second;
177 DBG(DBG_DEBUG,
"%s: trying recorder num (%" PRIu32
") channum (%s)\n", __FUNCTION__, input->cardId, channel->chanNum.c_str());
178 m_recorder = GetRecorderFromNum((
int) input->cardId);
180 m_chain.switchOnCreate =
true;
181 m_chain.watch =
true;
182 if (m_recorder->SpawnLiveTV(m_chain.UID, channel->chanNum))
185 uint32_t delayMs = m_tuneDelay * 1000;
186 OS::Timeout timeout(delayMs);
192 if (!m_chain.switchOnCreate)
194 DBG(DBG_DEBUG,
"%s: tune delay (%" PRIu32
"ms)\n", __FUNCTION__, (delayMs - timeout.time_left()));
198 while (timeout.time_left() > 0);
199 DBG(DBG_ERROR,
"%s: tune delay exceeded (%" PRIu32
"ms)\n", __FUNCTION__, delayMs);
200 m_recorder->StopLiveTV();
204 if (m_limitTuneAttempts)
206 DBG(DBG_DEBUG,
"%s: limiting tune attempts to first tunable card\n", __FUNCTION__);
215bool LiveTVPlayback::SpawnLiveTV(
const ChannelPtr& thisChannel)
218 list.push_back(thisChannel);
219 return SpawnLiveTV(thisChannel->chanNum, list);
222void LiveTVPlayback::StopLiveTV()
225 OS::WriteLock lock(*m_latch);
226 if (m_recorder && m_recorder->IsPlaying())
228 m_recorder->StopLiveTV();
231 if (m_recorder->IsLiveRecording())
236void LiveTVPlayback::InitChain()
240 OS::WriteLock lock(*m_latch);
241 time_to_iso8601(time(0), &buf);
242 m_chain.UID = m_socket->GetMyHostName();
243 m_chain.UID.append(
"-").append(buf.data);
244 m_chain.currentSequence = 0;
245 m_chain.lastSequence = 0;
246 m_chain.watch =
false;
247 m_chain.switchOnCreate =
true;
248 m_chain.chained.clear();
249 m_chain.currentTransfer.reset();
252void LiveTVPlayback::ClearChain()
255 OS::WriteLock lock(*m_latch);
256 m_chain.currentSequence = 0;
257 m_chain.lastSequence = 0;
258 m_chain.watch =
false;
259 m_chain.switchOnCreate =
false;
260 m_chain.chained.clear();
261 m_chain.currentTransfer.reset();
264bool LiveTVPlayback::IsChained(
const Program& program)
266 OS::ReadLock lock(*m_latch);
267 for (chained_t::const_iterator it = m_chain.chained.begin(); it != m_chain.chained.end(); ++it)
269 if (it->first && it->first->GetPathName() == program.fileName)
275void LiveTVPlayback::HandleChainUpdate()
278 OS::WriteLock lock(*m_latch);
281 ProgramPtr prog = m_recorder->GetCurrentRecording();
286 if (prog && !prog->fileName.empty() && !IsChained(*prog))
288 DBG(DBG_DEBUG,
"%s: liveTV (%s): adding new transfer %s\n", __FUNCTION__,
289 m_chain.UID.c_str(), prog->fileName.c_str());
290 ProtoTransferPtr transfer(
new ProtoTransfer(m_recorder->GetServer(), m_recorder->GetPort(), prog->fileName, prog->recording.storageGroup));
292 if (m_chain.lastSequence && m_chain.chained[m_chain.lastSequence - 1].first->GetSize() == 0)
294 --m_chain.lastSequence;
295 m_chain.chained.pop_back();
297 m_chain.chained.push_back(std::make_pair(transfer, prog));
298 m_chain.lastSequence = m_chain.chained.size();
303 if (m_chain.switchOnCreate && transfer->GetSize() > 0 && SwitchChainLast())
304 m_chain.switchOnCreate =
false;
305 m_chain.watch =
false;
306 DBG(DBG_DEBUG,
"%s: liveTV (%s): chain last (%u), watching (%u)\n", __FUNCTION__,
307 m_chain.UID.c_str(), m_chain.lastSequence, m_chain.currentSequence);
311bool LiveTVPlayback::SwitchChain(
unsigned sequence)
313 OS::WriteLock lock(*m_latch);
315 if (sequence < 1 || sequence > m_chain.lastSequence)
318 if (!m_chain.chained[sequence - 1].first->IsOpen() && !m_chain.chained[sequence - 1].first->Open())
320 m_chain.currentTransfer = m_chain.chained[sequence - 1].first;
321 m_chain.currentSequence = sequence;
322 DBG(DBG_DEBUG,
"%s: switch to file (%u) %s\n", __FUNCTION__,
323 (
unsigned)m_chain.currentTransfer->GetFileId(), m_chain.currentTransfer->GetPathName().c_str());
327bool LiveTVPlayback::SwitchChainLast()
329 if (SwitchChain(m_chain.lastSequence))
331 OS::ReadLock lock(*m_latch);
332 if (m_recorder && m_chain.currentTransfer
333 && m_recorder->TransferSeek(*m_chain.currentTransfer, 0, WHENCE_SET) == 0)
339void LiveTVPlayback::HandleBackendMessage(EventMessagePtr msg)
342 m_latch->lock_shared();
343 ProtoRecorderPtr recorder(m_recorder);
344 m_latch->unlock_shared();
345 if (!recorder || !recorder->IsPlaying())
357 case EVENT_LIVETV_CHAIN:
358 if (msg->subject.size() >= 3)
360 if (msg->subject[1] ==
"UPDATE" && msg->subject[2] == m_chain.UID)
378 case EVENT_LIVETV_WATCH:
379 if (msg->subject.size() >= 3)
383 if (string_to_int32(msg->subject[1].c_str(), &rnum) == 0 && string_to_int8(msg->subject[2].c_str(), &flag) == 0)
385 if (recorder->GetNum() == (
int)rnum)
387 OS::WriteLock lock(*m_latch);
388 m_chain.watch =
true;
403 case EVENT_DONE_RECORDING:
404 if (msg->subject.size() >= 2)
407 if (string_to_int32(msg->subject[1].c_str(), &rnum) == 0 && recorder->GetNum() == (
int)rnum)
410 recorder->DoneRecordingCallback();
419 OS::Timeout timeout(BREAK_TIMEOUT);
425 while (m_chain.watch &&
426 timeout.time_left() > 0);
431 case EVENT_UPDATE_FILE_SIZE:
432 if (msg->subject.size() >= 3)
435 OS::WriteLock lock(*m_latch);
436 if (m_chain.lastSequence > 0)
440 if (msg->subject.size() >= 4)
444 if (string_to_uint32(msg->subject[1].c_str(), &chanid)
445 || string_to_time(msg->subject[2].c_str(), &startts)
446 || m_chain.chained[m_chain.lastSequence -1].second->channel.chanId != chanid
447 || m_chain.chained[m_chain.lastSequence -1].second->recording.startTs != startts
448 || string_to_int64(msg->subject[3].c_str(), &newsize)
449 || m_chain.chained[m_chain.lastSequence - 1].first->GetSize() >= newsize)
456 if (string_to_uint32(msg->subject[1].c_str(), &recordedid)
457 || m_chain.chained[m_chain.lastSequence -1].second->recording.recordedId != recordedid
458 || string_to_int64(msg->subject[2].c_str(), &newsize)
459 || m_chain.chained[m_chain.lastSequence - 1].first->GetSize() >= newsize)
463 m_chain.chained[m_chain.lastSequence - 1].first->SetSize(newsize);
465 if (m_chain.switchOnCreate && SwitchChainLast())
466 m_chain.switchOnCreate =
false;
467 DBG(DBG_DEBUG,
"%s: liveTV (%s): chain last (%u) filesize %" PRIi64
"\n", __FUNCTION__,
468 m_chain.UID.c_str(), m_chain.lastSequence, newsize);
473 if (msg->subject.size() >= 2)
476 if (string_to_int32(msg->subject[1].c_str(), &rnum) == 0 && recorder->GetNum() == (
int)rnum)
480 m_signal = msg->signal;
494void LiveTVPlayback::SetChunk(
unsigned size)
496 if (size < MYTH_LIVETV_CHUNK_MIN)
497 size = MYTH_LIVETV_CHUNK_MIN;
498 else if (size > MYTH_LIVETV_CHUNK_MAX)
499 size = MYTH_LIVETV_CHUNK_MAX;
503int64_t LiveTVPlayback::GetSize()
const
506 OS::ReadLock lock(*m_latch);
507 for (chained_t::const_iterator it = m_chain.chained.begin(); it != m_chain.chained.end(); ++it)
508 size += it->first->GetSize();
512int LiveTVPlayback::Read(
void* buffer,
unsigned n)
516 if (m_buffer.packet ==
nullptr)
519 m_buffer.packet = m_buffer.rbuf->read();
520 m_buffer.consumed = 0;
525 int s = m_buffer.packet->size - m_buffer.consumed;
526 int r = ((int)n < s ? (int)n : s);
527 memcpy(
static_cast<char*
>(buffer), m_buffer.packet->data + m_buffer.consumed, r);
528 m_buffer.consumed += r;
529 if (m_buffer.consumed >= m_buffer.packet->size)
531 m_buffer.rbuf->freePacket(m_buffer.packet);
532 m_buffer.packet =
nullptr;
538 RingBufferPacket * p = m_buffer.rbuf->newPacket(m_chunk);
539 int r = _read(p->data, m_chunk);
543 m_buffer.rbuf->writePacket(p);
546 m_buffer.rbuf->freePacket(p);
553int LiveTVPlayback::_read(
void* buffer,
unsigned n)
560 m_latch->lock_shared();
561 ProtoRecorderPtr recorder(m_recorder);
562 m_latch->unlock_shared();
563 if (!m_chain.currentTransfer || !recorder)
566 fp = m_chain.currentTransfer->GetPosition();
571 s = m_chain.currentTransfer->GetRemaining();
574 OS::Timeout timeout(AHEAD_TIMEOUT);
578 m_latch->lock_shared();
579 unsigned lastseq = m_chain.lastSequence;
580 m_latch->unlock_shared();
581 if (m_chain.currentSequence == lastseq)
583 int64_t rp = recorder->GetFilePosition();
586 m_chain.currentTransfer->SetSize(rp);
590 if (!timeout.time_left())
592 DBG(DBG_WARN,
"%s: read position is ahead (%" PRIi64
")\n", __FUNCTION__, fp);
600 if (!SwitchChain(m_chain.currentSequence + 1))
602 if (m_chain.currentTransfer->GetPosition() != 0)
603 recorder->TransferSeek(*(m_chain.currentTransfer), 0, WHENCE_SET);
604 DBG(DBG_DEBUG,
"%s: liveTV (%s): chain last (%u), watching (%u)\n", __FUNCTION__,
605 m_chain.UID.c_str(), lastseq, m_chain.currentSequence);
622 r = recorder->TransferRequestBlock(*(m_chain.currentTransfer), buffer, n);
626int64_t LiveTVPlayback::Seek(int64_t offset, WHENCE_t whence)
628 if (whence == WHENCE_CUR)
631 unsigned unread = m_buffer.rbuf->bytesUnread() + (m_buffer.packet ? m_buffer.packet->size - m_buffer.consumed : 0);
634 int64_t p = _seek(offset, whence);
636 return (p >= unread ? p - unread : p);
644 m_buffer.rbuf->freePacket(m_buffer.packet);
645 m_buffer.packet =
nullptr;
647 m_buffer.rbuf->clear();
649 return _seek(offset, whence);
652int64_t LiveTVPlayback::_seek(int64_t offset, WHENCE_t whence)
655 OS::WriteLock lock(*m_latch);
656 if (!m_recorder || !m_chain.currentSequence)
659 unsigned ci = m_chain.currentSequence - 1;
660 int64_t size = GetSize();
661 int64_t position = GetPosition();
672 p = position + offset;
677 if (p > size || p < 0)
679 DBG(DBG_WARN,
"%s: invalid seek (%" PRId64
")\n", __FUNCTION__, p);
686 if (position + m_chain.chained[ci].first->GetRemaining() >= p)
689 if (m_recorder->TransferSeek(*(m_chain.chained[ci].first), p - position, WHENCE_CUR) < 0 ||
694 position += m_chain.chained[ci].first->GetRemaining();
696 if (ci < m_chain.lastSequence)
697 position += m_chain.chained[ci].first->GetPosition();
706 if (position - m_chain.chained[ci].first->GetPosition() <= p)
709 if (m_recorder->TransferSeek(*(m_chain.chained[ci].first), p - position, WHENCE_CUR) < 0 ||
714 position -= m_chain.chained[ci].first->GetPosition();
718 position -= m_chain.chained[ci].first->GetRemaining();
728int64_t LiveTVPlayback::GetPosition()
const
731 OS::ReadLock lock(*m_latch);
732 if (m_chain.currentSequence)
734 unsigned s = m_chain.currentSequence - 1;
735 for (
unsigned i = 0; i < s; ++i)
736 pos += m_chain.chained[i].first->GetSize();
737 pos += m_chain.currentTransfer->GetPosition();
740 unsigned unread = m_buffer.rbuf->bytesUnread() + (m_buffer.packet ? m_buffer.packet->size - m_buffer.consumed : 0);
744bool LiveTVPlayback::IsPlaying()
const
746 OS::ReadLock lock(*m_latch);
747 return (m_recorder ? m_recorder->IsPlaying() :
false);
750bool LiveTVPlayback::IsLiveRecording()
const
752 OS::ReadLock lock(*m_latch);
753 return (m_recorder ? m_recorder->IsLiveRecording() :
false);
756bool LiveTVPlayback::KeepLiveRecording(
bool keep)
759 OS::WriteLock lock(*m_latch);
760 if (m_recorder && m_recorder->IsPlaying())
762 ProgramPtr prog = m_recorder->GetCurrentRecording();
767 if (UndeleteRecording(*prog) && m_recorder->SetLiveRecording(keep))
769 QueryGenpixmap(*prog);
775 if (m_recorder->SetLiveRecording(keep) && m_recorder->FinishRecording())
783ProgramPtr LiveTVPlayback::GetPlayedProgram()
const
785 OS::ReadLock lock(*m_latch);
786 if (m_chain.currentSequence > 0)
787 return m_chain.chained[m_chain.currentSequence - 1].second;
791time_t LiveTVPlayback::GetLiveTimeStart()
const
793 OS::ReadLock lock(*m_latch);
794 if (m_chain.lastSequence)
795 return m_chain.chained[0].second->recording.startTs;
799unsigned LiveTVPlayback::GetChainedCount()
const
801 OS::ReadLock lock(*m_latch);
802 return m_chain.lastSequence;
805ProgramPtr LiveTVPlayback::GetChainedProgram(
unsigned sequence)
const
807 OS::ReadLock lock(*m_latch);
808 if (sequence > 0 && sequence <= m_chain.lastSequence)
809 return m_chain.chained[sequence - 1].second;
813uint32_t LiveTVPlayback::GetCardId()
const
815 OS::ReadLock lock(*m_latch);
816 return (m_recorder ? m_recorder->GetNum() : 0);
819SignalStatusPtr LiveTVPlayback::GetSignal()
const
821 OS::ReadLock lock(*m_latch);
822 return (m_recorder ? m_signal : SignalStatusPtr());
825LiveTVPlayback::preferredCards_t LiveTVPlayback::FindTunableCardIds(
const std::string& chanNum,
const ChannelList& channels)
829 for (ChannelList::const_iterator it = channels.begin(); it != channels.end(); ++it)
831 if ((*it)->chanNum == chanNum)
832 chanset.push_back(*it);
837 preferredCards_t preferredCards;
839 for (CardInputList::const_iterator iti = inputs->begin(); iti != inputs->end(); ++iti)
841 for (ChannelList::const_iterator itchan = chanset.begin(); itchan != chanset.end(); ++itchan)
843 if ((*itchan)->sourceId == (*iti)->sourceId && ( (*iti)->mplexId == 0 || (*iti)->mplexId == (*itchan)->mplexId ))
845 preferredCards.insert(std::make_pair((*iti)->liveTVOrder, std::make_pair(*iti, *itchan)));
846 DBG(DBG_DEBUG,
"%s: [%u] channel=%s(%" PRIu32
") card=%" PRIu32
" input=%s(%" PRIu32
") mplex=%" PRIu32
" source=%" PRIu32
"\n",
847 __FUNCTION__, (*iti)->liveTVOrder, (*itchan)->callSign.c_str(), (*itchan)->chanId,
848 (*iti)->cardId, (*iti)->inputName.c_str(), (*iti)->inputId, (*iti)->mplexId, (*iti)->sourceId);
853 return preferredCards;
CardInputListPtr GetFreeInputs(int rnum=0)
Asks the backend for a list of free inputs. A free input is one that is connected but not busy,...
This is the main namespace that encloses all public classes.