From de6bdfee21eff3516bbb1eb857ca0cbbad106106 Mon Sep 17 00:00:00 2001
From: Lawrence Rust <lvr@softsystem.co.uk>
Date: Tue, 19 Jul 2011 14:49:12 +0200
Subject: [PATCH 8/9] MythPlayer: Add support for InteractiveTV streams

This patch adds functionality to MythPlayer to enable interactive TV content
to select altenative streamed media for display.

NB this patch needs to be applied together with that for InteractionChannel
streaming.

Signed-off-by: Lawrence Rust <lvr@softsystem.co.uk>
---
 mythtv/libs/libmythtv/icringbuffer.cpp  |  107 ++++++++++++++++++++
 mythtv/libs/libmythtv/icringbuffer.h    |   40 ++++++++
 mythtv/libs/libmythtv/interactivetv.cpp |   18 ++--
 mythtv/libs/libmythtv/interactivetv.h   |    2 +
 mythtv/libs/libmythtv/libmythtv.pro     |    6 +-
 mythtv/libs/libmythtv/mythplayer.cpp    |  165 ++++++++++++++++++++++++++++---
 mythtv/libs/libmythtv/mythplayer.h      |    7 ++
 mythtv/libs/libmythtv/ringbuffer.h      |    4 +
 8 files changed, 325 insertions(+), 24 deletions(-)
 create mode 100644 mythtv/libs/libmythtv/icringbuffer.cpp
 create mode 100644 mythtv/libs/libmythtv/icringbuffer.h

