CPPMyth
Library to interoperate with MythTV server
mythlivetvplayback.cpp
1 /*
2  * Copyright (C) 2014 Jean-Luc Barriere
3  *
4  * This Program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2, or (at your option)
7  * any later version.
8  *
9  * This Program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; see the file COPYING. If not, write to
16  * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston,
17  * MA 02110-1301 USA
18  * http://www.gnu.org/copyleft/gpl.html
19  *
20  */
21 
22 #include "mythlivetvplayback.h"
23 #include "private/debug.h"
24 #include "private/socket.h"
25 #include "private/os/threads/mutex.h"
26 #include "private/os/threads/timeout.h"
27 #include "private/builtin.h"
28 
29 #include <limits>
30 #include <cstdio>
31 #include <cstdlib>
32 
33 #define MIN_TUNE_DELAY 5
34 #define MAX_TUNE_DELAY 60
35 #define TICK_USEC 100000 // valid range: 10000 - 999999
36 #define START_TIMEOUT 2000 // millisec
37 #define AHEAD_TIMEOUT 10000 // millisec
38 #define BREAK_TIMEOUT 4000 // millisec
39 
40 using namespace Myth;
41 
46 
47 LiveTVPlayback::LiveTVPlayback(EventHandler& handler)
48 : ProtoMonitor(handler.GetServer(), handler.GetPort()), EventSubscriber()
49 , m_eventHandler(handler)
50 , m_eventSubscriberId(0)
51 , m_tuneDelay(MIN_TUNE_DELAY)
52 , m_limitTuneAttempts(true)
53 , m_recorder()
54 , m_signal()
55 , m_chain()
56 , m_chunk(MYTH_LIVETV_CHUNK_SIZE)
57 {
58  m_buffer.pos = 0;
59  m_buffer.len = 0;
60  m_buffer.data = new unsigned char[m_chunk];
61  m_eventSubscriberId = m_eventHandler.CreateSubscription(this);
62  m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_SIGNAL);
63  m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_LIVETV_CHAIN);
64  m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_LIVETV_WATCH);
65  m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_DONE_RECORDING);
66  m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_UPDATE_FILE_SIZE);
67  Open();
68 }
69 
70 LiveTVPlayback::LiveTVPlayback(const std::string& server, unsigned port)
71 : ProtoMonitor(server, port), EventSubscriber()
72 , m_eventHandler(server, port)
73 , m_eventSubscriberId(0)
74 , m_tuneDelay(MIN_TUNE_DELAY)
75 , m_recorder()
76 , m_signal()
77 , m_chain()
78 , m_chunk(MYTH_LIVETV_CHUNK_SIZE)
79 {
80  m_buffer.pos = 0;
81  m_buffer.len = 0;
82  m_buffer.data = new unsigned char[m_chunk];
83  // Private handler will be stopped and closed by destructor.
84  m_eventSubscriberId = m_eventHandler.CreateSubscription(this);
85  m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_SIGNAL);
86  m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_LIVETV_CHAIN);
87  m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_LIVETV_WATCH);
88  m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_DONE_RECORDING);
89  m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_UPDATE_FILE_SIZE);
90  Open();
91 }
92 
93 LiveTVPlayback::~LiveTVPlayback()
94 {
95  if (m_eventSubscriberId)
96  m_eventHandler.RevokeSubscription(m_eventSubscriberId);
97  Close();
98  delete[] m_buffer.data;
99 }
100 
101 bool LiveTVPlayback::Open()
102 {
103  // Begin critical section
104  OS::CLockGuard lock(*m_mutex);
105  if (ProtoMonitor::IsOpen())
106  return true;
107  if (ProtoMonitor::Open())
108  {
109  if (!m_eventHandler.IsRunning())
110  {
111  OS::CTimeout timeout(START_TIMEOUT);
112  m_eventHandler.Start();
113  do
114  {
115  usleep(TICK_USEC);
116  }
117  while (!m_eventHandler.IsConnected() && timeout.TimeLeft() > 0);
118  if (!m_eventHandler.IsConnected())
119  DBG(DBG_WARN, "%s: event handler is not connected in time\n", __FUNCTION__);
120  else
121  DBG(DBG_DEBUG, "%s: event handler is connected\n", __FUNCTION__);
122  }
123  return true;
124  }
125  return false;
126 }
127 
128 void LiveTVPlayback::Close()
129 {
130  // Begin critical section
131  OS::CLockGuard lock(*m_mutex);
132  m_recorder.reset();
133  ProtoMonitor::Close();
134 }
135 
136 void LiveTVPlayback::SetTuneDelay(unsigned delay)
137 {
138  if (delay < MIN_TUNE_DELAY)
139  m_tuneDelay = MIN_TUNE_DELAY;
140  else if (delay > MAX_TUNE_DELAY)
141  m_tuneDelay = MAX_TUNE_DELAY;
142  else
143  m_tuneDelay = delay;
144 }
145 
146 void LiveTVPlayback::SetLimitTuneAttempts(bool limit)
147 {
148  // true : Try first tunable card in prefered order
149  // false: Try all tunable cards in prefered order
150  m_limitTuneAttempts = limit;
151 }
152 
153 bool LiveTVPlayback::SpawnLiveTV(const std::string& chanNum, const ChannelList& channels)
154 {
155  // Begin critical section
156  OS::CLockGuard lock(*m_mutex);
157  if (!ProtoMonitor::IsOpen() || !m_eventHandler.IsConnected())
158  {
159  DBG(DBG_ERROR, "%s: not connected\n", __FUNCTION__);
160  return false;
161  }
162 
163  StopLiveTV();
164  preferredCards_t preferredCards = FindTunableCardIds(chanNum, channels);
165  preferredCards_t::const_iterator card = preferredCards.begin();
166  while (card != preferredCards.end())
167  {
168  InitChain(); // Setup chain
169  const CardInputPtr& input = card->second.first;
170  const ChannelPtr& channel = card->second.second;
171  DBG(DBG_DEBUG, "%s: trying recorder num (%" PRIu32 ") channum (%s)\n", __FUNCTION__, input->cardId, channel->chanNum.c_str());
172  m_recorder = GetRecorderFromNum((int) input->cardId);
173  // Setup the chain
174  m_chain.switchOnCreate = true;
175  m_chain.watch = true;
176  if (m_recorder->SpawnLiveTV(m_chain.UID, channel->chanNum))
177  {
178  // Wait chain update until time limit
179  uint32_t delayMs = m_tuneDelay * 1000;
180  OS::CTimeout timeout(delayMs);
181  do
182  {
183  lock.Unlock(); // Release the latch to allow chain update
184  usleep(TICK_USEC);
185  lock.Lock();
186  if (!m_chain.switchOnCreate)
187  {
188  DBG(DBG_DEBUG, "%s: tune delay (%" PRIu32 "ms)\n", __FUNCTION__, (delayMs - timeout.TimeLeft()));
189  return true;
190  }
191  }
192  while (timeout.TimeLeft() > 0);
193  DBG(DBG_ERROR, "%s: tune delay exceeded (%" PRIu32 "ms)\n", __FUNCTION__, delayMs);
194  m_recorder->StopLiveTV();
195  }
196  ClearChain();
197  // Check if we need to stop after first attempt at tuning
198  if (m_limitTuneAttempts)
199  {
200  DBG(DBG_DEBUG, "%s: limiting tune attempts to first tunable card\n", __FUNCTION__);
201  break;
202  }
203  // Retry the next preferred card
204  ++card;
205  }
206  return false;
207 }
208 
209 bool LiveTVPlayback::SpawnLiveTV(const ChannelPtr& thisChannel)
210 {
211  ChannelList list;
212  list.push_back(thisChannel);
213  return SpawnLiveTV(thisChannel->chanNum, list);
214 }
215 
216 void LiveTVPlayback::StopLiveTV()
217 {
218  // Begin critical section
219  OS::CLockGuard lock(*m_mutex);
220  if (m_recorder && m_recorder->IsPlaying())
221  {
222  m_recorder->StopLiveTV();
223  // If recorder is keeping recording then release it to clear my instance status.
224  // Otherwise next program would be considered as preserved.
225  if (m_recorder->IsLiveRecording())
226  m_recorder.reset();
227  }
228 }
229 
230 void LiveTVPlayback::InitChain()
231 {
232  char buf[32];
233  // Begin critical section
234  OS::CLockGuard lock(*m_mutex);
235  time_to_iso8601(time(NULL), buf);
236  m_chain.UID = m_socket->GetMyHostName();
237  m_chain.UID.append("-").append(buf);
238  m_chain.currentSequence = 0;
239  m_chain.lastSequence = 0;
240  m_chain.watch = false;
241  m_chain.switchOnCreate = true;
242  m_chain.chained.clear();
243  m_chain.currentTransfer.reset();
244 }
245 
246 void LiveTVPlayback::ClearChain()
247 {
248  // Begin critical section
249  OS::CLockGuard lock(*m_mutex);
250  m_chain.currentSequence = 0;
251  m_chain.lastSequence = 0;
252  m_chain.watch = false;
253  m_chain.switchOnCreate = false;
254  m_chain.chained.clear();
255  m_chain.currentTransfer.reset();
256 }
257 
258 bool LiveTVPlayback::IsChained(const Program& program)
259 {
260  for (chained_t::const_iterator it = m_chain.chained.begin(); it != m_chain.chained.end(); ++it)
261  {
262  if (it->first && it->first->GetPathName() == program.fileName)
263  return true;
264  }
265  return false;
266 }
267 
268 void LiveTVPlayback::HandleChainUpdate()
269 {
270  OS::CLockGuard lock(*m_mutex); // Lock chain
271  ProtoRecorderPtr recorder(m_recorder);
272  if (!recorder)
273  return;
274  ProgramPtr prog = recorder->GetCurrentRecording();
275  /*
276  * If program file doesn't exist in the recorder chain then create a new
277  * transfer and add it to the chain.
278  */
279  if (prog && !prog->fileName.empty() && !IsChained(*prog))
280  {
281  DBG(DBG_DEBUG, "%s: liveTV (%s): adding new transfer %s\n", __FUNCTION__,
282  m_chain.UID.c_str(), prog->fileName.c_str());
283  ProtoTransferPtr transfer(new ProtoTransfer(recorder->GetServer(), recorder->GetPort(), prog->fileName, prog->recording.storageGroup));
284  // Pop previous dummy file if exists then add the new into the chain
285  if (m_chain.lastSequence && m_chain.chained[m_chain.lastSequence - 1].first->GetSize() == 0)
286  {
287  --m_chain.lastSequence;
288  m_chain.chained.pop_back();
289  }
290  m_chain.chained.push_back(std::make_pair(transfer, prog));
291  m_chain.lastSequence = m_chain.chained.size();
292  /*
293  * If switchOnCreate flag and file is filled then switch immediatly.
294  * Else we will switch later on the next event 'UPDATE_FILE_SIZE'
295  */
296  if (m_chain.switchOnCreate && transfer->GetSize() > 0 && SwitchChainLast())
297  m_chain.switchOnCreate = false;
298  m_chain.watch = false; // Chain update done. Restore watch flag
299  DBG(DBG_DEBUG, "%s: liveTV (%s): chain last (%u), watching (%u)\n", __FUNCTION__,
300  m_chain.UID.c_str(), m_chain.lastSequence, m_chain.currentSequence);
301  }
302 }
303 
304 bool LiveTVPlayback::SwitchChain(unsigned sequence)
305 {
306  OS::CLockGuard lock(*m_mutex);
307  // Check for out of range
308  if (sequence < 1 || sequence > m_chain.lastSequence)
309  return false;
310  // If closed then try to open
311  if (!m_chain.chained[sequence - 1].first->IsOpen() && !m_chain.chained[sequence - 1].first->Open())
312  return false;
313  m_chain.currentTransfer = m_chain.chained[sequence - 1].first;
314  m_chain.currentSequence = sequence;
315  DBG(DBG_DEBUG, "%s: switch to file (%u) %s\n", __FUNCTION__,
316  (unsigned)m_chain.currentTransfer->GetFileId(), m_chain.currentTransfer->GetPathName().c_str());
317  return true;
318 }
319 
320 bool LiveTVPlayback::SwitchChainLast()
321 {
322  if (SwitchChain(m_chain.lastSequence))
323  {
324  ProtoRecorderPtr recorder(m_recorder);
325  ProtoTransferPtr transfer(m_chain.currentTransfer);
326  if (recorder && transfer && recorder->TransferSeek(*transfer, 0, WHENCE_SET) == 0)
327  return true;
328  }
329  return false;
330 }
331 
332 void LiveTVPlayback::HandleBackendMessage(EventMessagePtr msg)
333 {
334  ProtoRecorderPtr recorder(m_recorder);
335  if (!recorder || !recorder->IsPlaying())
336  return;
337  switch (msg->event)
338  {
339  /*
340  * Event: LIVETV_CHAIN UPDATE
341  *
342  * Called in response to the backend's notification of a chain update.
343  * The recorder is supplied and will be queried for the current recording
344  * to determine if a new file needs to be added to the chain of files
345  * in the live tv instance.
346  */
347  case EVENT_LIVETV_CHAIN:
348  if (msg->subject.size() >= 3)
349  {
350  if (msg->subject[1] == "UPDATE" && msg->subject[2] == m_chain.UID)
351  HandleChainUpdate();
352  }
353  break;
354  /*
355  * Event: LIVETV_WATCH
356  *
357  * Called in response to the backend's notification of a livetv watch.
358  * The recorder is supplied and will be updated for the watch signal.
359  * This event is used to manage program breaks while watching live tv.
360  * When the guide data marks the end of one show and the beginning of
361  * the next, which will be recorded to a new file, this instructs the
362  * frontend to terminate the existing playback, and change channel to
363  * the new file. Before updating livetv chain and switching to new file
364  * we must to wait for event DONE_RECORDING that informs the current
365  * show is completed. Then we will call livetv chain update to get
366  * current program info. Watch signal will be down during this period.
367  */
368  case EVENT_LIVETV_WATCH:
369  if (msg->subject.size() >= 3)
370  {
371  int32_t rnum;
372  int8_t flag;
373  if (string_to_int32(msg->subject[1].c_str(), &rnum) == 0 && string_to_int8(msg->subject[2].c_str(), &flag) == 0)
374  {
375  if (recorder->GetNum() == (int)rnum)
376  {
377  OS::CLockGuard lock(*m_mutex); // Lock chain
378  m_chain.watch = true;
379  }
380  }
381  }
382  break;
383  /*
384  * Event: DONE_RECORDING
385  *
386  * Indicates that an active recording has completed on the specified
387  * recorder. used to manage program breaks while watching live tv.
388  * When receive event for recorder, we force an update of livetv chain
389  * to get current program info when chain is not yet updated.
390  * Watch signal is used when up, to mark the break period and
391  * queuing the frontend for reading file buffer.
392  */
393  case EVENT_DONE_RECORDING:
394  if (msg->subject.size() >= 2)
395  {
396  int32_t rnum;
397  if (string_to_int32(msg->subject[1].c_str(), &rnum) == 0 && recorder->GetNum() == (int)rnum)
398  {
399  // Recorder is not subscriber. So callback event to it
400  recorder->DoneRecordingCallback();
401  // Manage program break
402  if (m_chain.watch)
403  {
404  /*
405  * Last recording is now completed but watch signal is ON.
406  * Then force live tv chain update for the new current
407  * program. We will retry for a short period before returning.
408  */
409  OS::CTimeout timeout(BREAK_TIMEOUT);
410  do
411  {
412  usleep(500000); // wait for 500 ms
413  HandleChainUpdate();
414  }
415  while (m_chain.watch && timeout.TimeLeft() > 0);
416  }
417  }
418  }
419  break;
420  case EVENT_UPDATE_FILE_SIZE:
421  if (msg->subject.size() >= 3)
422  {
423  OS::CLockGuard lock(*m_mutex); // Lock chain
424  if (m_chain.lastSequence > 0)
425  {
426  int64_t newsize;
427  // Message contains chanid + starttime as recorded key
428  if (msg->subject.size() >= 4)
429  {
430  uint32_t chanid;
431  time_t startts;
432  if (string_to_uint32(msg->subject[1].c_str(), &chanid)
433  || string_to_time(msg->subject[2].c_str(), &startts)
434  || m_chain.chained[m_chain.lastSequence -1].second->channel.chanId != chanid
435  || m_chain.chained[m_chain.lastSequence -1].second->recording.startTs != startts
436  || string_to_int64(msg->subject[3].c_str(), &newsize)
437  || m_chain.chained[m_chain.lastSequence - 1].first->GetSize() >= newsize)
438  break;
439  }
440  // Message contains recordedid as key
441  else
442  {
443  uint32_t recordedid;
444  if (string_to_uint32(msg->subject[1].c_str(), &recordedid)
445  || m_chain.chained[m_chain.lastSequence -1].second->recording.recordedId != recordedid
446  || string_to_int64(msg->subject[2].c_str(), &newsize)
447  || m_chain.chained[m_chain.lastSequence - 1].first->GetSize() >= newsize)
448  break;
449  }
450  // Update transfer file size
451  m_chain.chained[m_chain.lastSequence - 1].first->SetSize(newsize);
452  // Is wait the filling before switching ?
453  if (m_chain.switchOnCreate && SwitchChainLast())
454  m_chain.switchOnCreate = false;
455  DBG(DBG_DEBUG, "%s: liveTV (%s): chain last (%u) filesize %" PRIi64 "\n", __FUNCTION__,
456  m_chain.UID.c_str(), m_chain.lastSequence, newsize);
457  }
458  }
459  break;
460  case EVENT_SIGNAL:
461  if (msg->subject.size() >= 2)
462  {
463  int32_t rnum;
464  if (string_to_int32(msg->subject[1].c_str(), &rnum) == 0 && recorder->GetNum() == (int)rnum)
465  m_signal = msg->signal;
466  }
467  break;
468  //case EVENT_HANDLER_STATUS:
469  // if (msg->subject[0] == EVENTHANDLER_DISCONNECTED)
470  // closeTransfer();
471  // break;
472  default:
473  break;
474  }
475 }
476 
477 void LiveTVPlayback::SetChunk(unsigned size)
478 {
479  if (size < MYTH_LIVETV_CHUNK_MIN)
480  size = MYTH_LIVETV_CHUNK_MIN;
481  else if (size > MYTH_LIVETV_CHUNK_MAX)
482  size = MYTH_LIVETV_CHUNK_MAX;
483 
484  m_buffer.pos = m_buffer.len = 0;
485  delete[] m_buffer.data;
486  m_buffer.data = new unsigned char[size];
487  m_chunk = size;
488 }
489 
490 int64_t LiveTVPlayback::GetSize() const
491 {
492  int64_t size = 0;
493  OS::CLockGuard lock(*m_mutex); // Lock chain
494  for (chained_t::const_iterator it = m_chain.chained.begin(); it != m_chain.chained.end(); ++it)
495  size += it->first->GetSize();
496  return size;
497 }
498 
499 int LiveTVPlayback::Read(void* buffer, unsigned n)
500 {
501  int c = 0;
502  bool refill = true;
503  for (;;)
504  {
505  // all requested data are in the buffer
506  if (m_buffer.len >= n)
507  {
508  memcpy(static_cast<unsigned char*>(buffer) + c, m_buffer.data + m_buffer.pos, n);
509  c += n;
510  m_buffer.pos += n;
511  m_buffer.len -= n;
512  return c;
513  }
514  // fill with the rest of data before read a new chunk
515  if (m_buffer.len > 0)
516  {
517  memcpy(static_cast<unsigned char*>(buffer) + c, m_buffer.data + m_buffer.pos, m_buffer.len);
518  c += m_buffer.len;
519  n -= m_buffer.len;
520  m_buffer.len = 0;
521  }
522  if (!refill)
523  break;
524  m_buffer.pos = 0;
525  int r = _read(m_buffer.data, m_chunk);
526  if (r < 0)
527  return -1;
528  m_buffer.len += r;
529  refill = false; // won't read again
530  }
531  return c;
532 }
533 
534 int LiveTVPlayback::_read(void* buffer, unsigned n)
535 {
536  int r = 0;
537  bool retry;
538  int64_t s, fp;
539 
540  // Begin critical section
541  // First of all i hold my shared resources using copies
542  ProtoRecorderPtr recorder(m_recorder);
543  if (!m_chain.currentTransfer || !recorder)
544  return -1;
545 
546  fp = m_chain.currentTransfer->GetPosition();
547 
548  do
549  {
550  retry = false;
551  s = m_chain.currentTransfer->GetRemaining(); // Acceptable block size
552  if (s == 0)
553  {
554  OS::CTimeout timeout(AHEAD_TIMEOUT);
555  for (;;)
556  {
557  // Reading ahead
558  if (m_chain.currentSequence == m_chain.lastSequence)
559  {
560  int64_t rp = recorder->GetFilePosition();
561  if (rp > fp)
562  {
563  m_chain.currentTransfer->SetSize(rp);
564  retry = true;
565  break;
566  }
567  if (!timeout.TimeLeft())
568  {
569  DBG(DBG_WARN, "%s: read position is ahead (%" PRIi64 ")\n", __FUNCTION__, fp);
570  return 0;
571  }
572  usleep(500000);
573  }
574  // Switch next file transfer is required to continue
575  else
576  {
577  if (!SwitchChain(m_chain.currentSequence + 1))
578  return -1;
579  if (m_chain.currentTransfer->GetPosition() != 0)
580  recorder->TransferSeek(*(m_chain.currentTransfer), 0, WHENCE_SET);
581  DBG(DBG_DEBUG, "%s: liveTV (%s): chain last (%u), watching (%u)\n", __FUNCTION__,
582  m_chain.UID.c_str(), m_chain.lastSequence, m_chain.currentSequence);
583  retry = true;
584  break;
585  }
586  }
587  }
588  else if (s < 0)
589  return -1;
590  }
591  while (retry);
592 
593  if (s < (int64_t)n)
594  n = (unsigned)s ;
595 
596  r = recorder->TransferRequestBlock(*(m_chain.currentTransfer), buffer, n);
597  return r;
598 }
599 
600 int64_t LiveTVPlayback::Seek(int64_t offset, WHENCE_t whence)
601 {
602  if (whence == WHENCE_CUR)
603  {
604  if (offset == 0)
605  {
606  int64_t p = _seek(offset, whence);
607  // it returns the current position of the first byte in buffer
608  return (p >= m_buffer.len ? p - m_buffer.len : p);
609  }
610  // rebase to the first position in the buffer
611  offset -= m_buffer.len;
612  }
613  m_buffer.len = 0; // clear data in buffer
614  return _seek(offset, whence);
615 }
616 
617 int64_t LiveTVPlayback::_seek(int64_t offset, WHENCE_t whence)
618 {
619  OS::CLockGuard lock(*m_mutex); // Lock chain
620  if (!m_recorder || !m_chain.currentSequence)
621  return -1;
622 
623  unsigned ci = m_chain.currentSequence - 1; // current sequence index
624  int64_t size = GetSize(); // total stream size
625  int64_t position = GetPosition(); // absolute position in stream
626  int64_t p = 0;
627  switch (whence)
628  {
629  case WHENCE_SET:
630  p = offset;
631  break;
632  case WHENCE_END:
633  p = size + offset;
634  break;
635  case WHENCE_CUR:
636  p = position + offset;
637  break;
638  default:
639  return -1;
640  }
641  if (p > size || p < 0)
642  {
643  DBG(DBG_WARN, "%s: invalid seek (%" PRId64 ")\n", __FUNCTION__, p);
644  return -1;
645  }
646  if (p > position)
647  {
648  for (;;)
649  {
650  if (position + m_chain.chained[ci].first->GetRemaining() >= p)
651  {
652  // Try seek file to desired position. On success switch chain
653  if (m_recorder->TransferSeek(*(m_chain.chained[ci].first), p - position, WHENCE_CUR) < 0 ||
654  !SwitchChain(++ci))
655  return -1;
656  return p;
657  }
658  position += m_chain.chained[ci].first->GetRemaining();
659  ++ci; // switch next
660  if (ci < m_chain.lastSequence)
661  position += m_chain.chained[ci].first->GetPosition();
662  else
663  return -1;
664  }
665  }
666  if (p < position)
667  {
668  for (;;)
669  {
670  if (position - m_chain.chained[ci].first->GetPosition() <= p)
671  {
672  // Try seek file to desired position. On success switch chain
673  if (m_recorder->TransferSeek(*(m_chain.chained[ci].first), p - position, WHENCE_CUR) < 0 ||
674  !SwitchChain(++ci))
675  return -1;
676  return p;
677  }
678  position -= m_chain.chained[ci].first->GetPosition();
679  if (ci > 0)
680  {
681  --ci; // switch previous
682  position -= m_chain.chained[ci].first->GetRemaining();
683  }
684  else
685  return -1;
686  }
687  }
688  // p == position
689  return p;
690 }
691 
692 int64_t LiveTVPlayback::GetPosition() const
693 {
694  int64_t pos = 0;
695  OS::CLockGuard lock(*m_mutex); // Lock chain
696  if (m_chain.currentSequence)
697  {
698  unsigned s = m_chain.currentSequence - 1;
699  for (unsigned i = 0; i < s; ++i)
700  pos += m_chain.chained[i].first->GetSize();
701  pos += m_chain.currentTransfer->GetPosition();
702  }
703  // it returns the current position of first byte in buffer
704  return pos - m_buffer.len;
705 }
706 
707 bool LiveTVPlayback::IsPlaying() const
708 {
709  ProtoRecorderPtr recorder(m_recorder);
710  return (recorder ? recorder->IsPlaying() : false);
711 }
712 
713 bool LiveTVPlayback::IsLiveRecording() const
714 {
715  ProtoRecorderPtr recorder(m_recorder);
716  return (recorder ? recorder->IsLiveRecording() : false);
717 }
718 
719 bool LiveTVPlayback::KeepLiveRecording(bool keep)
720 {
721  ProtoRecorderPtr recorder(m_recorder);
722  // Begin critical section
723  OS::CLockGuard lock(*m_mutex);
724  if (recorder && recorder->IsPlaying())
725  {
726  ProgramPtr prog = recorder->GetCurrentRecording();
727  if (prog)
728  {
729  if (keep)
730  {
731  if (UndeleteRecording(*prog) && recorder->SetLiveRecording(keep))
732  {
733  QueryGenpixmap(*prog);
734  return true;
735  }
736  }
737  else
738  {
739  if (recorder->SetLiveRecording(keep) && recorder->FinishRecording())
740  return true;
741  }
742  }
743  }
744  return false;
745 }
746 
747 ProgramPtr LiveTVPlayback::GetPlayedProgram() const
748 {
749  OS::CLockGuard lock(*m_mutex); // Lock chain
750  if (m_chain.currentSequence > 0)
751  return m_chain.chained[m_chain.currentSequence - 1].second;
752  return ProgramPtr();
753 }
754 
755 time_t LiveTVPlayback::GetLiveTimeStart() const
756 {
757  OS::CLockGuard lock(*m_mutex); // Lock chain
758  if (m_chain.lastSequence)
759  return m_chain.chained[0].second->recording.startTs;
760  return (time_t)(-1);
761 }
762 
763 unsigned LiveTVPlayback::GetChainedCount() const
764 {
765  OS::CLockGuard lock(*m_mutex); // Lock chain
766  return m_chain.lastSequence;
767 }
768 
769 ProgramPtr LiveTVPlayback::GetChainedProgram(unsigned sequence) const
770 {
771  OS::CLockGuard lock(*m_mutex); // Lock chain
772  if (sequence > 0 && sequence <= m_chain.lastSequence)
773  return m_chain.chained[sequence - 1].second;
774  return ProgramPtr();
775 }
776 
777 uint32_t LiveTVPlayback::GetCardId() const
778 {
779  ProtoRecorderPtr recorder(m_recorder);
780  return (recorder ? recorder->GetNum() : 0);
781 }
782 
783 SignalStatusPtr LiveTVPlayback::GetSignal() const
784 {
785  return (m_recorder ? m_signal : SignalStatusPtr());
786 }
787 
788 LiveTVPlayback::preferredCards_t LiveTVPlayback::FindTunableCardIds(const std::string& chanNum, const ChannelList& channels)
789 {
790  // Make the set of channels matching the desired channel number
791  ChannelList chanset;
792  for (ChannelList::const_iterator it = channels.begin(); it != channels.end(); ++it)
793  {
794  if ((*it)->chanNum == chanNum)
795  chanset.push_back(*it);
796  }
797  // Retrieve unlocked encoders and fill the list of preferred cards.
798  // It is ordered by its key liveTVOrder and contains matching between channels
799  // and card inputs using their respective sourceId and mplexId
800  preferredCards_t preferredCards;
801  CardInputListPtr inputs = GetFreeInputs(0);
802  for (CardInputList::const_iterator iti = inputs->begin(); iti != inputs->end(); ++iti)
803  {
804  for (ChannelList::const_iterator itchan = chanset.begin(); itchan != chanset.end(); ++itchan)
805  {
806  if ((*itchan)->sourceId == (*iti)->sourceId && ( (*iti)->mplexId == 0 || (*iti)->mplexId == (*itchan)->mplexId ))
807  {
808  preferredCards.insert(std::make_pair((*iti)->liveTVOrder, std::make_pair(*iti, *itchan)));
809  DBG(DBG_DEBUG, "%s: [%u] channel=%s(%" PRIu32 ") card=%" PRIu32 " input=%s(%" PRIu32 ") mplex=%" PRIu32 " source=%" PRIu32 "\n",
810  __FUNCTION__, (*iti)->liveTVOrder, (*itchan)->callSign.c_str(), (*itchan)->chanId,
811  (*iti)->cardId, (*iti)->inputName.c_str(), (*iti)->inputId, (*iti)->mplexId, (*iti)->sourceId);
812  break;
813  }
814  }
815  }
816  return preferredCards;
817 }
This is the main namespace that encloses all public classes.
Definition: mythcontrol.h:29