CPPMyth
Library to interoperate with MythTV server
Loading...
Searching...
No Matches
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/ringbuffer.h"
26#include "private/os/threads/latch.h"
27#include "private/os/threads/timeout.h"
28#include "private/builtin.h"
29
30#include <limits>
31#include <cstdio>
32#include <cstdlib>
33
34#define MIN_TUNE_DELAY 5
35#define MAX_TUNE_DELAY 60
36#define TICK_USEC 100000 // valid range: 10000 - 999999
37#define START_TIMEOUT 2000 // millisec
38#define AHEAD_TIMEOUT 10000 // millisec
39#define BREAK_TIMEOUT 4000 // millisec
40#define BUFFER_CAPACITY 2 // 2 chunks
41
42using namespace Myth;
43
48
49LiveTVPlayback::LiveTVPlayback(EventHandler& handler)
50: ProtoMonitor(handler.GetServer(), handler.GetPort()), EventSubscriber()
51, m_eventHandler(handler)
52, m_eventSubscriberId(0)
53, m_tuneDelay(MIN_TUNE_DELAY)
54, m_limitTuneAttempts(true)
55, m_recorder()
56, m_signal()
57, m_chain()
58, m_chunk(MYTH_LIVETV_CHUNK_SIZE)
59{
60 m_buffer.rbuf = new RingBuffer(BUFFER_CAPACITY);
61 m_buffer.packet = nullptr;
62 m_buffer.consumed = 0;
63
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);
70 Open();
71}
72
73LiveTVPlayback::LiveTVPlayback(const std::string& server, unsigned port)
74: ProtoMonitor(server, port), EventSubscriber()
75, m_eventHandler(server, port)
76, m_eventSubscriberId(0)
77, m_tuneDelay(MIN_TUNE_DELAY)
78, m_recorder()
79, m_signal()
80, m_chain()
81, m_chunk(MYTH_LIVETV_CHUNK_SIZE)
82{
83 m_buffer.rbuf = new RingBuffer(BUFFER_CAPACITY);
84 m_buffer.packet = nullptr;
85 m_buffer.consumed = 0;
86
87 // Private handler will be stopped and closed by destructor.
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);
94 Open();
95}
96
97LiveTVPlayback::~LiveTVPlayback()
98{
99 if (m_eventSubscriberId)
100 m_eventHandler.RevokeSubscription(m_eventSubscriberId);
101 Close();
102 if (m_buffer.packet)
103 m_buffer.rbuf->freePacket(m_buffer.packet);
104 delete m_buffer.rbuf;
105}
106
107bool LiveTVPlayback::Open()
108{
109 // Begin critical section
110 OS::WriteLock lock(*m_latch);
111 if (ProtoMonitor::IsOpen())
112 return true;
113 if (ProtoMonitor::Open())
114 {
115 if (!m_eventHandler.IsRunning())
116 {
117 OS::Timeout timeout(START_TIMEOUT);
118 m_eventHandler.Start();
119 do
120 {
121 usleep(TICK_USEC);
122 }
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__);
126 else
127 DBG(DBG_DEBUG, "%s: event handler is connected\n", __FUNCTION__);
128 }
129 return true;
130 }
131 return false;
132}
133
134void LiveTVPlayback::Close()
135{
136 // Begin critical section
137 OS::WriteLock lock(*m_latch);
138 m_recorder.reset();
139 ProtoMonitor::Close();
140}
141
142void LiveTVPlayback::SetTuneDelay(unsigned delay)
143{
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;
148 else
149 m_tuneDelay = delay;
150}
151
152void LiveTVPlayback::SetLimitTuneAttempts(bool limit)
153{
154 // true : Try first tunable card in prefered order
155 // false: Try all tunable cards in prefered order
156 m_limitTuneAttempts = limit;
157}
158
159bool LiveTVPlayback::SpawnLiveTV(const std::string& chanNum, const ChannelList& channels)
160{
161 // Begin critical section
162 OS::WriteLock lock(*m_latch);
163 if (!ProtoMonitor::IsOpen() || !m_eventHandler.IsConnected())
164 {
165 DBG(DBG_ERROR, "%s: not connected\n", __FUNCTION__);
166 return false;
167 }
168
169 StopLiveTV();
170 preferredCards_t preferredCards = FindTunableCardIds(chanNum, channels);
171 preferredCards_t::const_iterator card = preferredCards.begin();
172 while (card != preferredCards.end())
173 {
174 InitChain(); // Setup chain
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);
179 // Setup the chain
180 m_chain.switchOnCreate = true;
181 m_chain.watch = true;
182 if (m_recorder->SpawnLiveTV(m_chain.UID, channel->chanNum))
183 {
184 // Wait chain update until time limit
185 uint32_t delayMs = m_tuneDelay * 1000;
186 OS::Timeout timeout(delayMs);
187 do
188 {
189 lock.unlock(); // Release the latch to allow chain update
190 usleep(TICK_USEC);
191 lock.lock();
192 if (!m_chain.switchOnCreate)
193 {
194 DBG(DBG_DEBUG, "%s: tune delay (%" PRIu32 "ms)\n", __FUNCTION__, (delayMs - timeout.time_left()));
195 return true;
196 }
197 }
198 while (timeout.time_left() > 0);
199 DBG(DBG_ERROR, "%s: tune delay exceeded (%" PRIu32 "ms)\n", __FUNCTION__, delayMs);
200 m_recorder->StopLiveTV();
201 }
202 ClearChain();
203 // Check if we need to stop after first attempt at tuning
204 if (m_limitTuneAttempts)
205 {
206 DBG(DBG_DEBUG, "%s: limiting tune attempts to first tunable card\n", __FUNCTION__);
207 break;
208 }
209 // Retry the next preferred card
210 ++card;
211 }
212 return false;
213}
214
215bool LiveTVPlayback::SpawnLiveTV(const ChannelPtr& thisChannel)
216{
217 ChannelList list;
218 list.push_back(thisChannel);
219 return SpawnLiveTV(thisChannel->chanNum, list);
220}
221
222void LiveTVPlayback::StopLiveTV()
223{
224 // Begin critical section
225 OS::WriteLock lock(*m_latch);
226 if (m_recorder && m_recorder->IsPlaying())
227 {
228 m_recorder->StopLiveTV();
229 // If recorder is keeping recording then release it to clear my instance status.
230 // Otherwise next program would be considered as preserved.
231 if (m_recorder->IsLiveRecording())
232 m_recorder.reset();
233 }
234}
235
236void LiveTVPlayback::InitChain()
237{
238 BUILTIN_BUFFER buf;
239 // Begin critical section
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();
250}
251
252void LiveTVPlayback::ClearChain()
253{
254 // Begin critical section
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();
262}
263
264bool LiveTVPlayback::IsChained(const Program& program)
265{
266 OS::ReadLock lock(*m_latch);
267 for (chained_t::const_iterator it = m_chain.chained.begin(); it != m_chain.chained.end(); ++it)
268 {
269 if (it->first && it->first->GetPathName() == program.fileName)
270 return true;
271 }
272 return false;
273}
274
275void LiveTVPlayback::HandleChainUpdate()
276{
277 // Begin critical section
278 OS::WriteLock lock(*m_latch); // Lock chain
279 if (!m_recorder)
280 return;
281 ProgramPtr prog = m_recorder->GetCurrentRecording();
282 /*
283 * If program file doesn't exist in the recorder chain then create a new
284 * transfer and add it to the chain.
285 */
286 if (prog && !prog->fileName.empty() && !IsChained(*prog))
287 {
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));
291 // Pop previous dummy file if exists then add the new into the chain
292 if (m_chain.lastSequence && m_chain.chained[m_chain.lastSequence - 1].first->GetSize() == 0)
293 {
294 --m_chain.lastSequence;
295 m_chain.chained.pop_back();
296 }
297 m_chain.chained.push_back(std::make_pair(transfer, prog));
298 m_chain.lastSequence = m_chain.chained.size();
299 /*
300 * If switchOnCreate flag and file is filled then switch immediatly.
301 * Else we will switch later on the next event 'UPDATE_FILE_SIZE'
302 */
303 if (m_chain.switchOnCreate && transfer->GetSize() > 0 && SwitchChainLast())
304 m_chain.switchOnCreate = false;
305 m_chain.watch = false; // Chain update done. Restore watch flag
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);
308 }
309}
310
311bool LiveTVPlayback::SwitchChain(unsigned sequence)
312{
313 OS::WriteLock lock(*m_latch);
314 // Check for out of range
315 if (sequence < 1 || sequence > m_chain.lastSequence)
316 return false;
317 // If closed then try to open
318 if (!m_chain.chained[sequence - 1].first->IsOpen() && !m_chain.chained[sequence - 1].first->Open())
319 return false;
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());
324 return true;
325}
326
327bool LiveTVPlayback::SwitchChainLast()
328{
329 if (SwitchChain(m_chain.lastSequence))
330 {
331 OS::ReadLock lock(*m_latch);
332 if (m_recorder && m_chain.currentTransfer
333 && m_recorder->TransferSeek(*m_chain.currentTransfer, 0, WHENCE_SET) == 0)
334 return true;
335 }
336 return false;
337}
338
339void LiveTVPlayback::HandleBackendMessage(EventMessagePtr msg)
340{
341 /* stage the current recorder */
342 m_latch->lock_shared();
343 ProtoRecorderPtr recorder(m_recorder);
344 m_latch->unlock_shared();
345 if (!recorder || !recorder->IsPlaying())
346 return;
347 switch (msg->event)
348 {
349 /*
350 * Event: LIVETV_CHAIN UPDATE
351 *
352 * Called in response to the backend's notification of a chain update.
353 * The recorder is supplied and will be queried for the current recording
354 * to determine if a new file needs to be added to the chain of files
355 * in the live tv instance.
356 */
357 case EVENT_LIVETV_CHAIN:
358 if (msg->subject.size() >= 3)
359 {
360 if (msg->subject[1] == "UPDATE" && msg->subject[2] == m_chain.UID)
361 HandleChainUpdate();
362 }
363 break;
364 /*
365 * Event: LIVETV_WATCH
366 *
367 * Called in response to the backend's notification of a livetv watch.
368 * The recorder is supplied and will be updated for the watch signal.
369 * This event is used to manage program breaks while watching live tv.
370 * When the guide data marks the end of one show and the beginning of
371 * the next, which will be recorded to a new file, this instructs the
372 * frontend to terminate the existing playback, and change channel to
373 * the new file. Before updating livetv chain and switching to new file
374 * we must to wait for event DONE_RECORDING that informs the current
375 * show is completed. Then we will call livetv chain update to get
376 * current program info. Watch signal will be down during this period.
377 */
378 case EVENT_LIVETV_WATCH:
379 if (msg->subject.size() >= 3)
380 {
381 int32_t rnum;
382 int8_t flag;
383 if (string_to_int32(msg->subject[1].c_str(), &rnum) == 0 && string_to_int8(msg->subject[2].c_str(), &flag) == 0)
384 {
385 if (recorder->GetNum() == (int)rnum)
386 {
387 OS::WriteLock lock(*m_latch); // Lock chain
388 m_chain.watch = true;
389 }
390 }
391 }
392 break;
393 /*
394 * Event: DONE_RECORDING
395 *
396 * Indicates that an active recording has completed on the specified
397 * recorder. used to manage program breaks while watching live tv.
398 * When receive event for recorder, we force an update of livetv chain
399 * to get current program info when chain is not yet updated.
400 * Watch signal is used when up, to mark the break period and
401 * queuing the frontend for reading file buffer.
402 */
403 case EVENT_DONE_RECORDING:
404 if (msg->subject.size() >= 2)
405 {
406 int32_t rnum;
407 if (string_to_int32(msg->subject[1].c_str(), &rnum) == 0 && recorder->GetNum() == (int)rnum)
408 {
409 // Recorder is not subscriber. So callback event to it
410 recorder->DoneRecordingCallback();
411 // Manage program break without locking
412 if (m_chain.watch /* volatile */)
413 {
414 /*
415 * Last recording is now completed but watch signal is ON.
416 * Then force live tv chain update for the new current
417 * program. We will retry for a short period before returning.
418 */
419 OS::Timeout timeout(BREAK_TIMEOUT);
420 do
421 {
422 usleep(500000); // wait for 500 ms
423 HandleChainUpdate();
424 }
425 while (m_chain.watch /* volatile */ &&
426 timeout.time_left() > 0);
427 }
428 }
429 }
430 break;
431 case EVENT_UPDATE_FILE_SIZE:
432 if (msg->subject.size() >= 3)
433 {
434 // Begin critical section
435 OS::WriteLock lock(*m_latch); // Lock chain
436 if (m_chain.lastSequence > 0)
437 {
438 int64_t newsize;
439 // Message contains chanid + starttime as recorded key
440 if (msg->subject.size() >= 4)
441 {
442 uint32_t chanid;
443 time_t startts;
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)
450 break;
451 }
452 // Message contains recordedid as key
453 else
454 {
455 uint32_t recordedid;
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)
460 break;
461 }
462 // Update transfer file size
463 m_chain.chained[m_chain.lastSequence - 1].first->SetSize(newsize);
464 // Is wait the filling before switching ?
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);
469 }
470 }
471 break;
472 case EVENT_SIGNAL:
473 if (msg->subject.size() >= 2)
474 {
475 int32_t rnum;
476 if (string_to_int32(msg->subject[1].c_str(), &rnum) == 0 && recorder->GetNum() == (int)rnum)
477 {
478 // Begin critical section
479 m_latch->lock();
480 m_signal = msg->signal;
481 m_latch->unlock();
482 }
483 }
484 break;
485 //case EVENT_HANDLER_STATUS:
486 // if (msg->subject[0] == EVENTHANDLER_DISCONNECTED)
487 // closeTransfer();
488 // break;
489 default:
490 break;
491 }
492}
493
494void LiveTVPlayback::SetChunk(unsigned size)
495{
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;
500 m_chunk = size;
501}
502
503int64_t LiveTVPlayback::GetSize() const
504{
505 int64_t size = 0;
506 OS::ReadLock lock(*m_latch); // Lock chain
507 for (chained_t::const_iterator it = m_chain.chained.begin(); it != m_chain.chained.end(); ++it)
508 size += it->first->GetSize();
509 return size;
510}
511
512int LiveTVPlayback::Read(void* buffer, unsigned n)
513{
514 for (;;)
515 {
516 if (m_buffer.packet == nullptr)
517 {
518 // get next available packet
519 m_buffer.packet = m_buffer.rbuf->read();
520 m_buffer.consumed = 0;
521 }
522 // read available packet
523 if (m_buffer.packet)
524 {
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)
530 {
531 m_buffer.rbuf->freePacket(m_buffer.packet);
532 m_buffer.packet = nullptr;
533 }
534 return r;
535 }
536 // no packet available, so read to fill the buffer
537 {
538 RingBufferPacket * p = m_buffer.rbuf->newPacket(m_chunk);
539 int r = _read(p->data, m_chunk);
540 if (r > 0)
541 {
542 p->size = r;
543 m_buffer.rbuf->writePacket(p);
544 continue;
545 }
546 m_buffer.rbuf->freePacket(p);
547 return r;
548 }
549 }
550 return -1;
551}
552
553int LiveTVPlayback::_read(void* buffer, unsigned n)
554{
555 int r = 0;
556 bool retry;
557 int64_t s, fp;
558
559 /* stage the current recorder */
560 m_latch->lock_shared();
561 ProtoRecorderPtr recorder(m_recorder);
562 m_latch->unlock_shared();
563 if (!m_chain.currentTransfer || !recorder)
564 return -1;
565
566 fp = m_chain.currentTransfer->GetPosition();
567
568 do
569 {
570 retry = false;
571 s = m_chain.currentTransfer->GetRemaining(); // Acceptable block size
572 if (s == 0)
573 {
574 OS::Timeout timeout(AHEAD_TIMEOUT);
575 for (;;)
576 {
577 // Reading ahead
578 m_latch->lock_shared();
579 unsigned lastseq = m_chain.lastSequence;
580 m_latch->unlock_shared();
581 if (m_chain.currentSequence == lastseq)
582 {
583 int64_t rp = recorder->GetFilePosition();
584 if (rp > fp)
585 {
586 m_chain.currentTransfer->SetSize(rp);
587 retry = true;
588 break;
589 }
590 if (!timeout.time_left())
591 {
592 DBG(DBG_WARN, "%s: read position is ahead (%" PRIi64 ")\n", __FUNCTION__, fp);
593 return 0;
594 }
595 usleep(500000);
596 }
597 // Switch next file transfer is required to continue
598 else
599 {
600 if (!SwitchChain(m_chain.currentSequence + 1))
601 return -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);
606 retry = true;
607 break;
608 }
609 }
610 }
611 else if (s < 0)
612 {
613 // file size not up to date
614 return 0;
615 }
616 }
617 while (retry);
618
619 if (s < (int64_t)n)
620 n = (unsigned)s ;
621
622 r = recorder->TransferRequestBlock(*(m_chain.currentTransfer), buffer, n);
623 return r;
624}
625
626int64_t LiveTVPlayback::Seek(int64_t offset, WHENCE_t whence)
627{
628 if (whence == WHENCE_CUR)
629 {
630 // Unread bytes: remaining bytes in the ring buffer + remaining bytes in the available packet
631 unsigned unread = m_buffer.rbuf->bytesUnread() + (m_buffer.packet ? m_buffer.packet->size - m_buffer.consumed : 0);
632 if (offset == 0)
633 {
634 int64_t p = _seek(offset, whence);
635 // it must returns the current position of the first byte in buffer
636 return (p >= unread ? p - unread : p);
637 }
638 // rebase to the first position in the buffer
639 offset -= unread;
640 }
641 // clear all buffered data
642 if (m_buffer.packet)
643 {
644 m_buffer.rbuf->freePacket(m_buffer.packet);
645 m_buffer.packet = nullptr;
646 }
647 m_buffer.rbuf->clear();
648
649 return _seek(offset, whence);
650}
651
652int64_t LiveTVPlayback::_seek(int64_t offset, WHENCE_t whence)
653{
654 // Begin critical section
655 OS::WriteLock lock(*m_latch); // Lock chain
656 if (!m_recorder || !m_chain.currentSequence)
657 return -1;
658
659 unsigned ci = m_chain.currentSequence - 1; // current sequence index
660 int64_t size = GetSize(); // total stream size
661 int64_t position = GetPosition(); // absolute position in stream
662 int64_t p = 0;
663 switch (whence)
664 {
665 case WHENCE_SET:
666 p = offset;
667 break;
668 case WHENCE_END:
669 p = size + offset;
670 break;
671 case WHENCE_CUR:
672 p = position + offset;
673 break;
674 default:
675 return -1;
676 }
677 if (p > size || p < 0)
678 {
679 DBG(DBG_WARN, "%s: invalid seek (%" PRId64 ")\n", __FUNCTION__, p);
680 return -1;
681 }
682 if (p > position)
683 {
684 for (;;)
685 {
686 if (position + m_chain.chained[ci].first->GetRemaining() >= p)
687 {
688 // Try seek file to desired position. On success switch chain
689 if (m_recorder->TransferSeek(*(m_chain.chained[ci].first), p - position, WHENCE_CUR) < 0 ||
690 !SwitchChain(++ci))
691 return -1;
692 return p;
693 }
694 position += m_chain.chained[ci].first->GetRemaining();
695 ++ci; // switch next
696 if (ci < m_chain.lastSequence)
697 position += m_chain.chained[ci].first->GetPosition();
698 else
699 return -1;
700 }
701 }
702 if (p < position)
703 {
704 for (;;)
705 {
706 if (position - m_chain.chained[ci].first->GetPosition() <= p)
707 {
708 // Try seek file to desired position. On success switch chain
709 if (m_recorder->TransferSeek(*(m_chain.chained[ci].first), p - position, WHENCE_CUR) < 0 ||
710 !SwitchChain(++ci))
711 return -1;
712 return p;
713 }
714 position -= m_chain.chained[ci].first->GetPosition();
715 if (ci > 0)
716 {
717 --ci; // switch previous
718 position -= m_chain.chained[ci].first->GetRemaining();
719 }
720 else
721 return -1;
722 }
723 }
724 // p == position
725 return p;
726}
727
728int64_t LiveTVPlayback::GetPosition() const
729{
730 int64_t pos = 0;
731 OS::ReadLock lock(*m_latch); // Lock chain
732 if (m_chain.currentSequence)
733 {
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();
738 }
739 // it must returns the current position of first byte in buffer
740 unsigned unread = m_buffer.rbuf->bytesUnread() + (m_buffer.packet ? m_buffer.packet->size - m_buffer.consumed : 0);
741 return pos - unread;
742}
743
744bool LiveTVPlayback::IsPlaying() const
745{
746 OS::ReadLock lock(*m_latch);
747 return (m_recorder ? m_recorder->IsPlaying() : false);
748}
749
750bool LiveTVPlayback::IsLiveRecording() const
751{
752 OS::ReadLock lock(*m_latch);
753 return (m_recorder ? m_recorder->IsLiveRecording() : false);
754}
755
756bool LiveTVPlayback::KeepLiveRecording(bool keep)
757{
758 // Begin critical section
759 OS::WriteLock lock(*m_latch);
760 if (m_recorder && m_recorder->IsPlaying())
761 {
762 ProgramPtr prog = m_recorder->GetCurrentRecording();
763 if (prog)
764 {
765 if (keep)
766 {
767 if (UndeleteRecording(*prog) && m_recorder->SetLiveRecording(keep))
768 {
769 QueryGenpixmap(*prog);
770 return true;
771 }
772 }
773 else
774 {
775 if (m_recorder->SetLiveRecording(keep) && m_recorder->FinishRecording())
776 return true;
777 }
778 }
779 }
780 return false;
781}
782
783ProgramPtr LiveTVPlayback::GetPlayedProgram() const
784{
785 OS::ReadLock lock(*m_latch); // Lock chain
786 if (m_chain.currentSequence > 0)
787 return m_chain.chained[m_chain.currentSequence - 1].second;
788 return ProgramPtr();
789}
790
791time_t LiveTVPlayback::GetLiveTimeStart() const
792{
793 OS::ReadLock lock(*m_latch); // Lock chain
794 if (m_chain.lastSequence)
795 return m_chain.chained[0].second->recording.startTs;
796 return (time_t)(-1);
797}
798
799unsigned LiveTVPlayback::GetChainedCount() const
800{
801 OS::ReadLock lock(*m_latch); // Lock chain
802 return m_chain.lastSequence;
803}
804
805ProgramPtr LiveTVPlayback::GetChainedProgram(unsigned sequence) const
806{
807 OS::ReadLock lock(*m_latch); // Lock chain
808 if (sequence > 0 && sequence <= m_chain.lastSequence)
809 return m_chain.chained[sequence - 1].second;
810 return ProgramPtr();
811}
812
813uint32_t LiveTVPlayback::GetCardId() const
814{
815 OS::ReadLock lock(*m_latch);
816 return (m_recorder ? m_recorder->GetNum() : 0);
817}
818
819SignalStatusPtr LiveTVPlayback::GetSignal() const
820{
821 OS::ReadLock lock(*m_latch);
822 return (m_recorder ? m_signal : SignalStatusPtr());
823}
824
825LiveTVPlayback::preferredCards_t LiveTVPlayback::FindTunableCardIds(const std::string& chanNum, const ChannelList& channels)
826{
827 // Make the set of channels matching the desired channel number
828 ChannelList chanset;
829 for (ChannelList::const_iterator it = channels.begin(); it != channels.end(); ++it)
830 {
831 if ((*it)->chanNum == chanNum)
832 chanset.push_back(*it);
833 }
834 // Retrieve unlocked encoders and fill the list of preferred cards.
835 // It is ordered by its key liveTVOrder and contains matching between channels
836 // and card inputs using their respective sourceId and mplexId
837 preferredCards_t preferredCards;
838 CardInputListPtr inputs = GetFreeInputs(0);
839 for (CardInputList::const_iterator iti = inputs->begin(); iti != inputs->end(); ++iti)
840 {
841 for (ChannelList::const_iterator itchan = chanset.begin(); itchan != chanset.end(); ++itchan)
842 {
843 if ((*itchan)->sourceId == (*iti)->sourceId && ( (*iti)->mplexId == 0 || (*iti)->mplexId == (*itchan)->mplexId ))
844 {
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);
849 break;
850 }
851 }
852 }
853 return preferredCards;
854}
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.
Definition mythcontrol.h:30