diff --git a/mythtv/libs/libmythtv/icringbuffer.cpp b/mythtv/libs/libmythtv/icringbuffer.cpp
new file mode 100644
index 0000000..f099de2
--- /dev/null
+++ b/mythtv/libs/libmythtv/icringbuffer.cpp
@@ -0,0 +1,107 @@
+#include "icringbuffer.h"
+
+#include <stdio.h> // SEEK_SET
+
+#include <QScopedPointer>
+#include <QWriteLocker>
+
+#include "netstream.h"
+#include "mythlogging.h"
+
+
+#define LOC QString("ICRingBuf ")
+
+
+ICRingBuffer::ICRingBuffer(const QString &url, RingBuffer *parent)
+  : RingBuffer(kRingBufferType), m_stream(0), m_parent(parent)
+{
+    startreadahead = true;
+    OpenFile(url);
+}
+
+ICRingBuffer::~ICRingBuffer()
+{
+    delete m_stream;
+    delete m_parent;
+}
+
+bool ICRingBuffer::IsOpen(void) const
+{
+    return m_stream ? m_stream->IsOpen() : false;
+}
+
+bool ICRingBuffer::OpenFile(const QString &url, uint retry_ms)
+{
+    if (!NetStream::IsSupported(url))
+    {
+        LOG(VB_GENERAL, LOG_ERR, LOC + QString("Unsupported URL %1").arg(url) );
+        return false;
+    }
+
+    QScopedPointer<NetStream> stream(new NetStream(url));
+    if (!stream || !stream->IsOpen())
+    {
+        LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to open %1").arg(url) );
+        return false;
+    }
+
+    if (!stream->WaitTillReady(30000))
+    {
+        LOG(VB_GENERAL, LOG_ERR, LOC + QString("Stream not ready%1").arg(url) );
+        return false;
+    }
+
+    if (m_parent)
+        m_parent->Pause();
+
+    QWriteLocker locker(&rwlock);
+
+    safefilename = url;
+    filename = url;
+
+    delete m_stream;
+    m_stream = stream.take();
+
+    // The initial bitrate needs to be set with consideration for low bit rate
+    // streams (e.g. radio @ 64Kbps) such that fill_min bytes are received
+    // in a reasonable time period to enable decoders to peek the first few KB
+    // to determine type & settings.
+    rawbitrate = 128; // remotefile
+    CalcReadAheadThresh();
+
+    locker.unlock();
+    Reset(true, false, true);
+
+    LOG(VB_GENERAL, LOG_INFO, LOC + QString("Opened %1").arg(url));
+    return true;
+}
+
+long long ICRingBuffer::GetReadPosition(void) const
+{
+    return m_stream ? m_stream->GetReadPosition() : 0;
+}
+
+long long ICRingBuffer::Seek(long long pos, int whence, bool has_lock)
+{
+    return m_stream ? (whence == SEEK_SET ? m_stream->Seek(pos) : -1) : -1;
+}
+
+int ICRingBuffer::safe_read(void *data, uint sz)
+{
+    return m_stream ? m_stream->safe_read(data, sz, 10) : (ateof = true, 0);
+}
+
+long long ICRingBuffer::GetRealFileSize(void) const
+{
+    return m_stream ? m_stream->GetSize() : -1;
+}
+
+// Take ownership of parent RingBuffer
+RingBuffer *ICRingBuffer::Take()
+{
+    RingBuffer *parent = m_parent;
+    m_parent = 0;
+    return parent;
+}
+
+// End of file
diff --git a/mythtv/libs/libmythtv/icringbuffer.h b/mythtv/libs/libmythtv/icringbuffer.h
new file mode 100644
index 0000000..15d4b1c
--- /dev/null
+++ b/mythtv/libs/libmythtv/icringbuffer.h
@@ -0,0 +1,40 @@
+#ifndef ICRINGBUFFER_H
+#define ICRINGBUFFER_H
+
+#include "ringbuffer.h"
+
+class NetStream;
+
+class ICRingBuffer : public RingBuffer
+{
+  public:
+    static enum RingBufferType const kRingBufferType = kRingBuffer_MHEG;
+
+    ICRingBuffer(const QString &url, RingBuffer *parent = 0);
+    virtual ~ICRingBuffer();
+
+    // RingBuffer implementation
+    virtual bool IsOpen(void) const;
+    virtual long long GetReadPosition(void) const;
+    virtual bool OpenFile(const QString &url,
+                          uint retry_ms = kDefaultOpenTimeout);
+    virtual long long Seek(long long pos, int whence, bool has_lock);
+    virtual long long GetRealFileSize(void) const;
+    virtual bool IsStreamed(void)       { return true;  }
+    virtual bool IsSeekingAllowed(void) { return false; }
+    virtual bool IsBookmarkAllowed(void) { return false; }
+
+  protected:
+    virtual int safe_read(void *data, uint sz);
+
+    // Operations
+  public:
+    // Take ownership of parent RingBuffer
+    RingBuffer *Take();
+
+  private:
+    NetStream *m_stream;
+    RingBuffer *m_parent; // parent RingBuffer
+};
+
+#endif // ICRINGBUFFER_H
diff --git a/mythtv/libs/libmythtv/interactivetv.cpp b/mythtv/libs/libmythtv/interactivetv.cpp
index 2cdbea0..d1efa87 100644
--- a/mythtv/libs/libmythtv/interactivetv.cpp
+++ b/mythtv/libs/libmythtv/interactivetv.cpp
@@ -17,14 +17,11 @@ InteractiveTV::InteractiveTV(MythPlayer *nvp)
 {
     Restart(0, 0, false);
 
-    if (VERBOSE_LEVEL_CHECK(VB_MHEG, LOG_ANY))
-    {
-        MHSetLogging(stdout, MHLogAll);
-    }
-    else
-    {
-        MHSetLogging(stdout, MHLogError);
-    }
+    MHSetLogging(stdout,
+        VERBOSE_LEVEL_CHECK(VB_MHEG, LOG_DEBUG) ? MHLogAll :
+        VERBOSE_LEVEL_CHECK(VB_MHEG, LOG_ANY) ?
+            MHLogError | MHLogWarning | MHLogNotifications /*| MHLogLinks | MHLogActions | MHLogDetail*/ :
+        MHLogError | MHLogWarning );
 }
 
 InteractiveTV::~InteractiveTV()
@@ -79,3 +76,8 @@ void InteractiveTV::SetNetBootInfo(const unsigned char *data, uint length)
 {
     m_context->SetNetBootInfo(data, length);
 }
+
+bool InteractiveTV::StreamStarted(bool bStarted)
+{
+    return m_context->StreamStarted(bStarted);
+}
diff --git a/mythtv/libs/libmythtv/interactivetv.h b/mythtv/libs/libmythtv/interactivetv.h
index c8e00e6..ba426de 100644
--- a/mythtv/libs/libmythtv/interactivetv.h
+++ b/mythtv/libs/libmythtv/interactivetv.h
@@ -39,6 +39,8 @@ class InteractiveTV
 
     // Get the initial component tags.
     void GetInitialStreams(int &audioTag, int &videoTag);
+    // Called when a stream starts or stops. Returns true if event is handled
+    bool StreamStarted(bool bStarted = true);
 
     MythPlayer *GetNVP(void) { return m_nvp; }
 
diff --git a/mythtv/libs/libmythtv/libmythtv.pro b/mythtv/libs/libmythtv/libmythtv.pro
index a67539b..8e40e03 100644
--- a/mythtv/libs/libmythtv/libmythtv.pro
+++ b/mythtv/libs/libmythtv/libmythtv.pro
@@ -164,7 +164,8 @@ HEADERS += mythsystemevent.h
 HEADERS += avfringbuffer.h          ThreadedFileWriter.h
 HEADERS += ringbuffer.h             fileringbuffer.h
 HEADERS += dvdringbuffer.h          bdringbuffer.h
-HEADERS += streamingringbuffer.h    metadataimagehelper.h
+HEADERS += streamingringbuffer.h    icringbuffer.h
+HEADERS += metadataimagehelper.h
 
 SOURCES += recordinginfo.cpp
 SOURCES += dbcheck.cpp
@@ -192,7 +193,8 @@ SOURCES += mythsystemevent.cpp
 SOURCES += avfringbuffer.cpp        ThreadedFileWriter.cpp
 SOURCES += ringbuffer.cpp           fileringBuffer.cpp
 SOURCES += dvdringbuffer.cpp        bdringbuffer.cpp
-SOURCES += streamingringbuffer.cpp  metadataimagehelper.cpp
+SOURCES += streamingringbuffer.cpp  icringbuffer.cpp
+SOURCES += metadataimagehelper.cpp
 
 # DiSEqC
 HEADERS += diseqc.h                 diseqcsettings.h
diff --git a/mythtv/libs/libmythtv/mythplayer.cpp b/mythtv/libs/libmythtv/mythplayer.cpp
index 3e2966c..0523d77 100644
--- a/mythtv/libs/libmythtv/mythplayer.cpp
+++ b/mythtv/libs/libmythtv/mythplayer.cpp
@@ -26,6 +26,7 @@ using namespace std;
 #include <QCoreApplication>
 #include <QKeyEvent>
 #include <QDir>
+#include <QScopedPointer>
 
 // MythTV headers
 #include "mthread.h"
@@ -59,6 +60,7 @@ using namespace std;
 #include "mythimage.h"
 #include "mythuiimage.h"
 #include "mythlogging.h"
+#include "icringbuffer.h"
 
 extern "C" {
 #include "vbitext/vbi.h"
@@ -916,7 +918,8 @@ int MythPlayer::OpenFile(uint retries, bool allow_libmpeg2)
         MythTimer peekTimer; peekTimer.start();
         while (player_ctx->buffer->Peek(testbuf, testreadsize) != testreadsize)
         {
-            if (peekTimer.elapsed() > 1000 || bigTimer.elapsed() > timeout)
+            // NB need to allow for streams encountering network congestion
+            if (peekTimer.elapsed() > 30000 || bigTimer.elapsed() > timeout)
             {
                 LOG(VB_GENERAL, LOG_ERR, LOC +
                     QString("OpenFile(): Could not read first %1 bytes of '%2'")
@@ -926,7 +929,7 @@ int MythPlayer::OpenFile(uint retries, bool allow_libmpeg2)
                 return -1;
             }
             LOG(VB_GENERAL, LOG_WARNING, LOC + "OpenFile() waiting on data");
-            usleep(50 * 1000);
+            usleep(150 * 1000);
         }
 
         player_ctx->LockPlayingInfo(__FILE__, __LINE__);
@@ -1937,6 +1940,7 @@ void MythPlayer::DisplayPauseFrame(void)
     SetBuffering(false);
 
     RefreshPauseFrame();
+    PreProcessNormalFrame(); // Allow interactiveTV to draw on pause frame
 
     osdLock.lock();
     videofiltersLock.lock();
@@ -2001,7 +2005,7 @@ bool MythPlayer::PrebufferEnoughFrames(int min_buffers)
             // to recover from serious problems if frames get leaked.
             DiscardVideoFrames(true);
         }
-        if (waited_for > 20000) // 20 seconds
+        if (waited_for > 30000) // 30 seconds for internet streamed media
         {
             LOG(VB_GENERAL, LOG_ERR, LOC +
                 "Waited too long for decoder to fill video buffers. Exiting..");
@@ -2330,6 +2334,14 @@ void MythPlayer::SwitchToProgram(void)
         return;
     }
 
+    if (player_ctx->buffer->GetType() == ICRingBuffer::kRingBufferType)
+    {
+        // Restore original ringbuffer
+        ICRingBuffer *ic = dynamic_cast< ICRingBuffer* >(player_ctx->buffer);
+        player_ctx->buffer = ic->Take();
+        delete ic;
+    }
+
     player_ctx->buffer->OpenFile(
         pginfo->GetPlaybackURL(), RingBuffer::kLiveTVOpenTimeout);
 
@@ -2437,6 +2449,14 @@ void MythPlayer::JumpToProgram(void)
 
     SendMythSystemPlayEvent("PLAY_CHANGED", pginfo);
 
+    if (player_ctx->buffer->GetType() == ICRingBuffer::kRingBufferType)
+    {
+        // Restore original ringbuffer
+        ICRingBuffer *ic = dynamic_cast< ICRingBuffer* >(player_ctx->buffer);
+        player_ctx->buffer = ic->Take();
+        delete ic;
+    }
+
     player_ctx->buffer->OpenFile(
         pginfo->GetPlaybackURL(), RingBuffer::kLiveTVOpenTimeout);
 
@@ -2623,6 +2643,16 @@ void MythPlayer::EventLoop(void)
         JumpToProgram();
     }
 
+    // Change interactive stream if requested
+    { QMutexLocker locker(&itvLock);
+    if (!m_newStream.isEmpty())
+    {
+        QString stream = m_newStream;
+        m_newStream.clear();
+        locker.unlock();
+        JumpToStream(stream);
+    }}
+
     // Disable fastforward if we are too close to the end of the buffer
     if (ffrew_skip > 1 && (CalcMaxFFTime(100, false) < 100))
     {
@@ -2659,22 +2689,23 @@ void MythPlayer::EventLoop(void)
     }
 
     // Handle end of file
-    if (GetEof())
+    if (GetEof() && !allpaused)
     {
-        if (player_ctx->tvchain)
+        if (interactiveTV && interactiveTV->StreamStarted(false))
         {
-            if (!allpaused && player_ctx->tvchain->HasNext())
-            {
-                LOG(VB_GENERAL, LOG_NOTICE, LOC + "LiveTV forcing JumpTo 1");
-                player_ctx->tvchain->JumpToNext(true, 1);
-                return;
-            }
+            Pause();
+            return;
         }
-        else if (!allpaused)
+
+        if (player_ctx->tvchain && player_ctx->tvchain->HasNext())
         {
-            SetPlaying(false);
+            LOG(VB_GENERAL, LOG_NOTICE, LOC + "LiveTV forcing JumpTo 1");
+            player_ctx->tvchain->JumpToNext(true, 1);
             return;
         }
+
+        SetPlaying(false);
+        return;
     }
 
     // Handle rewind
@@ -2805,7 +2836,7 @@ void MythPlayer::UnpauseDecoder(void)
 
     int tries = 0;
     unpauseDecoder = true;
-    while (decoderThread && !killdecoder && (tries++ < 100) &&
+    while (decoderPaused && decoderThread && !killdecoder && (tries++ < 10) &&
           !decoderThreadUnpause.wait(&decoderPauseLock, 100))
     {
         LOG(VB_GENERAL, LOG_WARNING, LOC +
@@ -4641,6 +4672,112 @@ bool MythPlayer::SetVideoByComponentTag(int tag)
     return false;
 }
 
+// Called from MHIContext::Begin/End/Stream on the MHIContext::StartMHEGEngine thread
+bool MythPlayer::SetStream(const QString &stream)
+{
+    // The stream name is empty if the stream is closing
+    LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("SetStream '%1'").arg(stream));
+
+    QMutexLocker locker(&itvLock);
+    m_newStream = stream;
+    m_newStream.detach();
+    // Stream will be changed by JumpToStream called from EventLoop
+    // If successful will call interactiveTV->StreamStarted();
+    return !stream.isEmpty();
+}
+
+// Called from EventLoop pn the main application thread
+void MythPlayer::JumpToStream(const QString &stream)
+{
+    LOG(VB_PLAYBACK, LOG_INFO, LOC + "JumpToStream - begin");
+
+    if (stream.isEmpty())
+        return; // Shouldn't happen
+
+    Pause();
+    ResetCaptions();
+
+    if (player_ctx->buffer->GetType() != ICRingBuffer::kRingBufferType)
+        player_ctx->buffer = new ICRingBuffer(stream, player_ctx->buffer);
+    else
+        player_ctx->buffer->OpenFile(stream);
+
+    if (!player_ctx->buffer->IsOpen())
+    {
+        LOG(VB_GENERAL, LOG_ERR, LOC + "JumpToStream buffer OpenFile failed");
+        SetEof(true);
+        SetErrored(QObject::tr("Error opening remote stream buffer"));
+        return;
+    }
+
+    if (OpenFile(120) < 0) // 120 retries ~= 60 seconds
+    {
+        LOG(VB_GENERAL, LOG_ERR, LOC + "JumpToStream OpenFile failed.");
+        SetEof(true);
+        SetErrored(QObject::tr("Error opening remote stream"));
+        return;
+    }
+
+    SetEof(false);
+
+    if (player_ctx->tvchain) CheckTVChain();
+    //player_ctx->buffer->IgnoreLiveEOF(false);
+
+    Play();
+    ChangeSpeed();
+
+    player_ctx->SetPlayerChangingBuffers(false);
+    if (interactiveTV) interactiveTV->StreamStarted();
+
+    LOG(VB_PLAYBACK, LOG_INFO, LOC + "JumpToStream - end");
+}
+
+static inline int SafeFPS(DecoderBase *decoder)
+{
+    if (!decoder)
+        return 25;
+    double fps = decoder->GetFPS();
+    return fps > 0 ? (int)(fps + 0.5) : 25;
+}
+
+long MythPlayer::GetStreamPos()
+{
+    return (1000L * GetFramesPlayed()) / SafeFPS(decoder);
+}
+
+long MythPlayer::GetStreamMaxPos()
+{
+    uint kbps = decoder ? decoder->GetRawBitrate() : 0;
+    long pos = GetStreamPos(), maxpos = 0;
+    long long len = player_ctx->buffer->GetRealFileSize();
+    if (len > 0)
+    {
+        const uint kMin = 64;
+        maxpos = (long)((8 * len) / (kbps < kMin ? kMin : kbps));
+    }
+
+    if (maxpos < pos)
+        maxpos = pos;
+    LOG(VB_PLAYBACK, LOG_INFO, LOC +
+        QString("GetStreamMaxPos => %1 mS (%2 KB @ %3 KBPS)")
+        .arg(maxpos).arg(len > 0 ? len/1024 : len).arg((kbps+4)/8) );
+    return maxpos;
+}
+
+long MythPlayer::SetStreamPos(long ms)
+{
+    LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("SetStreamPos %1").arg(ms));
+    return JumpToFrame((uint64_t)((ms * SafeFPS(decoder)) / 1000));
+}
+
+void MythPlayer::StreamPlay(bool play)
+{
+    if (play)
+        Play();
+    else
+        Pause();
+}
+
 /** \fn MythPlayer::SetDecoder(DecoderBase*)
  *  \brief Sets the stream decoder, deleting any existing recorder.
  */
diff --git a/mythtv/libs/libmythtv/mythplayer.h b/mythtv/libs/libmythtv/mythplayer.h
index daeb4dc..e4eae0b 100644
--- a/mythtv/libs/libmythtv/mythplayer.h
+++ b/mythtv/libs/libmythtv/mythplayer.h
@@ -259,6 +259,11 @@ class MTV_PUBLIC MythPlayer
     // Public MHEG/MHI stream selection
     bool SetAudioByComponentTag(int tag);
     bool SetVideoByComponentTag(int tag);
+    bool SetStream(const QString &);
+    long GetStreamPos(); // mS
+    long GetStreamMaxPos(); // mS
+    long SetStreamPos(long); // mS
+    void StreamPlay(bool play = true);
 
     // LiveTV public stuff
     void CheckTVChain();
@@ -524,6 +529,7 @@ class MTV_PUBLIC MythPlayer
     // Private LiveTV stuff
     void  SwitchToProgram(void);
     void  JumpToProgram(void);
+    void  JumpToStream(const QString&);
 
     void calcSliderPosPriv(osdInfo &info, bool paddedFields,
                            int playbackLen, float secsplayed, bool islive);
@@ -660,6 +666,7 @@ class MTV_PUBLIC MythPlayer
     InteractiveTV *interactiveTV;
     bool       itvEnabled;
     QMutex     itvLock;
+    QString    m_newStream; // Guarded by itvLock
 
     // OSD stuff
     OSD  *osd;
diff --git a/mythtv/libs/libmythtv/ringbuffer.h b/mythtv/libs/libmythtv/ringbuffer.h
index 8dc633d..08a007c 100644
--- a/mythtv/libs/libmythtv/ringbuffer.h
+++ b/mythtv/libs/libmythtv/ringbuffer.h
@@ -38,10 +38,13 @@ enum RingBufferType
     kRingBuffer_DVD,
     kRingBuffer_BD,
     kRingBuffer_HTTP,
+    kRingBuffer_MHEG
 };
 
 class MTV_PUBLIC RingBuffer : protected MThread
 {
+    friend class ICRingBuffer;
+
   public:
     static RingBuffer *Create(const QString &lfilename, bool write,
                               bool usereadahead = true,
@@ -84,6 +87,7 @@ class MTV_PUBLIC RingBuffer : protected MThread
     virtual bool IsBookmarkAllowed(void) { return true; }
     virtual int  BestBufferSize(void)   { return 32768; }
     static QString BitrateToString(uint64_t rate);
+    RingBufferType GetType() const { return type; }
 
     // DVD and bluray methods
     bool IsDisc(void) const { return IsDVD() || IsBD(); }
-- 
1.7.4.1

