Ticket #10019: iplayer.diff

File iplayer.diff, 130.9 KB (added by paulh, 8 years ago)

Updated patch to compile on Qt4.6

  • mythtv/libs/libmythfreemheg/ASN1Codes.h

    diff --git a/mythtv/libs/libmythfreemheg/ASN1Codes.h b/mythtv/libs/libmythfreemheg/ASN1Codes.h
    index 53ea5aa..8cc353a 100644
    a b  
    280280#define C_SET_BITMAP_DECODE_OFFSET  246
    281281#define C_GET_BITMAP_DECODE_OFFSET  247
    282282#define C_SET_SLIDER_PARAMETERS     248
     283// Added in ETSI ES 202 184 V2.1.1 (2010-01)
     284#define C_GET_DESKTOP_COLOUR        249
     285#define C_SET_DESKTOP_COLOUR        250
     286#define C_GET_COUNTER_POSITION      251
     287#define C_GET_COUNTER_MAX_POSITION  252
    283288
    284289// Pseudo-codes.  These are encoded into the link condition in binary but it's convenient
    285290// to give them codes here since that way we can include them in the same lookup table.
  • mythtv/libs/libmythfreemheg/Actions.cpp

    diff --git a/mythtv/libs/libmythfreemheg/Actions.cpp b/mythtv/libs/libmythfreemheg/Actions.cpp
    index 75ede6a..041f6d2 100644
    a b  
    4444class MHUnimplementedAction: public MHElemAction
    4545{
    4646  public:
    47     MHUnimplementedAction(int nTag): MHElemAction("")
     47    MHUnimplementedAction(int nTag): MHElemAction(""), m_nTag(nTag)
    4848    {
    49         m_nTag = nTag;
     49        MHLOG(MHLogWarning, QString("WARN Unimplemented action %1").arg(m_nTag) );
    5050    }
    5151    virtual void Initialise(MHParseNode *, MHEngine *) {}
    5252    virtual void PrintMe(FILE *fd, int /*nTabs*/) const
    void MHActionSequence::Initialise(MHParseNode *p, MHEngine *engine) 
    297297                pAction = new MHUnimplementedAction(pElemAction->GetTagNo());
    298298                break; // Stream
    299299            case C_SET_COUNTER_POSITION:
    300                 pAction = new MHUnimplementedAction(pElemAction->GetTagNo());
     300                pAction = new MHSetCounterPosition;
    301301                break; // Stream
    302302            case C_SET_COUNTER_TRIGGER:
    303303                pAction = new MHUnimplementedAction(pElemAction->GetTagNo());
    void MHActionSequence::Initialise(MHParseNode *p, MHEngine *engine) 
    357357                pAction = new MHSetSliderValue;
    358358                break;
    359359            case C_SET_SPEED:
    360                 pAction = new MHUnimplementedAction(pElemAction->GetTagNo());
     360                pAction = new MHSetSpeed;
    361361                break; // ?
    362362            case C_SET_TIMER:
    363363                pAction = new MHSetTimer;
    void MHActionSequence::Initialise(MHParseNode *p, MHEngine *engine) 
    442442                pAction = new MHSetSliderParameters;
    443443                break;
    444444
     445            // Added in ETSI ES 202 184 V2.1.1 (2010-01)
     446            case C_GET_COUNTER_POSITION: // Stream position
     447                pAction = new MHGetCounterPosition;
     448                break;
     449            case C_GET_COUNTER_MAX_POSITION: // Stream total size
     450                pAction = new MHGetCounterMaxPosition;
     451                break;
     452
    445453            default:
    446                 MHLOG(MHLogWarning, QString("Unknown action %1").arg(pElemAction->GetTagNo()));
     454                MHLOG(MHLogWarning, QString("WARN Unknown action %1").arg(pElemAction->GetTagNo()));
    447455                // Future proofing: ignore any actions that we don't know about.
    448456                // Obviously these can only arise in the binary coding.
    449457                pAction = NULL;
  • mythtv/libs/libmythfreemheg/BaseClasses.cpp

    diff --git a/mythtv/libs/libmythfreemheg/BaseClasses.cpp b/mythtv/libs/libmythfreemheg/BaseClasses.cpp
    index 8bd8ff8..af7245b 100644
    a b void MHGenericObjectRef::GetValue(MHObjectRef &ref, MHEngine *engine) const 
    588588    }
    589589    else
    590590    {
     591        // LVR - Hmm I don't think this is right. Should be: ref.Copy(m_Indirect);
     592        // But it's used in several places so workaround in Stream::MHActionGenericObjectRefFix
    591593        MHUnion result;
    592594        MHRoot *pBase = engine->FindObject(m_Indirect);
    593595        pBase->GetVariableValue(result, engine);
  • mythtv/libs/libmythfreemheg/BaseClasses.h

    diff --git a/mythtv/libs/libmythfreemheg/BaseClasses.h b/mythtv/libs/libmythfreemheg/BaseClasses.h
    index 587577f..7f7670f 100644
    a b class MHGenericBase 
    184184{
    185185  public:
    186186    MHObjectRef *GetReference(); // Return the indirect reference or fail if it's direct
    187 protected:
    188187    bool    m_fIsDirect;
     188protected:
    189189    MHObjectRef m_Indirect;
    190190};
    191191
  • mythtv/libs/libmythfreemheg/Bitmap.cpp

    diff --git a/mythtv/libs/libmythfreemheg/Bitmap.cpp b/mythtv/libs/libmythfreemheg/Bitmap.cpp
    index 42c9df1..522175d 100644
    a b void MHBitmap::ContentPreparation(MHEngine *engine) 
    127127        MHERROR("Bitmap must contain a content");
    128128    }
    129129
    130     if (m_ContentType == IN_IncludedContent) // We can't handle included content at the moment.
    131     {
    132         MHERROR("Included content in bitmap is not implemented");
    133     }
     130    if (m_ContentType == IN_IncludedContent)
     131        CreateContent(m_IncludedContent.Bytes(), m_IncludedContent.Size(), engine);
    134132}
    135133
    136134// Decode the content.
    void MHBitmap::ContentArrived(const unsigned char *data, int length, MHEngine *e 
    143141        return;    // Shouldn't happen.
    144142    }
    145143
     144    CreateContent(data, length, engine);
     145    // Now signal that the content is available.
     146    engine->EventTriggered(this, EventContentAvailable);
     147}
     148
     149void MHBitmap::CreateContent(const unsigned char *data, int length, MHEngine *engine)
     150{
     151    QRegion updateArea = GetVisibleArea(); // If there's any content already we have to redraw it.
     152
    146153    int nCHook = m_nContentHook;
    147154
    148155    if (nCHook == 0)
    void MHBitmap::ContentArrived(const unsigned char *data, int length, MHEngine *e 
    162169    {
    163170        m_pContent->CreateFromMPEG(data, length);
    164171    }
    165 
     172    else if (nCHook == 6) // JPEG ISO/IEC 10918-1, JFIF file
     173    {
     174        m_pContent->CreateFromJPEG(data, length);
     175    }
    166176    else
    167177    {
     178        // 1,3,5,8 are reserved. 7= H.264 Intra Frame
    168179        MHERROR(QString("Unknown bitmap content hook %1").arg(nCHook));
    169180    }
    170181
    171182    updateArea += GetVisibleArea(); // Redraw this bitmap.
    172183    engine->Redraw(updateArea); // Mark for redrawing
    173 
    174     // Now signal that the content is available.
    175     engine->EventTriggered(this, EventContentAvailable);
    176184}
    177185
    178186
    179 
    180187// Set the transparency.
    181188void MHBitmap::SetTransparency(int nTransPerCent, MHEngine *)
    182189{
  • mythtv/libs/libmythfreemheg/Bitmap.h

    diff --git a/mythtv/libs/libmythfreemheg/Bitmap.h b/mythtv/libs/libmythfreemheg/Bitmap.h
    index 06d5f0b..e37c67d 100644
    a b class MHBitmap : public MHVisible 
    6969    int     m_nXDecodeOffset, m_nYDecodeOffset;
    7070
    7171    MHBitmapDisplay  *m_pContent; // Pointer to current image if any.
     72
     73    void CreateContent(const unsigned char *p, int s, MHEngine *engine);
    7274};
    7375
    7476// Actions.
  • mythtv/libs/libmythfreemheg/Engine.cpp

    diff --git a/mythtv/libs/libmythfreemheg/Engine.cpp b/mythtv/libs/libmythfreemheg/Engine.cpp
    index 3ef0825..175c7c0 100644
    a b  
    3232#include "Logging.h"
    3333#include "freemheg.h"
    3434#include "Visible.h"  // For MHInteractible
     35#include "Stream.h"
    3536
    3637#include <stdio.h>
    3738#include <stdlib.h>
    int MHEngine::RunAll() 
    111112
    112113            if (! Launch(startObj))
    113114            {
    114                 MHLOG(MHLogWarning, "MHEG engine auto-boot failed");
     115                MHLOG(MHLogNotifications, "NOTE Engine auto-boot failed");
    115116                return -1;
    116117            }
    117118        }
    MHGroup *MHEngine::ParseProgram(QByteArray &text) 
    242243    return pRes;
    243244}
    244245
    245 // Launch and Spawn
    246 bool MHEngine::Launch(const MHObjectRef &target, bool fIsSpawn)
     246// Determine protocol for a file
     247enum EProtocol { kProtoUnknown, kProtoDSM, kProtoCI, kProtoHTTP, kProtoHybrid };
     248static EProtocol PathProtocol(const QString& csPath)
    247249{
    248     QString csPath = GetPathName(target.m_GroupId); // Get path relative to root.
     250    if (csPath.isEmpty() || csPath.startsWith("DSM:") || csPath.startsWith("~"))
     251        return kProtoDSM;
     252    if (csPath.startsWith("hybrid:"))
     253        return kProtoHybrid;
     254    if (csPath.startsWith("http:") || csPath.startsWith("https:"))
     255        return kProtoHTTP;
     256    if (csPath.startsWith("CI:"))
     257        return kProtoCI;
    249258
    250     if (csPath.length() == 0)
    251     {
    252         return false;    // No file name.
    253     }
     259    int firstColon = csPath.indexOf(':'), firstSlash = csPath.indexOf('/');
     260    if (firstColon > 0 && firstSlash > 0 && firstColon < firstSlash)
     261        return kProtoUnknown;
     262
     263    return kProtoDSM;
     264}
    254265
     266// Launch and Spawn
     267bool MHEngine::Launch(const MHObjectRef &target, bool fIsSpawn)
     268{
    255269    if (m_fInTransition)
    256270    {
    257         MHLOG(MHLogWarning, "Launch during transition - ignoring");
     271        MHLOG(MHLogWarning, "WARN Launch during transition - ignoring");
    258272        return false;
    259273    }
    260274
    261     QByteArray text;
     275    if (target.m_GroupId.Size() == 0) return false; // No file name.
     276    QString csPath = GetPathName(target.m_GroupId); // Get path relative to root.
    262277
    263278    // Check that the file exists before we commit to the transition.
    264279    // This may block if we cannot be sure whether the object is present.
     280    QByteArray text;
    265281    if (! m_Context->GetCarouselData(csPath, text))
    266282    {
    267         if (CurrentApp())
    268         {
    269             EventTriggered(CurrentApp(), EventEngineEvent, 2);    // GroupIDRefError
    270         }
    271 
     283        if (!m_fBooting)
     284            EngineEvent(2); // GroupIDRefError
    272285        return false;
    273286    }
    274287
    void MHEngine::Quit() 
    351364{
    352365    if (m_fInTransition)
    353366    {
    354         MHLOG(MHLogWarning, "Quit during transition - ignoring");
     367        MHLOG(MHLogWarning, "WARN Quit during transition - ignoring");
    355368        return;
    356369    }
    357370
    void MHEngine::TransitionToScene(const MHObjectRef &target) 
    395408    if (m_fInTransition)
    396409    {
    397410        // TransitionTo is not allowed in OnStartUp or OnCloseDown actions.
    398         MHLOG(MHLogWarning, "TransitionTo during transition - ignoring");
     411        MHLOG(MHLogWarning, "WARN TransitionTo during transition - ignoring");
    399412        return;
    400413    }
    401414
    void MHEngine::TransitionToScene(const MHObjectRef &target) 
    405418    }
    406419
    407420    QString csPath = GetPathName(target.m_GroupId);
    408     QByteArray text;
    409421
    410422    // Check that the file exists before we commit to the transition.
    411     if (! m_Context->GetCarouselData(csPath, text))
    412     {
    413         EventTriggered(CurrentApp(), EventEngineEvent, 2); // GroupIDRefError
     423    // This may block if we cannot be sure whether the object is present.
     424    QByteArray text;
     425    if (! m_Context->GetCarouselData(csPath, text)) {
     426        EngineEvent(2); // GroupIDRefError
    414427        return;
    415428    }
    416429
    void MHEngine::TransitionToScene(const MHObjectRef &target) 
    482495    m_Interacting = 0;
    483496
    484497    // Switch to the new scene.
    485     CurrentApp()->m_pCurrentScene = (MHScene *) pProgram;
     498    CurrentApp()->m_pCurrentScene = static_cast< MHScene* >(pProgram);
    486499    SetInputRegister(CurrentScene()->m_nEventReg);
    487500    m_redrawRegion = QRegion(0, 0, CurrentScene()->m_nSceneCoordX, CurrentScene()->m_nSceneCoordY); // Redraw the whole screen
    488501
    void MHEngine::SetInputRegister(int nReg) 
    504517// Create a canonical path name.  The rules are given in the UK MHEG document.
    505518QString MHEngine::GetPathName(const MHOctetString &str)
    506519{
    507     QString csPath;
    508 
    509     if (str.Size() != 0)
    510     {
    511         csPath = QString::fromUtf8((const char *)str.Bytes(), str.Size());
    512     }
    513 
    514     if (csPath.left(4) == "DSM:")
    515     {
    516         csPath = csPath.mid(4);    // Remove DSM:
    517     }
    518 
    519     // If it has any other prefix this isn't a request for a carousel object.
    520     int firstColon = csPath.indexOf(':'), firstSlash = csPath.indexOf('/');
    521 
    522     if (firstColon > 0 && firstSlash > 0 && firstColon < firstSlash)
    523     {
     520    if (str.Size() == 0)
    524521        return QString();
    525     }
    526522
    527     if (csPath.left(1) == "~")
     523    QString csPath = QString::fromUtf8((const char *)str.Bytes(), str.Size());
     524    switch (PathProtocol(csPath))
    528525    {
    529         csPath = csPath.mid(1);    // Remove ~
     526    default:
     527    case kProtoUnknown:
     528    case kProtoHybrid:
     529    case kProtoHTTP:
     530    case kProtoCI:
     531        return csPath;
     532    case kProtoDSM:
     533        break;
    530534    }
    531535
    532     // Ignore "CI://"
    533     if (csPath.left(2) != "//")   //
     536    if (csPath.startsWith("DSM:"))
     537        csPath = csPath.mid(4); // Remove DSM:
     538    else if (csPath.startsWith("~"))
     539        csPath = csPath.mid(1); // Remove ~
     540    if (!csPath.startsWith("//"))
    534541    {
    535542        // Add the current application's path name
    536543        if (CurrentApp())
    MHRoot *MHEngine::FindObject(const MHObjectRef &oRef, bool failOnNotFound) 
    589596        // an object that may or may not exist at a particular time.
    590597        // Another case was a call to CallActionSlot with an object reference variable
    591598        // that had been initialised to zero.
    592         MHLOG(MHLogWarning, QString("Reference %1 not found").arg(oRef.m_nObjectNo));
     599        MHLOG(MHLogWarning, QString("WARN Reference %1 not found").arg(oRef.m_nObjectNo));
    593600        throw "FindObject failed";
    594601    }
    595602
    void MHEngine::RunActions() 
    609616        {
    610617            if ((__mhlogoptions & MHLogActions) && __mhlogStream != 0)   // Debugging
    611618            {
    612                 fprintf(__mhlogStream, "Action - ");
     619                fprintf(__mhlogStream, "[freemheg] Action - ");
    613620                pAction->PrintMe(__mhlogStream, 0);
    614621                fflush(__mhlogStream);
    615622            }
    void MHEngine::EventTriggered(MHRoot *pSource, enum EventType ev, const MHUnion 
    670677        case EventUserInput:
    671678        case EventFocusMoved: // UK MHEG.  Generated by HyperText class
    672679        case EventSliderValueChanged: // UK MHEG.  Generated by Slider class
     680        default:
    673681        {
    674682            // Asynchronous events.  Add to the event queue.
    675683            MHAsynchEvent *pEvent = new MHAsynchEvent;
    void MHEngine::EventTriggered(MHRoot *pSource, enum EventType ev, const MHUnion 
    678686            pEvent->eventData = evData;
    679687            m_EventQueue.enqueue(pEvent);
    680688        }
     689        break;
    681690    }
    682691}
    683692
    void MHEngine::GenerateUserAction(int nCode) 
    921930        case 101: // Green
    922931        case 102: // Yellow
    923932        case 103: // Blue
     933        case 300: // EPG
    924934            EventTriggered(pScene, EventEngineEvent, nCode);
    925935            break;
    926936    }
    void MHEngine::GenerateUserAction(int nCode) 
    939949
    940950void MHEngine::EngineEvent(int nCode)
    941951{
    942     EventTriggered(CurrentApp(), EventEngineEvent, nCode);
     952    if (CurrentApp())
     953        EventTriggered(CurrentApp(), EventEngineEvent, nCode);
     954    else if (!m_fBooting)
     955        MHLOG(MHLogWarning, QString("WARN EngineEvent %1 but no app").arg(nCode));
     956}
     957
     958void MHEngine::StreamStarted(MHStream *stream, bool bStarted)
     959{
     960    EventTriggered(stream, bStarted ? EventStreamPlaying : EventStreamStopped);
    943961}
    944962
    945963// Called by an ingredient wanting external content.
    void MHEngine::RequestExternalContent(MHIngredient *pRequester) 
    954972
    955973    // Remove any existing content requests for this ingredient.
    956974    CancelExternalContentRequest(pRequester);
    957     QString csPath = GetPathName(pRequester->m_ContentRef.m_ContentRef);
    958 
    959     // Is this actually a carousel object?  It could be a stream.  We should deal
    960     // with that separately.
    961     if (csPath.isEmpty())
    962     {
    963         MHLOG(MHLogWarning, "RequestExternalContent empty path");
    964         return;
    965     }
    966975
    967     QByteArray text;
    968 
    969     if (m_Context->CheckCarouselObject(csPath) && m_Context->GetCarouselData(csPath, text))
     976    QString csPath = GetPathName(pRequester->m_ContentRef.m_ContentRef);
     977    if (m_Context->CheckCarouselObject(csPath))
    970978    {
    971979        // Available now - pass it to the ingredient.
    972         pRequester->ContentArrived((const unsigned char *)text.data(), text.size(), this);
     980        QByteArray text;
     981        if (m_Context->GetCarouselData(csPath, text))
     982        {
     983            // If the content is not recognized catch the exception and continue
     984            try
     985            {
     986                pRequester->ContentArrived((const unsigned char *)text.data(), text.size(), this);
     987            }
     988            catch (char const *)
     989            {}
     990        }
     991        else
     992        {
     993            MHLOG(MHLogWarning, QString("WARN No file content %1 <= %2")
     994                .arg(pRequester->m_ObjectReference.Printable()).arg(csPath));
     995            if (kProtoHTTP == PathProtocol(csPath))
     996                EngineEvent(203); // 203=RemoteNetworkError if 404 reply
     997            EngineEvent(3); // ContentRefError
     998        }
    973999    }
    9741000    else
    9751001    {
    9761002        // Need to record this and check later.
    977         MHLOG(MHLogLinks, QString("RequestExternalContent %1 pending").arg(csPath));
     1003        MHLOG(MHLogNotifications, QString("Waiting for %1 <= %2")
     1004            .arg(pRequester->m_ObjectReference.Printable()).arg(csPath.left(128)) );
    9781005        MHExternContent *pContent = new MHExternContent;
    9791006        pContent->m_FileName = csPath;
    9801007        pContent->m_pRequester = pRequester;
    void MHEngine::CancelExternalContentRequest(MHIngredient *pRequester) 
    9951022
    9961023        if (pContent->m_pRequester == pRequester)
    9971024        {
    998             delete pContent;
     1025            MHLOG(MHLogNotifications, QString("Cancelled wait for %1")
     1026                .arg(pRequester->m_ObjectReference.Printable()) );
    9991027            it = m_ExternContentTable.erase(it);
     1028            delete pContent;
    10001029            return;
    10011030        }
    10021031        else
    void MHEngine::CancelExternalContentRequest(MHIngredient *pRequester) 
    10091038// See if we can satisfy any of the outstanding requests.
    10101039void MHEngine::CheckContentRequests()
    10111040{
    1012     QList<MHExternContent *>::iterator it = m_ExternContentTable.begin();
    1013     MHExternContent *pContent;
    1014 
     1041    QList<MHExternContent*>::iterator it = m_ExternContentTable.begin();
    10151042    while (it != m_ExternContentTable.end())
    10161043    {
    1017         pContent = *it;
    1018         QByteArray text;
    1019 
    1020         if (m_Context->CheckCarouselObject(pContent->m_FileName) &&
    1021             m_Context->GetCarouselData(pContent->m_FileName, text))
     1044        MHExternContent *pContent = *it;
     1045        if (m_Context->CheckCarouselObject(pContent->m_FileName))
    10221046        {
    1023             // If the content is not recognized catch the exception and continue
    1024             try
     1047            // Remove from the list.
     1048            it = m_ExternContentTable.erase(it);
     1049
     1050            QByteArray text;
     1051            if (m_Context->GetCarouselData(pContent->m_FileName, text))
    10251052            {
    1026                 MHLOG(MHLogLinks, QString("CheckContentRequests %1 arrived")
    1027                       .arg(pContent->m_FileName));
    1028                 pContent->m_pRequester->ContentArrived((const unsigned char *)text.data(),
    1029                                                        text.size(), this);
     1053                MHLOG(MHLogNotifications, QString("Received %1 len %2")
     1054                    .arg(pContent->m_pRequester->m_ObjectReference.Printable())
     1055                    .arg(text.size()) );
     1056                // If the content is not recognized catch the exception and continue
     1057                try
     1058                {
     1059                    pContent->m_pRequester->ContentArrived(
     1060                        (const unsigned char *)text.data(), text.size(), this);
     1061                }
     1062                catch (char const *)
     1063                {}
    10301064            }
    1031             catch (char const *)
     1065            else
    10321066            {
     1067                MHLOG(MHLogWarning, QString("WARN No file content %1 <= %2")
     1068                    .arg(pContent->m_pRequester->m_ObjectReference.Printable())
     1069                    .arg(pContent->m_FileName));
     1070                if (kProtoHTTP == PathProtocol(pContent->m_FileName))
     1071                    EngineEvent(203); // 203=RemoteNetworkError if 404 reply
     1072                EngineEvent(3); // ContentRefError
    10331073            }
    10341074
    1035             // Remove from the list.
    10361075            delete pContent;
    1037             it = m_ExternContentTable.erase(it);
    10381076        }
    10391077        else if (pContent->m_time.elapsed() > 60000) // TODO Get this from carousel
    10401078        {
    1041             MHLOG(MHLogWarning, QString("CheckContentRequests %1 timed out")
    1042                   .arg(pContent->m_FileName));
    1043             delete pContent;
     1079            // Remove from the list.
    10441080            it = m_ExternContentTable.erase(it);
    1045             EventTriggered(CurrentApp(), EventEngineEvent, 3); // ContentRefError
     1081
     1082            MHLOG(MHLogWarning, QString("WARN File timed out %1 <= %2")
     1083                .arg(pContent->m_pRequester->m_ObjectReference.Printable())
     1084                .arg(pContent->m_FileName));
     1085
     1086            if (kProtoHTTP == PathProtocol(pContent->m_FileName))
     1087                EngineEvent(203); // 203=RemoteNetworkError if 404 reply
     1088            EngineEvent(3); // ContentRefError
     1089
     1090            delete pContent;
    10461091        }
    10471092        else
    10481093        {
    bool MHEngine::GetEngineSupport(const MHOctetString &feature) 
    11211166    QString csFeat = QString::fromUtf8((const char *)feature.Bytes(), feature.Size());
    11221167    QStringList strings = csFeat.split(QRegExp("[\\(\\,\\)]"));
    11231168
     1169    MHLOG(MHLogNotifications, "NOTE GetEngineSupport " + csFeat);
     1170
    11241171    if (strings[0] == "ApplicationStacking" || strings[0] == "ASt")
    11251172    {
    11261173        return true;
    bool MHEngine::GetEngineSupport(const MHOctetString &feature) 
    12411288    // We support bitmaps that are partially off screen (don't we?)
    12421289    if (strings[0] == "BitmapDecodeOffset" || strings[0] == "BDO")
    12431290    {
    1244         if (strings.count() >= 3 && strings[1] == "10" && (strings[2] == "0" || strings[2] == "1"))
     1291        if (strings.count() >= 3 && strings[1] == "2" && (strings[2] == "0" || strings[2] == "1"))
     1292        {
     1293            return true;
     1294        }
     1295        else if (strings.count() >= 2 && (strings[1] == "4" || strings[1] == "6"))
    12451296        {
    12461297            return true;
    12471298        }
    bool MHEngine::GetEngineSupport(const MHOctetString &feature) 
    12851336        }
    12861337    }
    12871338
     1339    // InteractionChannelExtension.
     1340    if (strings[0] == "ICProfile" || strings[0] == "ICP") {
     1341        if (strings.count() != 2) return false;
     1342        if (strings[1] == "0")
     1343            return true; // // InteractionChannelExtension.
     1344        if (strings[1] == "1")
     1345            return false; // ICStreamingExtension.
     1346        return false;
     1347    }
     1348
    12881349    // Otherwise return false.
    12891350    return false;
    12901351}
    FILE *__mhlogStream = NULL; 
    14421503void __mhlog(QString logtext)
    14431504{
    14441505    QByteArray tmp = logtext.toAscii();
    1445     fprintf(__mhlogStream, "%s\n", tmp.constData());
     1506    fprintf(__mhlogStream, "[freemheg] %s\n", tmp.constData());
    14461507}
    14471508
    14481509// Called from the user of the library to set the logging.
  • mythtv/libs/libmythfreemheg/Engine.h

    diff --git a/mythtv/libs/libmythfreemheg/Engine.h b/mythtv/libs/libmythfreemheg/Engine.h
    index e392cb1..6d2a1d9 100644
    a b class MHEngine: public MHEG { 
    117117    // Generate a UserAction event i.e. a key press.
    118118    virtual void GenerateUserAction(int nCode);
    119119    virtual void EngineEvent(int nCode);
     120    virtual void StreamStarted(MHStream*, bool bStarted);
    120121
    121122    // Called from an ingredient to request a load of external content.
    122123    void RequestExternalContent(MHIngredient *pRequester);
  • mythtv/libs/libmythfreemheg/Presentable.h

    diff --git a/mythtv/libs/libmythfreemheg/Presentable.h b/mythtv/libs/libmythfreemheg/Presentable.h
    index faa4869..fbd4c2c 100644
    a b class MHPresentable : public MHIngredient 
    4343    virtual void Stop(MHEngine *engine);
    4444
    4545    // Additional actions for stream components.
    46     virtual void SetStreamRef(MHEngine *, const MHContentRef &) {}
    4746    virtual void BeginPlaying(MHEngine *) {}
    4847    virtual void StopPlaying(MHEngine *) {}
    4948};
  • mythtv/libs/libmythfreemheg/Programs.cpp

    diff --git a/mythtv/libs/libmythfreemheg/Programs.cpp b/mythtv/libs/libmythfreemheg/Programs.cpp
    index ab5658a..0bea727 100644
    a b  
    2929#include "Logging.h"
    3030#include "freemheg.h"
    3131
     32#include <QStringList>
    3233#include <sys/timeb.h>
    3334#ifdef __FreeBSD__
    3435#include <sys/time.h>
    static int GetInt(MHParameter *parm, MHEngine *engine) 
    138139    return un.m_nIntVal;
    139140}
    140141
     142// Return a bool value.  May throw an exception if it isn't the correct type.
     143static bool GetBool(MHParameter *parm, MHEngine *engine)
     144{
     145    MHUnion un;
     146    un.GetValueFrom(*parm, engine);
     147    un.CheckType(MHUnion::U_Bool);
     148    return un.m_fBoolVal;
     149}
     150
    141151// Extract a string value.
    142152static void GetString(MHParameter *parm, MHOctetString &str, MHEngine *engine)
    143153{
    void MHResidentProgram::CallProgram(bool fIsFork, const MHObjectRef &success, co 
    738748        else if (m_Name.Equal("SSM"))   // SetSubtitleMode
    739749        {
    740750            // Enable or disable subtitles in addition to MHEG.
    741             MHERROR("SetSubtitleMode ResidentProgram is not implemented");
     751            if (args.Size() == 1) {
     752                bool status = GetBool(args.GetAt(0), engine);
     753                MHLOG(MHLogNotifications, QString("NOTE SetSubtitleMode %1")
     754                    .arg(status ? "enabled" : "disabled"));
     755                // TODO Notify player
     756                SetSuccessFlag(success, true, engine);
     757            }
     758            else SetSuccessFlag(success, false, engine);
    742759        }
    743760
    744761        else if (m_Name.Equal("WAI"))   // WhoAmI
    void MHResidentProgram::CallProgram(bool fIsFork, const MHObjectRef &success, co 
    798815
    799816        else if (m_Name.Equal("SBI"))   // SetBroadcastInterrupt
    800817        {
    801             // Required for InteractionChannelExtension
     818            // Required for NativeApplicationExtension
    802819            // En/dis/able program interruptions e.g. green button
    803             MHERROR("SetBroadcastInterrupt ResidentProgram is not implemented");
     820            if (args.Size() == 1) {
     821                bool status = GetBool(args.GetAt(0), engine);
     822                MHLOG(MHLogNotifications, QString("NOTE SetBroadcastInterrupt %1")
     823                    .arg(status ? "enabled" : "disabled"));
     824                // Nothing todo at present
     825                SetSuccessFlag(success, true, engine);
     826            }
     827            else SetSuccessFlag(success, false, engine);
    804828        }
    805829
    806         else if (m_Name.Equal("GIS"))   // GetICStatus
    807         {
    808             // Required for NativeApplicationExtension
    809             MHERROR("GetICStatus ResidentProgram is not implemented");
     830        // InteractionChannelExtension
     831        else if (m_Name.Equal("GIS")) { // GetICStatus
     832            if (args.Size() == 1)
     833            {
     834                int ICstatus = engine->GetContext()->GetICStatus();
     835                MHLOG(MHLogNotifications, "NOTE InteractionChannel " + QString(
     836                    ICstatus == 0 ? "active" : ICstatus == 1 ? "inactive" :
     837                    ICstatus == 2 ? "disabled" : "undefined"));
     838                engine->FindObject(*(args.GetAt(0)->GetReference()))->SetVariableValue(ICstatus);
     839                SetSuccessFlag(success, true, engine);
     840            }
     841            else SetSuccessFlag(success, false, engine);
     842        }
     843        else if (m_Name.Equal("RDa")) { // ReturnData
     844            if (args.Size() >= 3)
     845            {
     846                MHOctetString string;
     847                GetString(args.GetAt(0), string, engine);
     848                QString url = QString::fromUtf8((const char *)string.Bytes(), string.Size());
     849
     850                // Variable name/value pairs
     851                QStringList params;
     852                int i = 1;
     853                for (; i + 2 < args.Size(); i += 2)
     854                {
     855                    GetString(args.GetAt(i), string, engine);
     856                    QString name = QString::fromUtf8((const char *)string.Bytes(), string.Size());
     857                    QString val;
     858                    MHUnion un;
     859                    un.GetValueFrom(*(args.GetAt(i+1)), engine);
     860                    switch (un.m_Type) {
     861                    case MHUnion::U_Int:
     862                        val = QString::number(un.m_nIntVal);
     863                        break;
     864                    case MHParameter::P_Bool:
     865                        val = un.m_fBoolVal ? "true" : "false";
     866                        break;
     867                    case MHParameter::P_String:
     868                        val = QString::fromUtf8((const char*)un.m_StrVal.Bytes(), un.m_StrVal.Size());
     869                        break;
     870                    case MHParameter::P_ObjRef:
     871                        val = un.m_ObjRefVal.Printable();
     872                        break;
     873                    case MHParameter::P_ContentRef:
     874                        val = un.m_ContentRefVal.Printable();
     875                        break;
     876                    case MHParameter::P_Null:
     877                        val = "<NULL>";
     878                        break;
     879                    default:
     880                        val = QString("<type %1>").arg(un.m_Type);
     881                        break;
     882                    }
     883                    params += name + "=" + val;
     884                }
     885                // TODO
     886                MHLOG(MHLogNotifications, "NOTE ReturnData '" + url + "' { " + params.join(" ") + " }");
     887                // HTTP response code, 0= none
     888                engine->FindObject(*(args.GetAt(i)->GetReference()))->SetVariableValue(0);
     889                // HTTP response data
     890                string = "";
     891                engine->FindObject(*(args.GetAt(i+1)->GetReference()))->SetVariableValue(string);
     892                SetSuccessFlag(success, false, engine);
     893            }
     894            else SetSuccessFlag(success, false, engine);
     895        }
     896        else if (m_Name.Equal("SHF")) { // SetHybridFileSystem
     897            if (args.Size() == 2)
     898            {
     899                MHOctetString string;
     900                GetString(args.GetAt(0), string, engine);
     901                QString str = QString::fromUtf8((const char *)string.Bytes(), string.Size());
     902                GetString(args.GetAt(1), string, engine);
     903                QString str2 = QString::fromUtf8((const char *)string.Bytes(), string.Size());
     904                // TODO
     905                MHLOG(MHLogNotifications, QString("NOTE SetHybridFileSystem %1=%2")
     906                    .arg(str).arg(str2));
     907                SetSuccessFlag(success, false, engine);
     908            }
     909            else SetSuccessFlag(success, false, engine);
    810910        }
    811911
    812912        else
    void MHCall::PrintArgs(FILE *fd, int nTabs) const 
    9081008        m_Parameters.GetAt(i)->PrintMe(fd, 0);
    9091009    }
    9101010
    911     fprintf(fd, " )\n");
     1011    fprintf(fd, " )");
    9121012}
    9131013
    9141014void MHCall::Perform(MHEngine *engine)
  • mythtv/libs/libmythfreemheg/Root.cpp

    diff --git a/mythtv/libs/libmythfreemheg/Root.cpp b/mythtv/libs/libmythfreemheg/Root.cpp
    index ffa184c..f6d07b4 100644
    a b void MHRoot::PrintMe(FILE *fd, int nTabs) const 
    4444// An action was attempted on an object of a class which doesn't support this.
    4545void MHRoot::InvalidAction(const char *actionName)
    4646{
    47     MHLOG(MHLogWarning, QString("Action \"%1\" is not understood by class \"%2\"").arg(actionName).arg(ClassName()));
     47    MHLOG(MHLogWarning, QString("WARN Action \"%1\" is not understood by class \"%2\"").arg(actionName).arg(ClassName()));
    4848    throw "Invalid Action";
    4949}
    5050
  • mythtv/libs/libmythfreemheg/Root.h

    diff --git a/mythtv/libs/libmythfreemheg/Root.h b/mythtv/libs/libmythfreemheg/Root.h
    index 929c272..3b436c8 100644
    a b class MHRoot 
    175175    virtual void ScaleVideo(int /*xScale*/, int /*yScale*/, MHEngine *) { InvalidAction("ScaleVideo"); }
    176176    virtual void SetVideoDecodeOffset(int /*newXOffset*/, int /*newYOffset*/, MHEngine *) { InvalidAction("SetVideoDecodeOffset"); }
    177177    virtual void GetVideoDecodeOffset(MHRoot * /*pXOffset*/, MHRoot * /*pYOffset*/, MHEngine *) { InvalidAction("GetVideoDecodeOffset"); }
     178    virtual void GetCounterPosition(MHRoot * /*pPos*/, MHEngine *) { InvalidAction("GetCounterPosition"); }
     179    virtual void GetCounterMaxPosition(MHRoot * /*pPos*/, MHEngine *) { InvalidAction("GetCounterMaxPosition"); }
     180    virtual void SetCounterPosition(int /*pos*/, MHEngine *) { InvalidAction("SetCounterPosition"); }
     181    virtual void SetSpeed(int /*speed 0=stop*/, MHEngine *) { InvalidAction("SetSpeed"); }
    178182
    179183    // Actions on Interactibles.
    180184    virtual void SetInteractionStatus(bool /*newStatus*/, MHEngine *) { InvalidAction("SetInteractionStatus"); }
  • mythtv/libs/libmythfreemheg/Stream.cpp

    diff --git a/mythtv/libs/libmythfreemheg/Stream.cpp b/mythtv/libs/libmythfreemheg/Stream.cpp
    index aa20faa..dd0547d 100644
    a b void MHStream::Initialise(MHParseNode *p, MHEngine *engine) 
    6565                m_Multiplex.Append(pRtGraph);
    6666                pRtGraph->Initialise(pItem, engine);
    6767            }
    68 
    69             // Ignore unknown items
     68            else
     69            {
     70                // Ignore unknown items
     71                MHLOG(MHLogWarning, QString("WARN unknown stream type %1")
     72                    .arg(pItem->GetTagNo()));
     73            }
    7074        }
    7175    }
    7276
    void MHStream::Activation(MHEngine *engine) 
    158162    MHPresentable::Activation(engine);
    159163
    160164    // Start playing all active stream components.
    161     for (int i = 0; i < m_Multiplex.Size(); i++)
    162     {
    163         m_Multiplex.GetAt(i)->BeginPlaying(engine);
    164     }
    165 
     165    BeginPlaying(engine);
     166    // subclasses are responsible for setting m_fRunning and generating IsRunning.
    166167    m_fRunning = true;
    167168    engine->EventTriggered(this, EventIsRunning);
    168169}
    void MHStream::Deactivation(MHEngine *engine) 
    174175        return;
    175176    }
    176177
    177     // Stop playing all active Stream components
    178     for (int i = 0; i < m_Multiplex.Size(); i++)
    179     {
    180         m_Multiplex.GetAt(i)->StopPlaying(engine);
    181     }
    182 
    183178    MHPresentable::Deactivation(engine);
     179    StopPlaying(engine);
    184180}
    185181
    186182// The MHEG corrigendum allows SetData to be targeted to a stream so
    void MHStream::Deactivation(MHEngine *engine) 
    188184void MHStream::ContentPreparation(MHEngine *engine)
    189185{
    190186    engine->EventTriggered(this, EventContentAvailable); // Perhaps test for the streams being available?
    191 
    192     for (int i = 0; i < m_Multiplex.Size(); i++)
    193     {
    194         m_Multiplex.GetAt(i)->SetStreamRef(engine, m_ContentRef);
    195     }
     187    if (m_fRunning)
     188        BeginPlaying(engine);
    196189}
    197190
    198 // TODO: Generate StreamPlaying and StreamStopped events.  These are supposed
    199 // to be generated as the first and last frames are displayed.
    200 
    201191// Return an object if there is a matching component.
    202192MHRoot *MHStream::FindByObjectNo(int n)
    203193{
    MHRoot *MHStream::FindByObjectNo(int n) 
    219209    return NULL;
    220210}
    221211
     212void MHStream::BeginPlaying(MHEngine *engine)
     213{
     214    QString stream;
     215    MHOctetString &str = m_ContentRef.m_ContentRef;
     216    if (str.Size() != 0) stream = QString::fromUtf8((const char *)str.Bytes(), str.Size());
     217    if ( !engine->GetContext()->BeginStream(stream, this))
     218        engine->EventTriggered(this, EventEngineEvent, 204); // StreamRefError
     219
     220    // Start playing all active stream components.
     221    for (int i = 0; i < m_Multiplex.Size(); i++)
     222        m_Multiplex.GetAt(i)->BeginPlaying(engine);
     223
     224    //engine->EventTriggered(this, EventStreamPlaying);
     225}
     226
     227void MHStream::StopPlaying(MHEngine *engine)
     228{
     229    // Stop playing all active Stream components
     230    for (int i = 0; i < m_Multiplex.Size(); i++)
     231        m_Multiplex.GetAt(i)->StopPlaying(engine);
     232    engine->GetContext()->EndStream();
     233    engine->EventTriggered(this, EventStreamStopped);
     234}
     235
     236void MHStream::GetCounterPosition(MHRoot *pResult, MHEngine *engine)
     237{
     238    // StreamCounterUnits (mS)
     239    pResult->SetVariableValue((int)engine->GetContext()->GetStreamPos());
     240}
     241
     242void MHStream::GetCounterMaxPosition(MHRoot *pResult, MHEngine *engine)
     243{
     244    // StreamCounterUnits (mS)
     245    pResult->SetVariableValue((int)engine->GetContext()->GetStreamMaxPos());
     246}
     247
     248void MHStream::SetCounterPosition(int pos, MHEngine *engine)
     249{
     250    // StreamCounterUnits (mS)
     251    engine->GetContext()->SetStreamPos(pos);
     252}
     253
     254void MHStream::SetSpeed(int speed, MHEngine *engine)
     255{
     256    engine->GetContext()->StreamPlay(speed);
     257}
     258
    222259MHAudio::MHAudio()
    223260{
    224261    m_nComponentTag = 0;
    void MHAudio::Activation(MHEngine *engine) 
    275312    m_fRunning = true;
    276313    engine->EventTriggered(this, EventIsRunning);
    277314
    278     if (m_fStreamPlaying && m_streamContentRef.IsSet())
    279     {
    280         QString stream;
    281         MHOctetString &str = m_streamContentRef.m_ContentRef;
    282 
    283         if (str.Size() != 0)
    284         {
    285             stream = QString::fromUtf8((const char *)str.Bytes(), str.Size());
    286         }
    287 
    288         engine->GetContext()->BeginAudio(stream, m_nComponentTag);
    289     }
     315    if (m_fStreamPlaying)
     316        engine->GetContext()->BeginAudio(m_nComponentTag);
    290317}
    291318
    292319// Deactivation for Audio is defined in the corrigendum
    void MHAudio::Deactivation(MHEngine *engine) 
    308335    MHPresentable::Deactivation(engine);
    309336}
    310337
    311 void MHAudio::SetStreamRef(MHEngine *engine, const MHContentRef &cr)
    312 {
    313     m_streamContentRef.Copy(cr);
    314 
    315     if (m_fStreamPlaying)
    316     {
    317         BeginPlaying(engine);
    318     }
    319 }
    320 
    321338void MHAudio::BeginPlaying(MHEngine *engine)
    322339{
    323340    m_fStreamPlaying = true;
    324 
    325     if (m_fRunning && m_streamContentRef.IsSet())
    326     {
    327         QString stream;
    328         MHOctetString &str = m_streamContentRef.m_ContentRef;
    329 
    330         if (str.Size() != 0)
    331         {
    332             stream = QString::fromUtf8((const char *)str.Bytes(), str.Size());
    333         }
    334 
    335         engine->GetContext()->BeginAudio(stream, m_nComponentTag);
    336     }
     341    if (m_fRunning)
     342        engine->GetContext()->BeginAudio(m_nComponentTag);
    337343}
    338344
    339345void MHAudio::StopPlaying(MHEngine *engine)
    void MHVideo::Activation(MHEngine *engine) 
    491497    }
    492498
    493499    MHVisible::Activation(engine);
    494 
    495     if (m_fStreamPlaying && m_streamContentRef.IsSet())
    496     {
    497         QString stream;
    498         MHOctetString &str = m_streamContentRef.m_ContentRef;
    499 
    500         if (str.Size() != 0)
    501         {
    502             stream = QString::fromUtf8((const char *)str.Bytes(), str.Size());
    503         }
    504 
    505         engine->GetContext()->BeginVideo(stream, m_nComponentTag);
    506     }
     500    if (m_fStreamPlaying)
     501        engine->GetContext()->BeginVideo(m_nComponentTag);
    507502}
    508503
    509504void MHVideo::Deactivation(MHEngine *engine)
    void MHVideo::Deactivation(MHEngine *engine) 
    521516    }
    522517}
    523518
    524 void MHVideo::SetStreamRef(MHEngine *engine, const MHContentRef &cr)
    525 {
    526     m_streamContentRef.Copy(cr);
    527 
    528     if (m_fStreamPlaying)
    529     {
    530         BeginPlaying(engine);
    531     }
    532 }
    533 
    534519void MHVideo::BeginPlaying(MHEngine *engine)
    535520{
    536521    m_fStreamPlaying = true;
    537 
    538     if (m_fRunning && m_streamContentRef.IsSet())
    539     {
    540         QString stream;
    541         MHOctetString &str = m_streamContentRef.m_ContentRef;
    542 
    543         if (str.Size() != 0)
    544         {
    545             stream = QString::fromUtf8((const char *)str.Bytes(), str.Size());
    546         }
    547 
    548         engine->GetContext()->BeginVideo(stream, m_nComponentTag);
    549     }
     522    if (m_fRunning)
     523        engine->GetContext()->BeginVideo(m_nComponentTag);
    550524}
    551525
    552526void MHVideo::StopPlaying(MHEngine *engine)
    void MHRTGraphics::PrintMe(FILE *fd, int nTabs) const 
    581555    MHVisible::PrintMe(fd, nTabs);
    582556    //
    583557}
     558
     559// Fix for MHActionGenericObjectRef
     560void MHActionGenericObjectRefFix::Perform(MHEngine *engine)
     561{
     562    MHObjectRef ref;
     563    if (m_RefObject.m_fIsDirect)
     564        m_RefObject.GetValue(ref, engine);
     565    else
     566        ref.Copy(*m_RefObject.GetReference());
     567    CallAction(engine, Target(engine), engine->FindObject(ref));
     568}
  • mythtv/libs/libmythfreemheg/Stream.h

    diff --git a/mythtv/libs/libmythfreemheg/Stream.h b/mythtv/libs/libmythfreemheg/Stream.h
    index 18dfb03..78ad2b1 100644
    a b class MHStream : public MHPresentable 
    4444    virtual void ContentPreparation(MHEngine *engine);
    4545
    4646    virtual MHRoot *FindByObjectNo(int n);
     47
     48    virtual void BeginPlaying(MHEngine *engine);
     49    virtual void StopPlaying(MHEngine *engine);
     50
     51    // Actions
     52    virtual void GetCounterPosition(MHRoot *, MHEngine *);
     53    virtual void GetCounterMaxPosition(MHRoot *, MHEngine *);
     54    virtual void SetCounterPosition(int /*pos*/, MHEngine *);
     55    virtual void SetSpeed(int, MHEngine *engine);
     56
    4757  protected:
    4858    MHOwnPtrSequence <MHPresentable> m_Multiplex;
    4959    enum Storage { ST_Mem = 1, ST_Stream = 2 } m_nStorage;
    class MHAudio : public MHPresentable 
    6272    virtual void Activation(MHEngine *engine);
    6373    virtual void Deactivation(MHEngine *engine);
    6474
    65     virtual void SetStreamRef(MHEngine *, const MHContentRef &);
    6675    virtual void BeginPlaying(MHEngine *engine);
    6776    virtual void StopPlaying(MHEngine *engine);
    6877
    class MHAudio : public MHPresentable 
    7180    int m_nOriginalVol;
    7281
    7382    bool m_fStreamPlaying;
    74     MHContentRef m_streamContentRef;
    7583};
    7684
    7785class MHVideo : public MHVisible 
    class MHVideo : public MHVisible 
    97105    virtual void SetVideoDecodeOffset(int newXOffset, int newYOffset, MHEngine *);
    98106    virtual void GetVideoDecodeOffset(MHRoot *pXOffset, MHRoot *pYOffset, MHEngine *);
    99107
    100     virtual void SetStreamRef(MHEngine *, const MHContentRef &);
    101108    virtual void BeginPlaying(MHEngine *engine);
    102109    virtual void StopPlaying(MHEngine *engine);
    103110
    class MHVideo : public MHVisible 
    109116    int     m_nDecodeWidth, m_nDecodeHeight;
    110117
    111118    bool m_fStreamPlaying;
    112     MHContentRef m_streamContentRef;
    113119};
    114120
    115121// Real-time graphics - not needed for UK MHEG.
    class MHGetVideoDecodeOffset: public MHActionObjectRef2 
    146152    virtual void CallAction(MHEngine *engine, MHRoot *pTarget, MHRoot *pArg1, MHRoot *pArg2) { pTarget->GetVideoDecodeOffset(pArg1, pArg2, engine); }
    147153};
    148154
     155class MHActionGenericObjectRefFix: public MHActionGenericObjectRef
     156{
     157public:
     158    MHActionGenericObjectRefFix(const char *name) : MHActionGenericObjectRef(name) {}
     159    virtual void Perform(MHEngine *engine);
     160};
     161
     162class MHGetCounterPosition: public MHActionGenericObjectRefFix
     163{
     164public:
     165    MHGetCounterPosition(): MHActionGenericObjectRefFix(":GetCounterPosition")  {}
     166    virtual void CallAction(MHEngine *engine, MHRoot *pTarget, MHRoot *pArg)
     167        { pTarget->GetCounterPosition(pArg, engine); }
     168};
     169
     170class MHGetCounterMaxPosition: public MHActionGenericObjectRefFix
     171{
     172public:
     173    MHGetCounterMaxPosition(): MHActionGenericObjectRefFix(":GetCounterMaxPosition")  {}
     174    virtual void CallAction(MHEngine *engine, MHRoot *pTarget, MHRoot *pArg)
     175        { pTarget->GetCounterMaxPosition(pArg, engine); }
     176};
     177
     178class MHSetCounterPosition: public MHActionInt
     179{
     180public:
     181    MHSetCounterPosition(): MHActionInt(":SetCounterPosition")  {}
     182    virtual void CallAction(MHEngine *engine, MHRoot *pTarget, int nArg)
     183        { pTarget->SetCounterPosition(nArg, engine); }
     184};
     185
     186
     187class MHSetSpeed: public MHElemAction
     188{
     189    typedef MHElemAction base;
     190public:
     191    MHSetSpeed(): base(":SetSpeed") {}
     192    virtual void Initialise(MHParseNode *p, MHEngine *engine) {
     193        //printf("SetSpeed Initialise args: "); p->PrintMe(stdout);
     194        base::Initialise(p, engine);
     195        MHParseNode *pn = p->GetArgN(1);
     196        if (pn->m_nNodeType == MHParseNode::PNSeq) pn = pn->GetArgN(0);
     197        m_Argument.Initialise(pn, engine);
     198    }
     199    virtual void Perform(MHEngine *engine) {
     200        Target(engine)->SetSpeed(m_Argument.GetValue(engine), engine);
     201    }
     202protected:
     203    virtual void PrintArgs(FILE *fd, int) const { m_Argument.PrintMe(fd, 0); }
     204    MHGenericInteger m_Argument;
     205};
     206
    149207
    150208#endif
  • mythtv/libs/libmythfreemheg/TokenGroup.cpp

    diff --git a/mythtv/libs/libmythfreemheg/TokenGroup.cpp b/mythtv/libs/libmythfreemheg/TokenGroup.cpp
    index b24160b..b147952 100644
    a b void MHTokenGroupItem::PrintMe(FILE *fd, int nTabs) const 
    6868        for (int i = 0; i < m_ActionSlots.Size(); i++)
    6969        {
    7070            PrintTabs(fd, nTabs + 2);
    71             fprintf(fd, "(\n");
     71            fprintf(fd, "( // slot %d\n", i);
    7272            MHActionSequence *pActions = m_ActionSlots.GetAt(i);
    7373
    7474            if (pActions->Size() == 0)
  • mythtv/libs/libmythfreemheg/Variables.h

    diff --git a/mythtv/libs/libmythfreemheg/Variables.h b/mythtv/libs/libmythfreemheg/Variables.h
    index 544a913..2363b06 100644
    a b class MHDivide: public MHIntegerAction { 
    205205  public:
    206206    MHDivide(): MHIntegerAction(":Divide") {}
    207207  protected:
    208     virtual int DoOp(int arg1, int arg2) { return arg1/arg2; } // What about divide by zero?
     208    virtual int DoOp(int arg1, int arg2) {
     209        if (arg2 == 0) throw "Divide by 0";
     210        return arg1/arg2;
     211    }
    209212};
    210213
    211214class MHModulo: public MHIntegerAction {
    212215  public:
    213216    MHModulo(): MHIntegerAction(":Modulo") {}
    214217  protected:
    215     virtual int DoOp(int arg1, int arg2) { return arg1%arg2; } // What about divide by zero?
     218    virtual int DoOp(int arg1, int arg2) { return arg2 ? arg1%arg2 : 0; }
    216219};
    217220
    218221// Append -
  • mythtv/libs/libmythfreemheg/Visible.cpp

    diff --git a/mythtv/libs/libmythfreemheg/Visible.cpp b/mythtv/libs/libmythfreemheg/Visible.cpp
    index 1d69a39..8ee4e3f 100644
    a b void MHVisible::Deactivation(MHEngine *engine) 
    164164MHRgba MHVisible::GetColour(const MHColour &colour)
    165165{
    166166    int red = 0, green = 0, blue = 0, alpha = 0;
    167     int cSize = colour.m_ColStr.Size();
    168 
    169     if (cSize != 4)
     167    if (colour.IsSet())
    170168    {
    171         MHLOG(MHLogWarning, QString("Colour string has length %1 not 4.").arg(cSize));
    172     }
     169        int cSize = colour.m_ColStr.Size();
    173170
    174     // Just in case the length is short we handle those properly.
    175     if (cSize > 0)
    176     {
    177         red = colour.m_ColStr.GetAt(0);
    178     }
     171        if (cSize != 4)
     172        {
     173            MHLOG(MHLogWarning, QString("Colour string has length %1 not 4.").arg(cSize));
     174        }
    179175
    180     if (cSize > 1)
    181     {
    182         green = colour.m_ColStr.GetAt(1);
    183     }
     176        // Just in case the length is short we handle those properly.
     177        if (cSize > 0)
     178        {
     179            red = colour.m_ColStr.GetAt(0);
     180        }
    184181
    185     if (cSize > 2)
    186     {
    187         blue = colour.m_ColStr.GetAt(2);
    188     }
     182        if (cSize > 1)
     183        {
     184            green = colour.m_ColStr.GetAt(1);
     185        }
    189186
    190     if (cSize > 3)
    191     {
    192         alpha = 255 - colour.m_ColStr.GetAt(3);    // Convert transparency to alpha
     187        if (cSize > 2)
     188        {
     189            blue = colour.m_ColStr.GetAt(2);
     190        }
     191
     192        if (cSize > 3)
     193        {
     194            alpha = 255 - colour.m_ColStr.GetAt(3);    // Convert transparency to alpha
     195        }
    193196    }
    194197
    195198    return MHRgba(red, green, blue, alpha);
  • mythtv/libs/libmythfreemheg/freemheg.h

    diff --git a/mythtv/libs/libmythfreemheg/freemheg.h b/mythtv/libs/libmythfreemheg/freemheg.h
    index 0b61f65..8a9a983 100644
    a b  
    2222#if !defined(FREEMHEG_H)
    2323#define FREEMHEG_H
    2424
     25#include <QtGlobal>
     26#include <QString>
     27#include <QByteArray>
    2528#include <QRegion>
     29#include <QRect>
     30#include <QSize>
    2631
    2732#include <stdio.h>
    2833#include <stdlib.h>
    class MHTextDisplay; 
    3237class MHBitmapDisplay;
    3338class MHContext;
    3439class MHEG;
     40class MHStream;
    3541
    3642// Called to create a new instance of the module.
    3743extern MHEG *MHCreateEngine(MHContext *context);
    class MHEG 
    5157    // Generate a UserAction event i.e. a key press.
    5258    virtual void GenerateUserAction(int nCode) = 0;
    5359    virtual void EngineEvent(int) = 0;
     60    virtual void StreamStarted(MHStream*, bool bStarted = true) = 0;
    5461};
    5562
    5663// Logging control
    class MHContext 
    128135    // the m_stopped condition if we have.
    129136    virtual bool CheckStop(void) = 0;
    130137
    131     // Begin playing audio from the specified stream
    132     virtual bool BeginAudio(const QString &stream, int tag) = 0;
     138    // Begin playing the specified stream
     139    virtual bool BeginStream(const QString &str, MHStream* notify = 0) = 0;
     140    // Stop playing stream
     141    virtual void EndStream() = 0;
     142    // Begin playing audio component
     143    virtual bool BeginAudio(int tag) = 0;
    133144    // Stop playing audio
    134     virtual void StopAudio(void) = 0;
    135     // Begin displaying video from the specified stream
    136     virtual bool BeginVideo(const QString &stream, int tag) = 0;
     145    virtual void StopAudio() = 0;
     146    // Begin displaying video component
     147    virtual bool BeginVideo(int tag) = 0;
    137148    // Stop displaying video
    138     virtual void StopVideo(void) = 0;
     149    virtual void StopVideo() = 0;
     150    // Get current stream position in mS, -1 if unknown
     151    virtual long GetStreamPos() = 0;
     152    // Get current stream size in mS, -1 if unknown
     153    virtual long GetStreamMaxPos() = 0;
     154    // Set current stream position in mS
     155    virtual long SetStreamPos(long) = 0;
     156    // Play or pause a stream
     157    virtual void StreamPlay(bool play = true) = 0;
    139158
    140159    // Get the context id strings.
    141160    virtual const char *GetReceiverId(void) = 0;
    142161    virtual const char *GetDSMCCId(void) = 0;
     162
     163    // InteractionChannel
     164    virtual int GetICStatus() = 0; // 0= Active, 1= Inactive, 2= Disabled
    143165};
    144166
    145167// Dynamic Line Art objects record a sequence of drawing actions.
    class MHBitmapDisplay 
    191213    // Creation functions
    192214    virtual void CreateFromPNG(const unsigned char *data, int length) = 0;
    193215    virtual void CreateFromMPEG(const unsigned char *data, int length) = 0;
     216    virtual void CreateFromJPEG(const unsigned char *data, int length) = 0;
    194217    // Scale the bitmap.  Only used for image derived from MPEG I-frames.
    195218    virtual void ScaleImage(int newWidth, int newHeight) = 0;
    196219    // Information about the image.
  • mythtv/libs/libmythtv/fileringbuffer.cpp

    diff --git a/mythtv/libs/libmythtv/fileringbuffer.cpp b/mythtv/libs/libmythtv/fileringbuffer.cpp
    index 944c98d..1449883 100644
    a b bool FileRingBuffer::OpenFile(const QString &lfilename, uint retry_ms) 
    356356    commserror = false;
    357357    numfailures = 0;
    358358
    359     rawbitrate = 8000;
     359    // The initial bitrate needs to be set with consideration for low bit rate
     360    // streams (e.g. radio @ 64Kbps) such that fill_min bytes are received
     361    // in a reasonable time period to enable decoders to peek the first few KB
     362    // to determine type & settings.
     363    if (is_local)
     364        rawbitrate = 256; // Allow for radio
     365    else
     366        rawbitrate = 128; // remotefile
     367
    360368    CalcReadAheadThresh();
    361369
    362370    bool ok = fd2 >= 0 || remotefile;
    int FileRingBuffer::safe_read(int fd, void *data, uint sz) 
    487495 */
    488496int FileRingBuffer::safe_read(RemoteFile *rf, void *data, uint sz)
    489497{
    490     int ret = rf->Read(data, sz);
    491     if (ret < 0)
    492     {
    493         LOG(VB_GENERAL, LOG_ERR, LOC +
    494             "safe_read(RemoteFile* ...): read failed");
    495            
    496         poslock.lockForRead();
    497         rf->Seek(internalreadpos - readAdjust, SEEK_SET);
    498         poslock.unlock();
    499         numfailures++;
    500     }
    501     else if (ret == 0)
     498    for (int retries = 0; ; ++retries)
    502499    {
    503         LOG(VB_FILE, LOG_INFO, LOC +
    504             "safe_read(RemoteFile* ...): at EOF");
     500        int ret = rf->Read(data, sz);
     501        if (ret > 0)
     502            return ret;
     503        else if (ret < 0)
     504        {
     505            LOG(VB_GENERAL, LOG_ERR, LOC +
     506                "safe_read(RemoteFile* ...): read failed");
     507
     508            poslock.lockForRead();
     509            rf->Seek(internalreadpos - readAdjust, SEEK_SET);
     510            poslock.unlock();
     511            numfailures++;
     512            return ret;
     513        }
     514        // Retry for 300mS if liveTV for low bit rate (radio) streams
     515        else if (!livetvchain || retries >= 5)
     516            break;
     517
     518        usleep(60000);
    505519    }
    506520
    507     return ret;
     521    LOG(VB_FILE, LOG_INFO, LOC +
     522        "safe_read(RemoteFile* ...): at EOF");
     523    return 0;
    508524}
    509525
    510526long long FileRingBuffer::GetReadPosition(void) const
  • new file mythtv/libs/libmythtv/icringbuffer.cpp

    diff --git a/mythtv/libs/libmythtv/icringbuffer.cpp b/mythtv/libs/libmythtv/icringbuffer.cpp
    new file mode 100644
    index 0000000..f099de2
    - +  
     1#include "icringbuffer.h"
     2
     3#include <stdio.h> // SEEK_SET
     4
     5#include <QScopedPointer>
     6#include <QWriteLocker>
     7
     8#include "netstream.h"
     9#include "mythlogging.h"
     10
     11
     12#define LOC QString("ICRingBuf ")
     13
     14
     15ICRingBuffer::ICRingBuffer(const QString &url, RingBuffer *parent)
     16  : RingBuffer(kRingBufferType), m_stream(0), m_parent(parent)
     17{
     18    startreadahead = true;
     19    OpenFile(url);
     20}
     21
     22ICRingBuffer::~ICRingBuffer()
     23{
     24    delete m_stream;
     25    delete m_parent;
     26}
     27
     28bool ICRingBuffer::IsOpen(void) const
     29{
     30    return m_stream ? m_stream->IsOpen() : false;
     31}
     32
     33bool ICRingBuffer::OpenFile(const QString &url, uint retry_ms)
     34{
     35    if (!NetStream::IsSupported(url))
     36    {
     37        LOG(VB_GENERAL, LOG_ERR, LOC + QString("Unsupported URL %1").arg(url) );
     38        return false;
     39    }
     40
     41    QScopedPointer<NetStream> stream(new NetStream(url));
     42    if (!stream || !stream->IsOpen())
     43    {
     44        LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to open %1").arg(url) );
     45        return false;
     46    }
     47
     48    if (!stream->WaitTillReady(30000))
     49    {
     50        LOG(VB_GENERAL, LOG_ERR, LOC + QString("Stream not ready%1").arg(url) );
     51        return false;
     52    }
     53
     54    if (m_parent)
     55        m_parent->Pause();
     56
     57    QWriteLocker locker(&rwlock);
     58
     59    safefilename = url;
     60    filename = url;
     61
     62    delete m_stream;
     63    m_stream = stream.take();
     64
     65    // The initial bitrate needs to be set with consideration for low bit rate
     66    // streams (e.g. radio @ 64Kbps) such that fill_min bytes are received
     67    // in a reasonable time period to enable decoders to peek the first few KB
     68    // to determine type & settings.
     69    rawbitrate = 128; // remotefile
     70    CalcReadAheadThresh();
     71
     72    locker.unlock();
     73    Reset(true, false, true);
     74
     75    LOG(VB_GENERAL, LOG_INFO, LOC + QString("Opened %1").arg(url));
     76    return true;
     77}
     78
     79long long ICRingBuffer::GetReadPosition(void) const
     80{
     81    return m_stream ? m_stream->GetReadPosition() : 0;
     82}
     83
     84long long ICRingBuffer::Seek(long long pos, int whence, bool has_lock)
     85{
     86    return m_stream ? (whence == SEEK_SET ? m_stream->Seek(pos) : -1) : -1;
     87}
     88
     89int ICRingBuffer::safe_read(void *data, uint sz)
     90{
     91    return m_stream ? m_stream->safe_read(data, sz, 10) : (ateof = true, 0);
     92}
     93
     94long long ICRingBuffer::GetRealFileSize(void) const
     95{
     96    return m_stream ? m_stream->GetSize() : -1;
     97}
     98
     99// Take ownership of parent RingBuffer
     100RingBuffer *ICRingBuffer::Take()
     101{
     102    RingBuffer *parent = m_parent;
     103    m_parent = 0;
     104    return parent;
     105}
     106
     107// End of file
  • new file mythtv/libs/libmythtv/icringbuffer.h

    diff --git a/mythtv/libs/libmythtv/icringbuffer.h b/mythtv/libs/libmythtv/icringbuffer.h
    new file mode 100644
    index 0000000..15d4b1c
    - +  
     1#ifndef ICRINGBUFFER_H
     2#define ICRINGBUFFER_H
     3
     4#include "ringbuffer.h"
     5
     6class NetStream;
     7
     8class ICRingBuffer : public RingBuffer
     9{
     10  public:
     11    static enum RingBufferType const kRingBufferType = kRingBuffer_MHEG;
     12
     13    ICRingBuffer(const QString &url, RingBuffer *parent = 0);
     14    virtual ~ICRingBuffer();
     15
     16    // RingBuffer implementation
     17    virtual bool IsOpen(void) const;
     18    virtual long long GetReadPosition(void) const;
     19    virtual bool OpenFile(const QString &url,
     20                          uint retry_ms = kDefaultOpenTimeout);
     21    virtual long long Seek(long long pos, int whence, bool has_lock);
     22    virtual long long GetRealFileSize(void) const;
     23    virtual bool IsStreamed(void)       { return true;  }
     24    virtual bool IsSeekingAllowed(void) { return false; }
     25    virtual bool IsBookmarkAllowed(void) { return false; }
     26
     27  protected:
     28    virtual int safe_read(void *data, uint sz);
     29
     30    // Operations
     31  public:
     32    // Take ownership of parent RingBuffer
     33    RingBuffer *Take();
     34
     35  private:
     36    NetStream *m_stream;
     37    RingBuffer *m_parent; // parent RingBuffer
     38};
     39
     40#endif // ICRINGBUFFER_H
  • mythtv/libs/libmythtv/interactivetv.cpp

    diff --git a/mythtv/libs/libmythtv/interactivetv.cpp b/mythtv/libs/libmythtv/interactivetv.cpp
    index 2cdbea0..d1efa87 100644
    a b InteractiveTV::InteractiveTV(MythPlayer *nvp) 
    1717{
    1818    Restart(0, 0, false);
    1919
    20     if (VERBOSE_LEVEL_CHECK(VB_MHEG, LOG_ANY))
    21     {
    22         MHSetLogging(stdout, MHLogAll);
    23     }
    24     else
    25     {
    26         MHSetLogging(stdout, MHLogError);
    27     }
     20    MHSetLogging(stdout,
     21        VERBOSE_LEVEL_CHECK(VB_MHEG, LOG_DEBUG) ? MHLogAll :
     22        VERBOSE_LEVEL_CHECK(VB_MHEG, LOG_ANY) ?
     23            MHLogError | MHLogWarning | MHLogNotifications /*| MHLogLinks | MHLogActions | MHLogDetail*/ :
     24        MHLogError | MHLogWarning );
    2825}
    2926
    3027InteractiveTV::~InteractiveTV()
    void InteractiveTV::SetNetBootInfo(const unsigned char *data, uint length) 
    7976{
    8077    m_context->SetNetBootInfo(data, length);
    8178}
     79
     80bool InteractiveTV::StreamStarted(bool bStarted)
     81{
     82    return m_context->StreamStarted(bStarted);
     83}
  • mythtv/libs/libmythtv/interactivetv.h

    diff --git a/mythtv/libs/libmythtv/interactivetv.h b/mythtv/libs/libmythtv/interactivetv.h
    index c8e00e6..ba426de 100644
    a b class InteractiveTV 
    3939
    4040    // Get the initial component tags.
    4141    void GetInitialStreams(int &audioTag, int &videoTag);
     42    // Called when a stream starts or stops. Returns true if event is handled
     43    bool StreamStarted(bool bStarted = true);
    4244
    4345    MythPlayer *GetNVP(void) { return m_nvp; }
    4446
  • mythtv/libs/libmythtv/libmythtv.pro

    diff --git a/mythtv/libs/libmythtv/libmythtv.pro b/mythtv/libs/libmythtv/libmythtv.pro
    index 36fd87c..7d8ff73 100644
    a b HEADERS += mythsystemevent.h 
    165165HEADERS += avfringbuffer.h          ThreadedFileWriter.h
    166166HEADERS += ringbuffer.h             fileringbuffer.h
    167167HEADERS += dvdringbuffer.h          bdringbuffer.h
    168 HEADERS += streamingringbuffer.h    metadataimagehelper.h
     168HEADERS += streamingringbuffer.h    icringbuffer.h
     169HEADERS += metadataimagehelper.h
    169170
    170171SOURCES += recordinginfo.cpp
    171172SOURCES += dbcheck.cpp
    SOURCES += mythsystemevent.cpp 
    193194SOURCES += avfringbuffer.cpp        ThreadedFileWriter.cpp
    194195SOURCES += ringbuffer.cpp           fileringBuffer.cpp
    195196SOURCES += dvdringbuffer.cpp        bdringbuffer.cpp
    196 SOURCES += streamingringbuffer.cpp  metadataimagehelper.cpp
     197SOURCES += streamingringbuffer.cpp  icringbuffer.cpp
     198SOURCES += metadataimagehelper.cpp
    197199
    198200# DiSEqC
    199201HEADERS += diseqc.h                 diseqcsettings.h
    using_frontend { 
    428430        SOURCES += dsmcc.cpp                dsmcccache.cpp
    429431        SOURCES += dsmccbiop.cpp            dsmccobjcarousel.cpp
    430432
     433         # MHEG interaction channel
     434        HEADERS += mhegic.h                 netstream.h
     435        SOURCES += mhegic.cpp               netstream.cpp
     436
    431437        # MHEG/MHI stuff
    432438        HEADERS += interactivetv.h          mhi.h
    433439        SOURCES += interactivetv.cpp        mhi.cpp
  • new file mythtv/libs/libmythtv/mhegic.cpp

    diff --git a/mythtv/libs/libmythtv/mhegic.cpp b/mythtv/libs/libmythtv/mhegic.cpp
    new file mode 100644
    index 0000000..4452c90
    - +  
     1/* MHEG Interaction Channel
     2 * Copyright 2011 Lawrence Rust <lvr at softsystem dot co dot uk>
     3 */
     4#include "mhegic.h"
     5
     6// C/C++ lib
     7#include <cstdlib>
     8using std::getenv;
     9
     10// Qt
     11#include <QByteArray>
     12#include <QMutexLocker>
     13#include <QNetworkRequest>
     14#include <QStringList>
     15#include <QScopedPointer>
     16#include <QApplication>
     17
     18// Myth
     19#include "netstream.h"
     20#include "mythlogging.h"
     21
     22#define LOC QString("[mhegic] ")
     23
     24
     25MHInteractionChannel::MHInteractionChannel(QObject* parent) : QObject(parent)
     26{
     27    setObjectName("MHInteractionChannel");
     28    moveToThread(&NAMThread::manager());
     29}
     30
     31// virtual
     32MHInteractionChannel::~MHInteractionChannel()
     33{
     34    QMutexLocker locker(&m_mutex);
     35    for ( map_t::iterator it = m_pending.begin(); it != m_pending.end(); ++it)
     36        (*it)->deleteLater();
     37    for ( map_t::iterator it = m_finished.begin(); it != m_finished.end(); ++it)
     38        (*it)->deleteLater();
     39}
     40
     41// Get network status
     42// static
     43MHInteractionChannel::EStatus MHInteractionChannel::status()
     44{
     45    if (!NetStream::isAvailable())
     46    {
     47        LOG(VB_MHEG, LOG_INFO, LOC + "WARN network is unavailable");
     48        return kInactive;
     49    }
     50
     51    // TODO get this from mythdb
     52    QStringList opts = QString(getenv("MYTHMHEG")).split(':');
     53    if (opts.contains("noice", Qt::CaseInsensitive))
     54        return kDisabled;
     55    else if (opts.contains("ice", Qt::CaseInsensitive))
     56        return kActive;
     57    else // Default
     58        return kActive;
     59}
     60
     61static inline bool isCached(const QString& csPath)
     62{
     63    return NetStream::GetLastModified(csPath).isValid();
     64}
     65
     66// Is a file ready to read?
     67bool MHInteractionChannel::CheckFile(const QString& csPath)
     68{
     69    QMutexLocker locker(&m_mutex);
     70
     71    // Is it complete?
     72    if (m_finished.contains(csPath))
     73        return true;
     74
     75    // Is it pending?
     76    if (m_pending.contains(csPath))
     77        return false; // It's pending so unavailable
     78
     79    // Is it in the cache?
     80    if (isCached(csPath))
     81        return true; // It's cached
     82
     83    // Queue a request
     84    LOG(VB_MHEG, LOG_DEBUG, LOC + QString("CheckFile queue %1").arg(csPath));
     85    QScopedPointer< NetStream > p(new NetStream(csPath));
     86    if (!p || !p->IsOpen())
     87    {
     88        LOG(VB_MHEG, LOG_WARNING, LOC + QString("CheckFile failed %1").arg(csPath) );
     89        return false;
     90    }
     91
     92    connect(p.data(), SIGNAL(Finished(QObject*)), this, SLOT(slotFinished(QObject*)) );
     93    m_pending.insert(csPath, p.take());
     94
     95    return false; // It's now pending so unavailable
     96}
     97
     98// Read a file. -1= error, 0= OK, 1= not ready
     99MHInteractionChannel::EResult
     100MHInteractionChannel::GetFile(const QString &csPath, QByteArray &data)
     101{
     102    QMutexLocker locker(&m_mutex);
     103
     104    // Is it pending?
     105    if (m_pending.contains(csPath))
     106        return kPending;
     107
     108    // Is it complete?
     109    QScopedPointer< NetStream > p(m_finished.take(csPath));
     110    if (p)
     111    {
     112        if (p->GetError() == QNetworkReply::NoError)
     113        {
     114            data = p->ReadAll();
     115            LOG(VB_MHEG, LOG_DEBUG, LOC + QString("GetFile finished %1").arg(csPath) );
     116            return kSuccess;
     117        }
     118
     119        LOG(VB_MHEG, LOG_WARNING, LOC + QString("GetFile failed %1").arg(csPath) );
     120        return kError;
     121    }
     122
     123    // Is it in the cache?
     124    if (isCached(csPath))
     125    {
     126        LOG(VB_MHEG, LOG_DEBUG, LOC + QString("GetFile cache read %1").arg(csPath) );
     127
     128        NetStream req(csPath, NetStream::kAlwaysCache);
     129        if (req.WaitTillFinished(3000) && req.GetError() == QNetworkReply::NoError)
     130        {
     131            data = req.ReadAll();
     132            LOG(VB_MHEG, LOG_DEBUG, LOC + QString("GetFile cache read %1 bytes %2")
     133                .arg(data.size()).arg(csPath) );
     134            return kSuccess;
     135        }
     136
     137        LOG(VB_MHEG, LOG_WARNING, LOC + QString("GetFile cache read failed %1").arg(csPath) );
     138        //return kError;
     139        // Retry
     140    }
     141
     142    // Queue a download
     143    LOG(VB_MHEG, LOG_DEBUG, LOC + QString("GetFile queue %1").arg(csPath) );
     144    p.reset(new NetStream(csPath));
     145    if (!p || !p->IsOpen())
     146    {
     147        LOG(VB_MHEG, LOG_WARNING, LOC + QString("GetFile failed %1").arg(csPath) );
     148        return kError;
     149    }
     150
     151    connect(p.data(), SIGNAL(Finished(QObject*)), this, SLOT(slotFinished(QObject*)) );
     152    m_pending.insert(csPath, p.take());
     153
     154    return kPending;
     155}
     156
     157// signal from NetStream
     158void MHInteractionChannel::slotFinished(QObject *obj)
     159{
     160    NetStream* p = dynamic_cast< NetStream* >(obj);
     161    if (!p)
     162        return;
     163
     164    QString url = p->Url().toString();
     165
     166    if (p->GetError() == QNetworkReply::NoError)
     167    {
     168        LOG(VB_MHEG, LOG_DEBUG, LOC + QString("Finished %1").arg(url) );
     169    }
     170    else
     171    {
     172        LOG(VB_MHEG, LOG_WARNING, LOC + QString("Finished %1").arg(p->GetErrorString()) );
     173    }
     174
     175    p->disconnect();
     176
     177    QMutexLocker locker(&m_mutex);
     178
     179    m_pending.remove(url);
     180    m_finished.insert(url, p);
     181}
     182
     183/* End of file */
  • new file mythtv/libs/libmythtv/mhegic.h

    diff --git a/mythtv/libs/libmythtv/mhegic.h b/mythtv/libs/libmythtv/mhegic.h
    new file mode 100644
    index 0000000..fcad95f
    - +  
     1/* MHEG Interaction Channel
     2 * Copyright 2011 Lawrence Rust <lvr at softsystem dot co dot uk>
     3 */
     4#ifndef MHEGIC_H
     5#define MHEGIC_H
     6
     7#include <QObject>
     8#include <QString>
     9#include <QMutex>
     10#include <QHash>
     11
     12class QByteArray;
     13class NetStream;
     14
     15class MHInteractionChannel : public QObject
     16{
     17    Q_OBJECT
     18    Q_DISABLE_COPY(MHInteractionChannel)
     19
     20public:
     21    MHInteractionChannel(QObject* parent = 0);
     22    virtual ~MHInteractionChannel();
     23
     24    // Properties
     25public:
     26    // Get network status
     27    enum EStatus { kActive = 0, kInactive, kDisabled };
     28    static EStatus status();
     29
     30    // Operations
     31public:
     32    // Is a file ready to read?
     33    bool CheckFile(const QString &url);
     34    // Read a file
     35    enum EResult { kError = -1, kSuccess = 0, kPending };
     36    EResult GetFile(const QString &url, QByteArray &data);
     37
     38    // Implementation
     39private slots:
     40    // NetStream signals
     41    void slotFinished(QObject*);
     42
     43private:
     44    mutable QMutex m_mutex;
     45    typedef QHash< QString, NetStream* > map_t;
     46    map_t m_pending; // Pending requests
     47    map_t m_finished; // Completed requests
     48};
     49
     50#endif /* ndef MHEGIC_H */
  • mythtv/libs/libmythtv/mhi.cpp

    diff --git a/mythtv/libs/libmythtv/mhi.cpp b/mythtv/libs/libmythtv/mhi.cpp
    index 1631495..ca8e9d0 100644
    a b  
     1#include "mhi.h"
     2
    13#include <unistd.h>
    24
    35#include <QRegion>
    46#include <QBitArray>
    57#include <QVector>
     8#include <QUrl>
    69
    7 #include "mhi.h"
    810#include "interactivescreen.h"
    911#include "mythpainter.h"
    1012#include "mythimage.h"
    void MHIContext::NetworkBootRequested(void) 
    358360// Called by the engine to check for the presence of an object in the carousel.
    359361bool MHIContext::CheckCarouselObject(QString objectPath)
    360362{
     363    if (objectPath.startsWith("http:") || objectPath.startsWith("https:"))
     364    {
     365        // TODO verify access to server in carousel file auth.servers
     366        // TODO use TLS cert from carousel auth.tls.<x>
     367        return m_ic.CheckFile(objectPath);
     368    }
     369
    361370    QStringList path = objectPath.split(QChar('/'), QString::SkipEmptyParts);
    362371    QByteArray result; // Unused
    363372    int res = m_dsmcc->GetDSMCCObject(path, result);
    bool MHIContext::CheckCarouselObject(QString objectPath) 
    367376// Called by the engine to request data from the carousel.
    368377bool MHIContext::GetCarouselData(QString objectPath, QByteArray &result)
    369378{
     379    bool const isIC = objectPath.startsWith("http:") || objectPath.startsWith("https:");
     380
    370381    // Get the path components.  The string will normally begin with "//"
    371382    // since this is an absolute path but that will be removed by split.
    372383    QStringList path = objectPath.split(QChar('/'), QString::SkipEmptyParts);
    bool MHIContext::GetCarouselData(QString objectPath, QByteArray &result) 
    375386    // the result.
    376387
    377388    QMutexLocker locker(&m_runLock);
     389    bool bReported = false;
    378390    QTime t; t.start();
    379391    while (!m_stop)
    380392    {
    381393        locker.unlock();
    382394
    383         int res = m_dsmcc->GetDSMCCObject(path, result);
    384         if (res == 0)
    385             return true; // Found it
    386         else if (res < 0)
    387             return false; // Not there.
    388         else if (t.elapsed() > 60000) // TODO get this from carousel info
    389             return false; // Not there.
     395        if (isIC)
     396        {
     397            // TODO verify access to server in carousel file auth.servers
     398            // TODO use TLS cert from carousel file auth.tls.<x>
     399            switch (m_ic.GetFile(objectPath, result))
     400            {
     401            case MHInteractionChannel::kSuccess:
     402                if (bReported)
     403                    LOG(VB_MHEG, LOG_INFO, QString("[mhi] Received %1").arg(objectPath));
     404                return true;
     405            case MHInteractionChannel::kError:
     406                if (bReported)
     407                    LOG(VB_MHEG, LOG_INFO, QString("[mhi] Not found %1").arg(objectPath));
     408                return false;
     409            case MHInteractionChannel::kPending:
     410                break;
     411            }
     412        }
     413        else
     414        {
     415            int res = m_dsmcc->GetDSMCCObject(path, result);
     416            if (res == 0)
     417            {
     418                if (bReported)
     419                    LOG(VB_MHEG, LOG_INFO, QString("[mhi] Received %1").arg(objectPath));
     420                return true; // Found it
     421            }
     422            // NB don't exit if -1 (not present) is returned as the object may
     423            // arrive later.  Exiting can cause the inital app to not be found
     424        }
     425
     426        if (t.elapsed() > 60000) // TODO get this from carousel info
     427             return false; // Not there.
    390428        // Otherwise we block.
    391         // Process DSMCC packets then block for a second or until we receive
     429        if (!bReported)
     430        {
     431            bReported = true;
     432            LOG(VB_MHEG, LOG_INFO, QString("[mhi] Waiting for %1").arg(objectPath));
     433        }
     434        // Process DSMCC packets then block for a while or until we receive
    392435        // some more packets.  We should eventually find out if this item is
    393436        // present.
    394437        ProcessDSMCCQueue();
    395438
    396439        locker.relock();
    397         if (!m_stop)
    398             m_engine_wait.wait(locker.mutex(), 1000);
     440        m_engine_wait.wait(locker.mutex(), 300);
    399441    }
    400442    return false; // Stop has been set.  Say the object isn't present.
    401443}
    402444
    403 // Called from tv_play when a key is pressed.
    404 // If it is one in the current profile we queue it for the engine
    405 // and return true otherwise we return false.
    406 bool MHIContext::OfferKey(QString key)
     445// Mapping from key name & UserInput register to UserInput EventData
     446class MHKeyLookup
    407447{
    408     int action = 0;
    409     QMutexLocker locker(&m_keyLock);
     448    typedef QPair< QString, int /*UserInput register*/ > key_t;
     449
     450public:
     451    MHKeyLookup();
     452
     453    int Find(const QString &name, int reg) const
     454        { return m_map.value(key_t(name,reg), 0); }
    410455
     456private:
     457    void key(const QString &name, int code, int r1,
     458        int r2=0, int r3=0, int r4=0, int r5=0, int r6=0, int r7=0, int r8=0, int r9=0);
     459
     460    QHash<key_t,int /*EventData*/ > m_map;
     461};
     462
     463void MHKeyLookup::key(const QString &name, int code, int r1,
     464    int r2, int r3, int r4, int r5, int r6, int r7, int r8, int r9)
     465{
     466    m_map.insert(key_t(name,r1), code);
     467    if (r2 > 0)
     468        m_map.insert(key_t(name,r2), code);
     469    if (r3 > 0)
     470        m_map.insert(key_t(name,r3), code);
     471    if (r4 > 0)
     472        m_map.insert(key_t(name,r4), code);
     473    if (r5 > 0)
     474        m_map.insert(key_t(name,r5), code);
     475    if (r6 > 0)
     476        m_map.insert(key_t(name,r6), code);
     477    if (r7 > 0)
     478        m_map.insert(key_t(name,r7), code);
     479    if (r8 > 0)
     480        m_map.insert(key_t(name,r8), code);
     481    if (r9 > 0)
     482        m_map.insert(key_t(name,r9), code);
     483}
     484
     485MHKeyLookup::MHKeyLookup()
     486{
    411487    // This supports the UK and NZ key profile registers.
    412488    // The UK uses 3, 4 and 5 and NZ 13, 14 and 15.  These are
    413489    // similar but the NZ profile also provides an EPG key.
     490    // ETSI ES 202 184 V2.2.1 (2011-03) adds group 6 for ICE.
     491    // The BBC use group 7 for ICE
     492    key(ACTION_UP,           1, 4,5,6,7,14,15);
     493    key(ACTION_DOWN,         2, 4,5,6,7,14,15);
     494    key(ACTION_LEFT,         3, 4,5,6,7,14,15);
     495    key(ACTION_RIGHT,        4, 4,5,6,7,14,15);
     496    key(ACTION_0,            5, 4,6,7,14);
     497    key(ACTION_1,            6, 4,6,7,14);
     498    key(ACTION_2,            7, 4,6,7,14);
     499    key(ACTION_3,            8, 4,6,7,14);
     500    key(ACTION_4,            9, 4,6,7,14);
     501    key(ACTION_5,           10, 4,6,7,14);
     502    key(ACTION_6,           11, 4,6,7,14);
     503    key(ACTION_7,           12, 4,6,7,14);
     504    key(ACTION_8,           13, 4,6,7,14);
     505    key(ACTION_9,           14, 4,6,7,14);
     506    key(ACTION_SELECT,      15, 4,5,6,7,14,15);
     507    key(ACTION_TEXTEXIT,    16, 3,4,5,6,7,13,14,15); // 16= Cancel
     508    // 17= help
     509    // 18..99 reserved by DAVIC
     510    key(ACTION_MENURED,    100, 3,4,5,6,7,13,14,15);
     511    key(ACTION_MENUGREEN,  101, 3,4,5,6,7,13,14,15);
     512    key(ACTION_MENUYELLOW, 102, 3,4,5,6,7,13,14,15);
     513    key(ACTION_MENUBLUE,   103, 3,4,5,6,7,13,14,15);
     514    key(ACTION_MENUTEXT,   104, 3,4,5,6,7);
     515    key(ACTION_MENUTEXT,   105, 13,14,15); // NB from original Myth code
     516    // 105..119 reserved for future spec
     517    key(ACTION_STOP,       120, 6,7);
     518    key(ACTION_PLAY,       121, 6,7);
     519    key(ACTION_PAUSE,      122, 6,7);
     520    key(ACTION_JUMPFFWD,   123, 6,7); // 123= Skip Forward
     521    key(ACTION_JUMPRWND,   124, 6,7); // 124= Skip Back
     522#if 0 // These conflict with left & right
     523    key(ACTION_SEEKFFWD,   125, 6,7); // 125= Fast Forward
     524    key(ACTION_SEEKRWND,   126, 6,7); // 126= Rewind
     525#endif
     526    key(ACTION_PLAYBACK,   127, 6,7);
     527    // 128..256 reserved for future spec
     528    // 257..299 vendor specific
     529    key(ACTION_MENUEPG,    300, 13,14,15);
     530    // 301.. Vendor specific
     531}
    414532
    415     if (key == ACTION_UP)
    416     {
    417         if (m_keyProfile == 4 || m_keyProfile == 5 ||
    418             m_keyProfile == 14 || m_keyProfile == 15)
    419             action = 1;
    420     }
    421     else if (key == ACTION_DOWN)
    422     {
    423         if (m_keyProfile == 4 || m_keyProfile == 5 ||
    424             m_keyProfile == 14 || m_keyProfile == 15)
    425             action = 2;
    426     }
    427     else if (key == ACTION_LEFT)
    428     {
    429         if (m_keyProfile == 4 || m_keyProfile == 5 ||
    430             m_keyProfile == 14 || m_keyProfile == 15)
    431             action = 3;
    432     }
    433     else if (key == ACTION_RIGHT)
    434     {
    435         if (m_keyProfile == 4 || m_keyProfile == 5 ||
    436             m_keyProfile == 14 || m_keyProfile == 15)
    437             action = 4;
    438     }
    439     else if (key == ACTION_0 || key == ACTION_1 || key == ACTION_2 ||
    440              key == ACTION_3 || key == ACTION_4 || key == ACTION_5 ||
    441              key == ACTION_6 || key == ACTION_7 || key == ACTION_8 ||
    442              key == ACTION_9)
    443     {
    444         if (m_keyProfile == 4 || m_keyProfile == 14)
    445             action = key.toInt() + 5;
    446     }
    447     else if (key == ACTION_SELECT)
    448     {
    449         if (m_keyProfile == 4 || m_keyProfile == 5 ||
    450             m_keyProfile == 14 || m_keyProfile == 15)
    451             action = 15;
    452     }
    453     else if (key == ACTION_TEXTEXIT)
    454         action = 16;
    455     else if (key == ACTION_MENURED)
    456         action = 100;
    457     else if (key == ACTION_MENUGREEN)
    458         action = 101;
    459     else if (key == ACTION_MENUYELLOW)
    460         action = 102;
    461     else if (key == ACTION_MENUBLUE)
    462         action = 103;
    463     else if (key == ACTION_MENUTEXT)
    464         action = m_keyProfile > 12 ? 105 : 104;
    465     else if (key == ACTION_MENUEPG)
    466         action = m_keyProfile > 12 ? 300 : 0;
    467 
    468     if (action != 0)
    469     {
    470         m_keyQueue.enqueue(action);
    471         LOG(VB_GENERAL, LOG_INFO, QString("Adding MHEG key %1:%2:%3").arg(key)
    472                 .arg(action).arg(m_keyQueue.size()));
    473         locker.unlock();
    474         QMutexLocker locker2(&m_runLock);
    475         m_engine_wait.wakeAll();
    476         return true;
    477     }
    478 
    479     return false;
     533// Called from tv_play when a key is pressed.
     534// If it is one in the current profile we queue it for the engine
     535// and return true otherwise we return false.
     536bool MHIContext::OfferKey(QString key)
     537{
     538    static const MHKeyLookup s_keymap;
     539    int action = s_keymap.Find(key, m_keyProfile);
     540    if (action == 0)
     541        return false;
     542 
     543    LOG(VB_GENERAL, LOG_INFO, QString("[mhi] Adding MHEG key %1:%2:%3")
     544        .arg(key).arg(action).arg(m_keyQueue.size()) );
     545    { QMutexLocker locker(&m_keyLock);
     546    m_keyQueue.enqueue(action);}
     547    QMutexLocker locker2(&m_runLock);
     548    m_engine_wait.wakeAll();
     549    return true;
    480550}
    481551
    482552void MHIContext::Reinit(const QRect &display)
    void MHIContext::Reinit(const QRect &display) 
    491561
    492562void MHIContext::SetInputRegister(int num)
    493563{
     564    LOG(VB_MHEG, LOG_INFO, QString("[mhi] SetInputRegister %1").arg(num));
    494565    QMutexLocker locker(&m_keyLock);
    495566    m_keyQueue.clear();
    496567    m_keyProfile = num;
    497568}
    498569
     570int MHIContext::GetICStatus()
     571{
     572   // 0= Active, 1= Inactive, 2= Disabled
     573    return m_ic.status();
     574}
    499575
    500576// Called by the video player to redraw the image.
    501577void MHIContext::UpdateOSD(InteractiveScreen *osdWindow,
    int MHIContext::GetChannelIndex(const QString &str) 
    700776            nResult = query.value(0).toInt();
    701777    }
    702778    else if (str == "rec://svc/cur")
    703         nResult = m_currentStream;
     779        nResult = m_currentStream > 0 ? m_currentStream : m_currentChannel;
    704780    else if (str == "rec://svc/def")
    705781        nResult = m_currentChannel;
    706782    else if (str.startsWith("rec://"))
    int MHIContext::GetChannelIndex(const QString &str) 
    716792bool MHIContext::GetServiceInfo(int channelId, int &netId, int &origNetId,
    717793                                int &transportId, int &serviceId)
    718794{
    719     LOG(VB_MHEG, LOG_INFO, QString("[mhi] GetServiceInfo %1").arg(channelId));
    720795    MSqlQuery query(MSqlQuery::InitCon());
    721796    query.prepare("SELECT networkid, transportid, serviceid "
    722797                  "FROM channel, dtv_multiplex "
    bool MHIContext::GetServiceInfo(int channelId, int &netId, int &origNetId, 
    729804        origNetId = netId; // We don't have this in the database.
    730805        transportId = query.value(1).toInt();
    731806        serviceId = query.value(2).toInt();
     807        LOG(VB_MHEG, LOG_INFO, QString("[mhi] GetServiceInfo %1 => NID=%2 TID=%3 SID=%4")
     808            .arg(channelId).arg(netId).arg(transportId).arg(serviceId));
    732809        return true;
    733810    }
    734     else return false;
     811
     812    LOG(VB_MHEG, LOG_WARNING, QString("[mhi] GetServiceInfo %1 failed").arg(channelId));
     813    return false;
    735814}
    736815
    737816bool MHIContext::TuneTo(int channel, int tuneinfo)
    738817{
    739     LOG(VB_MHEG, LOG_INFO, QString("[mhi] TuneTo %1 0x%2")
    740         .arg(channel).arg(tuneinfo,0,16));
    741 
    742818    if (!m_isLive)
     819    {
     820        LOG(VB_MHEG, LOG_WARNING, QString("[mhi] Can't TuneTo %1 0x%2 while not live")
     821            .arg(channel).arg(tuneinfo,0,16));
    743822        return false; // Can't tune if this is a recording.
     823    }
    744824
     825    LOG(VB_GENERAL, LOG_INFO, QString("[mhi] TuneTo %1 0x%2")
     826        .arg(channel).arg(tuneinfo,0,16));
    745827    m_tuneinfo.append(tuneinfo);
    746828
    747829    // Post an event requesting a channel change.
    bool MHIContext::TuneTo(int channel, int tuneinfo) 
    754836    return true;
    755837}
    756838
    757 // Begin playing audio from the specified stream
    758 bool MHIContext::BeginAudio(const QString &stream, int tag)
     839
     840// Begin playing the specified stream
     841bool MHIContext::BeginStream(const QString &stream, MHStream *notify)
    759842{
    760     LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginAudio %1 %2").arg(stream).arg(tag));
     843    LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginStream %1 0x%2")
     844        .arg(stream).arg((quintptr)notify,0,16));
     845
     846    m_audioTag = -1;
     847    m_videoTag = -1;
     848    m_notify = notify;
     849
     850    if (stream.startsWith("http://") || stream.startsWith("https://"))
     851    {
     852        m_currentStream = -1;
     853
     854        // The url is sometimes only http:// during stream startup
     855        if (QUrl(stream).authority().isEmpty())
     856            return false;
     857
     858        return m_parent->GetNVP()->SetStream(stream);
     859    }
    761860
    762861    int chan = GetChannelIndex(stream);
    763862    if (chan < 0)
    764863        return false;
    765 
     864    if (VERBOSE_LEVEL_CHECK(VB_MHEG, LOG_ANY))
     865    {
     866        int netId, origNetId, transportId, serviceId;
     867        GetServiceInfo(chan, netId, origNetId, transportId, serviceId);
     868    }
     869 
    766870    if (chan != m_currentStream)
    767871    {
    768         // We have to tune to the channel where the audio is to be found.
     872        // We have to tune to the channel where the stream is to be found.
    769873        // Because the audio and video are both components of an MHEG stream
    770874        // they will both be on the same channel.
    771875        m_currentStream = chan;
    772         m_audioTag = tag;
    773876        return TuneTo(chan, kTuneKeepChnl|kTuneQuietly|kTuneKeepApp);
    774877    }
     878 
     879    return true;
     880}
    775881
    776     if (tag < 0)
    777         return true; // Leave it at the default.
    778     else if (m_parent->GetNVP())
    779         return m_parent->GetNVP()->SetAudioByComponentTag(tag);
    780     else
     882void MHIContext::EndStream()
     883{
     884    LOG(VB_MHEG, LOG_INFO, QString("[mhi] EndStream 0x%1")
     885        .arg((quintptr)m_notify,0,16) );
     886
     887    m_notify = 0;
     888    (void)m_parent->GetNVP()->SetStream(QString());
     889}
     890
     891// Callback from MythPlayer when a stream starts or stops
     892bool MHIContext::StreamStarted(bool bStarted)
     893{
     894    if (!m_engine || !m_notify)
    781895        return false;
     896
     897    LOG(VB_MHEG, LOG_INFO, QString("[mhi] Stream 0x%1 %2")
     898        .arg((quintptr)m_notify,0,16).arg(bStarted ? "started" : "stopped"));
     899
     900    m_engine->StreamStarted(m_notify, bStarted);
     901    if (!bStarted)
     902        m_notify = 0;
     903    return m_currentStream == -1; // Return true if it's an http stream
    782904}
    783905
     906// Begin playing audio
     907bool MHIContext::BeginAudio(int tag)
     908{
     909    LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginAudio %1").arg(tag));
     910
     911    if (tag < 0)
     912        return true; // Leave it at the default.
     913
     914    m_audioTag = tag;
     915    if (m_parent->GetNVP())
     916        return m_parent->GetNVP()->SetAudioByComponentTag(tag);
     917    return false;
     918 }
     919 
    784920// Stop playing audio
    785 void MHIContext::StopAudio(void)
     921void MHIContext::StopAudio()
    786922{
    787923    // Do nothing at the moment.
    788924}
    789 
     925 
    790926// Begin displaying video from the specified stream
    791 bool MHIContext::BeginVideo(const QString &stream, int tag)
     927bool MHIContext::BeginVideo(int tag)
    792928{
    793     LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginVideo %1 %2").arg(stream).arg(tag));
     929    LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginVideo %1").arg(tag));
    794930
    795     int chan = GetChannelIndex(stream);
    796     if (chan < 0)
    797         return false;
    798     if (chan != m_currentStream)
    799     {
    800         // We have to tune to the channel where the video is to be found.
    801         m_currentStream = chan;
    802         m_videoTag = tag;
    803         return TuneTo(chan, kTuneKeepChnl|kTuneQuietly|kTuneKeepApp);
    804     }
    805931    if (tag < 0)
    806932        return true; // Leave it at the default.
    807     else if (m_parent->GetNVP())
     933 
     934    m_videoTag = tag;
     935    if (m_parent->GetNVP())
    808936        return m_parent->GetNVP()->SetVideoByComponentTag(tag);
    809 
    810937    return false;
    811938}
    812 
    813 // Stop displaying video
    814 void MHIContext::StopVideo(void)
     939 
     940 // Stop displaying video
     941void MHIContext::StopVideo()
    815942{
    816943    // Do nothing at the moment.
    817944}
     945 
     946// Get current stream position, -1 if unknown
     947long MHIContext::GetStreamPos()
     948{
     949    return m_parent->GetNVP() ? m_parent->GetNVP()->GetStreamPos() : -1;
     950}
     951
     952// Get current stream size, -1 if unknown
     953long MHIContext::GetStreamMaxPos()
     954{
     955    return m_parent->GetNVP() ? m_parent->GetNVP()->GetStreamMaxPos() : -1;
     956}
     957
     958// Set current stream position
     959long MHIContext::SetStreamPos(long pos)
     960{
     961    return m_parent->GetNVP() ? m_parent->GetNVP()->SetStreamPos(pos) : -1;
     962}
     963
     964// Play or pause a stream
     965void MHIContext::StreamPlay(bool play)
     966{
     967    if (m_parent->GetNVP())
     968        m_parent->GetNVP()->StreamPlay(play);
     969}
    818970
    819971// Create a new object to draw dynamic line art.
    820972MHDLADisplay *MHIContext::CreateDynamicLineArt(
    void MHIContext::DrawRect(int xPos, int yPos, int width, int height, 
    8661018// and usually that will be the same as the origin of the bounding
    8671019// box (clipRect).
    8681020void MHIContext::DrawImage(int x, int y, const QRect &clipRect,
    869                            const QImage &qImage)
     1021                           const QImage &qImage, bool bScaled /* = false */)
    8701022{
    8711023    if (qImage.isNull())
    8721024        return;
    8731025
    8741026    QRect imageRect(x, y, qImage.width(), qImage.height());
    875     QRect displayRect = QRect(clipRect.x(), clipRect.y(),
    876                               clipRect.width(), clipRect.height()) & imageRect;
     1027    QRect displayRect = clipRect & imageRect;
    8771028
    878     if (displayRect == imageRect) // No clipping required
     1029    if (bScaled || displayRect == imageRect) // No clipping required
    8791030    {
    8801031        QImage q_scaled =
    8811032            qImage.scaled(
    void MHIContext::DrawImage(int x, int y, const QRect &clipRect, 
    8891040    else if (!displayRect.isEmpty())
    8901041    { // We must clip the image.
    8911042        QImage clipped = qImage.convertToFormat(QImage::Format_ARGB32)
    892             .copy(displayRect.x() - x, displayRect.y() - y,
    893                   displayRect.width(), displayRect.height());
     1043            .copy(displayRect.translated(-x, -y));
    8941044        QImage q_scaled =
    8951045            clipped.scaled(
    8961046                SCALED_X(displayRect.width()),
    void MHIBitmap::Draw(int x, int y, QRect rect, bool tiled) 
    14701620                tiledImage.setPixel(i, j, m_image.pixel(i % m_image.width(), j % m_image.height()));
    14711621            }
    14721622        }
    1473         m_parent->DrawImage(rect.x(), rect.y(), rect, tiledImage);
     1623        m_parent->DrawImage(rect.x(), rect.y(), rect, tiledImage, true);
    14741624    }
    14751625    else
    14761626    {
    1477         m_parent->DrawImage(x, y, rect, m_image);
     1627        // NB THe BBC expects bitmaps to be scaled, not clipped
     1628        m_parent->DrawImage(x, y, rect, m_image, true);
    14781629    }
    14791630}
    14801631
    void MHIBitmap::CreateFromPNG(const unsigned char *data, int length) 
    14931644    m_opaque = ! m_image.hasAlphaChannel();
    14941645}
    14951646
     1647// Create a bitmap from JPEG.
     1648//virtual
     1649void MHIBitmap::CreateFromJPEG(const unsigned char *data, int length)
     1650{
     1651    m_image = QImage();
     1652
     1653    if (!m_image.loadFromData(data, length, "JPG"))
     1654    {
     1655        m_image = QImage();
     1656        return;
     1657    }
     1658
     1659    // Assume that if it has an alpha buffer then it's partly transparent.
     1660    m_opaque = ! m_image.hasAlphaChannel();
     1661}
     1662
    14961663// Convert an MPEG I-frame into a bitmap.  This is used as the way of
    14971664// sending still pictures.  We convert the image to a QImage even
    14981665// though that actually means converting it from YUV and eventually
  • mythtv/libs/libmythtv/mhi.h

    diff --git a/mythtv/libs/libmythtv/mhi.h b/mythtv/libs/libmythtv/mhi.h
    index 2b10c8b..17dea0e 100644
    a b using namespace std; 
    2121#include "../libmythfreemheg/freemheg.h"
    2222#include "interactivetv.h"
    2323#include "dsmcc.h"
     24#include "mhegic.h"
    2425#include "mythcontext.h"
    2526#include "mythdbcon.h"
    2627#include "mythdeque.h"
    class MHIContext : public MHContext, public QRunnable 
    99100    virtual void DrawBackground(const QRegion &reg);
    100101    virtual void DrawVideo(const QRect &videoRect, const QRect &displayRect);
    101102
    102     void DrawImage(int x, int y, const QRect &rect, const QImage &image);
     103    void DrawImage(int x, int y, const QRect &rect, const QImage &image, bool bScaled = false);
    103104
    104105    virtual int GetChannelIndex(const QString &str);
    105106    /// Get netId etc from the channel index.
    class MHIContext : public MHContext, public QRunnable 
    107108                                int &transportId, int &serviceId);
    108109    virtual bool TuneTo(int channel, int tuneinfo);
    109110
    110     /// Begin playing audio from the specified stream
    111     virtual bool BeginAudio(const QString &stream, int tag);
     111    /// Begin playing the specified stream
     112    virtual bool BeginStream(const QString &str, MHStream* notify);
     113    virtual void EndStream();
     114    // Called when the stream starts or stops
     115    bool StreamStarted(bool bStarted = true);
     116    /// Begin playing audio
     117    virtual bool BeginAudio(int tag);
    112118    /// Stop playing audio
    113     virtual void StopAudio(void);
    114     /// Begin displaying video from the specified stream
    115     virtual bool BeginVideo(const QString &stream, int tag);
     119    virtual void StopAudio();
     120    /// Begin displaying video
     121    virtual bool BeginVideo(int tag);
    116122    /// Stop displaying video
    117     virtual void StopVideo(void);
     123    virtual void StopVideo();
     124    // Get current stream position, -1 if unknown
     125    virtual long GetStreamPos();
     126    // Get current stream size, -1 if unknown
     127    virtual long GetStreamMaxPos();
     128    // Set current stream position
     129    virtual long SetStreamPos(long);
     130    // Play or pause a stream
     131    virtual void StreamPlay(bool);
    118132
    119133    // Get the context id strings.  The format of these strings is specified
    120134    // by the UK MHEG profile.
    class MHIContext : public MHContext, public QRunnable 
    123137    virtual const char *GetDSMCCId(void)
    124138        { return "DSMMYT001"; } // DSMCC version.
    125139
     140    // InteractionChannel
     141    virtual int GetICStatus(); // 0= Active, 1= Inactive, 2= Disabled
     142
    126143    // Operations used by the display classes
    127144    // Add an item to the display vector
    128145    void AddToDisplay(const QImage &image, int x, int y);
    class MHIContext : public MHContext, public QRunnable 
    150167    QMutex           m_dsmccLock;
    151168    MythDeque<DSMCCPacket*> m_dsmccQueue;
    152169
     170    MHInteractionChannel m_ic;  // Interaction channel
     171    MHStream        *m_notify;
     172
    153173    QMutex           m_keyLock;
    154174    MythDeque<int>   m_keyQueue;
    155175    int              m_keyProfile;
    class MHIBitmap : public MHBitmapDisplay 
    231251    /// Create bitmap from single I frame MPEG
    232252    virtual void CreateFromMPEG(const unsigned char *data, int length);
    233253
     254    /// Create bitmap from JPEG
     255    virtual void CreateFromJPEG(const unsigned char *data, int length);
     256
    234257    /** \fn MHIBitmap::Draw(int,int,QRect,bool)
    235258     *  \brief Draw the completed drawing onto the display.
    236259     *
  • mythtv/libs/libmythtv/mythplayer.cpp

    diff --git a/mythtv/libs/libmythtv/mythplayer.cpp b/mythtv/libs/libmythtv/mythplayer.cpp
    index 546c1ec..e9e9274 100644
    a b using namespace std; 
    2525#include <QCoreApplication>
    2626#include <QKeyEvent>
    2727#include <QDir>
     28#include <QScopedPointer>
    2829
    2930// MythTV headers
    3031#include "mthread.h"
    using namespace std; 
    5859#include "mythimage.h"
    5960#include "mythuiimage.h"
    6061#include "mythlogging.h"
     62#include "icringbuffer.h"
    6163
    6264extern "C" {
    6365#include "vbitext/vbi.h"
    int MythPlayer::OpenFile(uint retries) 
    899901        MythTimer peekTimer; peekTimer.start();
    900902        while (player_ctx->buffer->Peek(testbuf, testreadsize) != testreadsize)
    901903        {
    902             if (peekTimer.elapsed() > 1000 || bigTimer.elapsed() > timeout)
     904            // NB need to allow for streams encountering network congestion
     905            if (peekTimer.elapsed() > 30000 || bigTimer.elapsed() > timeout)
    903906            {
    904907                LOG(VB_GENERAL, LOG_ERR, LOC +
    905908                    QString("OpenFile(): Could not read first %1 bytes of '%2'")
    int MythPlayer::OpenFile(uint retries) 
    909912                return -1;
    910913            }
    911914            LOG(VB_GENERAL, LOG_WARNING, LOC + "OpenFile() waiting on data");
    912             usleep(50 * 1000);
     915            usleep(150 * 1000);
    913916        }
    914917
    915918        player_ctx->LockPlayingInfo(__FILE__, __LINE__);
    void MythPlayer::DisplayPauseFrame(void) 
    19982001    SetBuffering(false);
    19992002
    20002003    RefreshPauseFrame();
     2004    PreProcessNormalFrame(); // Allow interactiveTV to draw on pause frame
    20012005
    20022006    osdLock.lock();
    20032007    videofiltersLock.lock();
    bool MythPlayer::PrebufferEnoughFrames(int min_buffers) 
    20622066            // to recover from serious problems if frames get leaked.
    20632067            DiscardVideoFrames(true);
    20642068        }
    2065         if (waited_for > 20000) // 20 seconds
     2069        if (waited_for > 30000) // 30 seconds for internet streamed media
    20662070        {
    20672071            LOG(VB_GENERAL, LOG_ERR, LOC +
    20682072                "Waited too long for decoder to fill video buffers. Exiting..");
    void MythPlayer::SwitchToProgram(void) 
    24152419        return;
    24162420    }
    24172421
     2422    if (player_ctx->buffer->GetType() == ICRingBuffer::kRingBufferType)
     2423    {
     2424        // Restore original ringbuffer
     2425        ICRingBuffer *ic = dynamic_cast< ICRingBuffer* >(player_ctx->buffer);
     2426        player_ctx->buffer = ic->Take();
     2427        delete ic;
     2428    }
     2429
    24182430    player_ctx->buffer->OpenFile(
    24192431        pginfo->GetPlaybackURL(), RingBuffer::kLiveTVOpenTimeout);
    24202432
    void MythPlayer::JumpToProgram(void) 
    25222534
    25232535    SendMythSystemPlayEvent("PLAY_CHANGED", pginfo);
    25242536
     2537    if (player_ctx->buffer->GetType() == ICRingBuffer::kRingBufferType)
     2538    {
     2539        // Restore original ringbuffer
     2540        ICRingBuffer *ic = dynamic_cast< ICRingBuffer* >(player_ctx->buffer);
     2541        player_ctx->buffer = ic->Take();
     2542        delete ic;
     2543    }
     2544
    25252545    player_ctx->buffer->OpenFile(
    25262546        pginfo->GetPlaybackURL(), RingBuffer::kLiveTVOpenTimeout);
    25272547
    void MythPlayer::EventLoop(void) 
    27142734        JumpToProgram();
    27152735    }
    27162736
     2737    // Change interactive stream if requested
     2738    { QMutexLocker locker(&itvLock);
     2739    if (!m_newStream.isEmpty())
     2740    {
     2741        QString stream = m_newStream;
     2742        m_newStream.clear();
     2743        locker.unlock();
     2744        JumpToStream(stream);
     2745    }}
     2746
    27172747    // Disable fastforward if we are too close to the end of the buffer
    27182748    if (ffrew_skip > 1 && (CalcMaxFFTime(100, false) < 100))
    27192749    {
    void MythPlayer::EventLoop(void) 
    27502780    }
    27512781
    27522782    // Handle end of file
    2753     if (GetEof())
     2783    if (GetEof() && !allpaused)
    27542784    {
    2755         if (player_ctx->tvchain)
     2785        if (interactiveTV && interactiveTV->StreamStarted(false))
    27562786        {
    2757             if (!allpaused && player_ctx->tvchain->HasNext())
    2758             {
    2759                 LOG(VB_GENERAL, LOG_NOTICE, LOC + "LiveTV forcing JumpTo 1");
    2760                 player_ctx->tvchain->JumpToNext(true, 1);
    2761                 return;
    2762             }
     2787            Pause();
     2788            return;
    27632789        }
    2764         else if (!allpaused)
     2790
     2791        if (player_ctx->tvchain && player_ctx->tvchain->HasNext())
    27652792        {
    2766             SetPlaying(false);
     2793            LOG(VB_GENERAL, LOG_NOTICE, LOC + "LiveTV forcing JumpTo 1");
     2794            player_ctx->tvchain->JumpToNext(true, 1);
    27672795            return;
    27682796        }
     2797
     2798        SetPlaying(false);
     2799        return;
    27692800    }
    27702801
    27712802    // Handle rewind
    void MythPlayer::UnpauseDecoder(void) 
    28962927
    28972928    int tries = 0;
    28982929    unpauseDecoder = true;
    2899     while (decoderThread && !killdecoder && (tries++ < 100) &&
     2930    while (decoderPaused && decoderThread && !killdecoder && (tries++ < 10) &&
    29002931          !decoderThreadUnpause.wait(&decoderPauseLock, 100))
    29012932    {
    29022933        LOG(VB_GENERAL, LOG_WARNING, LOC +
    bool MythPlayer::SetVideoByComponentTag(int tag) 
    47844815    return false;
    47854816}
    47864817
     4818// Called from MHIContext::Begin/End/Stream on the MHIContext::StartMHEGEngine thread
     4819bool MythPlayer::SetStream(const QString &stream)
     4820{
     4821    // The stream name is empty if the stream is closing
     4822    LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("SetStream '%1'").arg(stream));
     4823
     4824    QMutexLocker locker(&itvLock);
     4825    m_newStream = stream;
     4826    m_newStream.detach();
     4827    // Stream will be changed by JumpToStream called from EventLoop
     4828    // If successful will call interactiveTV->StreamStarted();
     4829    return !stream.isEmpty();
     4830}
     4831
     4832// Called from EventLoop pn the main application thread
     4833void MythPlayer::JumpToStream(const QString &stream)
     4834{
     4835    LOG(VB_PLAYBACK, LOG_INFO, LOC + "JumpToStream - begin");
     4836
     4837    if (stream.isEmpty())
     4838        return; // Shouldn't happen
     4839
     4840    Pause();
     4841    ResetCaptions();
     4842
     4843    if (player_ctx->buffer->GetType() != ICRingBuffer::kRingBufferType)
     4844        player_ctx->buffer = new ICRingBuffer(stream, player_ctx->buffer);
     4845    else
     4846        player_ctx->buffer->OpenFile(stream);
     4847
     4848    if (!player_ctx->buffer->IsOpen())
     4849    {
     4850        LOG(VB_GENERAL, LOG_ERR, LOC + "JumpToStream buffer OpenFile failed");
     4851        SetEof(true);
     4852        SetErrored(QObject::tr("Error opening remote stream buffer"));
     4853        return;
     4854    }
     4855
     4856    if (OpenFile(120) < 0) // 120 retries ~= 60 seconds
     4857    {
     4858        LOG(VB_GENERAL, LOG_ERR, LOC + "JumpToStream OpenFile failed.");
     4859        SetEof(true);
     4860        SetErrored(QObject::tr("Error opening remote stream"));
     4861        return;
     4862    }
     4863
     4864    SetEof(false);
     4865
     4866    if (player_ctx->tvchain) CheckTVChain();
     4867    //player_ctx->buffer->IgnoreLiveEOF(false);
     4868
     4869    Play();
     4870    ChangeSpeed();
     4871
     4872    player_ctx->SetPlayerChangingBuffers(false);
     4873    if (interactiveTV) interactiveTV->StreamStarted();
     4874
     4875    LOG(VB_PLAYBACK, LOG_INFO, LOC + "JumpToStream - end");
     4876}
     4877
     4878static inline int SafeFPS(DecoderBase *decoder)
     4879{
     4880    if (!decoder)
     4881        return 25;
     4882    double fps = decoder->GetFPS();
     4883    return fps > 0 ? (int)(fps + 0.5) : 25;
     4884}
     4885
     4886long MythPlayer::GetStreamPos()
     4887{
     4888    return (1000L * GetFramesPlayed()) / SafeFPS(decoder);
     4889}
     4890
     4891long MythPlayer::GetStreamMaxPos()
     4892{
     4893    uint kbps = decoder ? decoder->GetRawBitrate() : 0;
     4894    long pos = GetStreamPos(), maxpos = 0;
     4895    long long len = player_ctx->buffer->GetRealFileSize();
     4896    if (len > 0)
     4897    {
     4898        const uint kMin = 64;
     4899        maxpos = (long)((8 * len) / (kbps < kMin ? kMin : kbps));
     4900    }
     4901
     4902    if (maxpos < pos)
     4903        maxpos = pos;
     4904    LOG(VB_PLAYBACK, LOG_INFO, LOC +
     4905        QString("GetStreamMaxPos => %1 mS (%2 KB @ %3 KBPS)")
     4906        .arg(maxpos).arg(len > 0 ? len/1024 : len).arg((kbps+4)/8) );
     4907    return maxpos;
     4908}
     4909
     4910long MythPlayer::SetStreamPos(long ms)
     4911{
     4912    LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("SetStreamPos %1").arg(ms));
     4913    return JumpToFrame((uint64_t)((ms * SafeFPS(decoder)) / 1000));
     4914}
     4915
     4916void MythPlayer::StreamPlay(bool play)
     4917{
     4918    if (play)
     4919        Play();
     4920    else
     4921        Pause();
     4922}
     4923
    47874924/** \fn MythPlayer::SetDecoder(DecoderBase*)
    47884925 *  \brief Sets the stream decoder, deleting any existing recorder.
    47894926 */
  • mythtv/libs/libmythtv/mythplayer.h

    diff --git a/mythtv/libs/libmythtv/mythplayer.h b/mythtv/libs/libmythtv/mythplayer.h
    index e30c3a7..234818f 100644
    a b class MTV_PUBLIC MythPlayer 
    288288    // Public MHEG/MHI stream selection
    289289    bool SetAudioByComponentTag(int tag);
    290290    bool SetVideoByComponentTag(int tag);
     291    bool SetStream(const QString &);
     292    long GetStreamPos(); // mS
     293    long GetStreamMaxPos(); // mS
     294    long SetStreamPos(long); // mS
     295    void StreamPlay(bool play = true);
    291296
    292297    // LiveTV public stuff
    293298    void CheckTVChain();
    class MTV_PUBLIC MythPlayer 
    560565    // Private LiveTV stuff
    561566    void  SwitchToProgram(void);
    562567    void  JumpToProgram(void);
     568    void  JumpToStream(const QString&);
    563569
    564570  protected:
    565571    PlayerFlags    playerFlags;
    class MTV_PUBLIC MythPlayer 
    695701    InteractiveTV *interactiveTV;
    696702    bool       itvEnabled;
    697703    QMutex     itvLock;
     704    QString    m_newStream; // Guarded by itvLock
    698705
    699706    // OSD stuff
    700707    OSD  *osd;
  • new file mythtv/libs/libmythtv/netstream.cpp

    diff --git a/mythtv/libs/libmythtv/netstream.cpp b/mythtv/libs/libmythtv/netstream.cpp
    new file mode 100644
    index 0000000..6bef91a
    - +  
     1/* Network stream
     2 * Copyright 2011 Lawrence Rust <lvr at softsystem dot co dot uk>
     3 */
     4#include "netstream.h"
     5
     6// C/C++ lib
     7#include <cstdlib>
     8using std::getenv;
     9#include <cstddef>
     10
     11// Qt
     12#include <QNetworkAccessManager>
     13#include <QNetworkRequest>
     14#include <QNetworkReply>
     15#include <QNetworkProxy>
     16#include <QNetworkDiskCache>
     17#include <QSslConfiguration>
     18#include <QSslError>
     19#include <QSslSocket>
     20#include <QUrl>
     21#include <QThread>
     22#include <QMutexLocker>
     23#include <QEvent>
     24#include <QCoreApplication>
     25#include <QAtomicInt>
     26#include <QMetaType> // qRegisterMetaType
     27#include <QDesktopServices>
     28#include <QScopedPointer>
     29
     30// Myth
     31#include "mythlogging.h"
     32#include "mythcorecontext.h"
     33#include "mythdirs.h"
     34
     35
     36/*
     37 * Constants
     38 */
     39#define LOC "[netstream] "
     40
     41
     42/*
     43 * Private data
     44 */
     45static QAtomicInt s_nRequest(1); // Unique NetStream request ID
     46static QMutex s_mtx; // Guard local static data e.g. NAMThread singleton
     47
     48
     49/*
     50 * Private types
     51 */
     52// Custom event posted to NAMThread
     53class NetStreamEvent : public QEvent
     54{
     55public:
     56    NetStreamEvent(int id, const QNetworkRequest &req) :
     57        QEvent(QEvent::User),
     58        m_id(id),
     59        m_req(req),
     60        m_bCancelled(false)
     61    { }
     62
     63    const int m_id;
     64    const QNetworkRequest m_req;
     65    volatile bool m_bCancelled;
     66};
     67
     68
     69/**
     70 * Network streaming request
     71 */
     72NetStream::NetStream(const QUrl &url, EMode mode /*= kPreferCache*/) :
     73    m_id(s_nRequest.fetchAndAddRelaxed(1)),
     74    m_state(kClosed),
     75    m_event(0),
     76    m_reply(0),
     77    m_nRedirections(0)
     78{
     79    setObjectName("NetStream " + url.toString());
     80
     81    m_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
     82        mode == kAlwaysCache ? QNetworkRequest::AlwaysCache :
     83        mode == kPreferCache ? QNetworkRequest::PreferCache :
     84        mode == kNeverCache ? QNetworkRequest::AlwaysNetwork :
     85            QNetworkRequest::PreferNetwork );
     86
     87    // Receive requestStarted signals from NAMThread when it processes a NetStreamEvent
     88    connect(&NAMThread::manager(), SIGNAL(requestStarted(int, QNetworkReply*)),
     89        this, SLOT(slotRequestStarted(int, QNetworkReply*)), Qt::DirectConnection );
     90
     91    QMutexLocker locker(&m_mutex);
     92
     93    if (Request(url))
     94        m_state = kPending;
     95}
     96
     97// virtual
     98NetStream::~NetStream()
     99{
     100    Abort();
     101
     102    QMutexLocker locker(&m_mutex);
     103
     104    if (m_reply)
     105    {
     106        m_reply->disconnect();
     107        m_reply->deleteLater();
     108        m_reply = 0;
     109    }
     110}
     111
     112static inline QString Source(const QNetworkRequest &request)
     113{
     114    switch (request.attribute(QNetworkRequest::CacheLoadControlAttribute).toInt())
     115    {
     116    case QNetworkRequest::AlwaysCache: return "cache";
     117    case QNetworkRequest::PreferCache: return "cache-preferred";
     118    case QNetworkRequest::PreferNetwork: return "net-preferred";
     119    case QNetworkRequest::AlwaysNetwork: return "net";
     120    }
     121    return "unknown";
     122}
     123
     124static inline QString Source(QNetworkReply* reply)
     125{
     126    return reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool() ?
     127        "cache" : "host";
     128}
     129
     130// Send request to the network manager
     131// Caller must hold m_mutex
     132bool NetStream::Request(const QUrl& url)
     133{
     134    if (!IsSupported(url))
     135    {
     136        LOG(VB_GENERAL, LOG_WARNING, LOC +
     137            QString("(%1) Request unsupported URL: %2")
     138            .arg(m_id).arg(url.toString()) );
     139        return false;
     140    }
     141
     142    if (m_event)
     143    {
     144        LOG(VB_GENERAL, LOG_ERR, LOC +
     145            QString("(%1) Can't Request while pending").arg(m_id) );
     146        return false;
     147    }
     148
     149    if (m_reply)
     150    {
     151        m_reply->disconnect();
     152        m_reply->deleteLater();
     153        m_reply = 0;
     154    }
     155
     156    m_request.setUrl(url);
     157
     158    const QByteArray ua("User-Agent");
     159    if (!m_request.hasRawHeader(ua))
     160        m_request.setRawHeader(ua, "UK-MHEG/2 MYT001/001 MHGGNU/001");
     161
     162#ifndef QT_NO_OPENSSL
     163#if 1 // The BBC use a self certified cert so don't verify it
     164    if (m_request.url().scheme() == "https")
     165    {
     166        // TODO use cert from carousel auth.tls.<x>
     167        QSslConfiguration ssl(QSslConfiguration::defaultConfiguration());
     168        ssl.setPeerVerifyMode(QSslSocket::VerifyNone);
     169        m_request.setSslConfiguration(ssl);
     170    }
     171#endif
     172#endif
     173
     174    LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Requesting %2 from %3")
     175        .arg(m_id).arg(m_request.url().toString()).arg(Source(m_request)) );
     176    m_event = new NetStreamEvent(m_id, m_request);
     177    NAMThread::PostEvent(m_event);
     178    return true;
     179}
     180
     181// signal from NAMThread manager that a request has been started
     182void NetStream::slotRequestStarted(int id, QNetworkReply *reply)
     183{
     184    QMutexLocker locker(&m_mutex);
     185
     186    if (m_id != id)
     187        return;
     188
     189    m_event = 0; // Event is no longer valid
     190
     191    if (!m_reply)
     192    {
     193        LOG(VB_FILE, LOG_DEBUG, LOC + QString("(%1) Started").arg(m_id) );
     194
     195        m_reply = reply;
     196        m_state = kStarted;
     197
     198        reply->setReadBufferSize(4*1024*1024L); // 0= unlimited, 1MB => 4secs @ 1.5Mbps
     199
     200        // NB The following signals must be Qt::DirectConnection 'cos this slot
     201        // was connected Qt::DirectConnection so the current thread is NAMThread
     202
     203        // QNetworkReply signals
     204        connect(reply, SIGNAL(finished()), this, SLOT(slotFinished()), Qt::DirectConnection );
     205#ifndef QT_NO_OPENSSL
     206        connect(reply, SIGNAL(sslErrors(const QList<QSslError> &)), this,
     207            SLOT(slotSslErrors(const QList<QSslError> &)), Qt::DirectConnection );
     208#endif
     209        // QIODevice signals
     210        connect(reply, SIGNAL(readyRead()), this, SLOT(slotReadyRead()), Qt::DirectConnection );
     211    }
     212    else
     213        LOG(VB_GENERAL, LOG_ERR, LOC +
     214            QString("(%1) Started but m_reply not NULL").arg(m_id));
     215}
     216
     217// signal from QNetworkReply
     218void NetStream::slotReadyRead()
     219{
     220    QMutexLocker locker(&m_mutex);
     221
     222    if (m_reply)
     223    {
     224        LOG(VB_FILE, LOG_DEBUG, LOC + QString("(%1) Ready %2 bytes")
     225            .arg(m_id).arg(m_reply->bytesAvailable()) );
     226        if (m_state < kReady)
     227            m_state = kReady;
     228
     229        locker.unlock();
     230        emit ReadyRead(this);
     231        locker.relock();
     232
     233        m_ready.wakeAll();
     234    }
     235    else
     236        LOG(VB_GENERAL, LOG_ERR, LOC +
     237            QString("(%1) ReadyRead but m_reply = NULL").arg(m_id));
     238}
     239
     240// signal from QNetworkReply
     241void NetStream::slotFinished()
     242{
     243    QMutexLocker locker(&m_mutex);
     244
     245    if (m_reply)
     246    {
     247        QNetworkReply::NetworkError error = m_reply->error();
     248        if (QNetworkReply::NoError == error)
     249        {
     250            // Check for a re-direct
     251            QUrl url = m_reply->attribute(
     252                QNetworkRequest::RedirectionTargetAttribute).toUrl();
     253            if (!url.isValid())
     254            {
     255                m_state = kFinished;
     256            }
     257            else if (m_nRedirections++ > 0)
     258            {
     259                LOG(VB_FILE, LOG_WARNING, LOC + QString("(%1) Too many redirections")
     260                    .arg(m_id));
     261                m_state = kFinished;
     262            }
     263            else if ((url = m_request.url().resolved(url)) == m_request.url())
     264            {
     265                LOG(VB_FILE, LOG_WARNING, LOC + QString("(%1) Redirection loop to %2")
     266                    .arg(m_id).arg(url.toString()) );
     267                m_state = kFinished;
     268            }
     269            else
     270            {
     271                LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Redirecting").arg(m_id));
     272                m_state = Request(url) ? kPending : kFinished;
     273            }
     274        }
     275        else
     276        {
     277            LOG(VB_FILE, LOG_WARNING, LOC + QString("(%1) Error: %2")
     278                .arg(m_id).arg(m_reply->errorString()) );
     279            m_state = kFinished;
     280        }
     281
     282        if (m_state == kFinished)
     283        {
     284            LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Finished %2 bytes from %3")
     285                .arg(m_id).arg(ContentLength()).arg(Source(m_reply)) );
     286
     287            locker.unlock();
     288            emit Finished(this);
     289            locker.relock();
     290
     291            m_finished.wakeAll();
     292        }
     293    }
     294    else
     295        LOG(VB_GENERAL, LOG_ERR, LOC + QString("(%1) Finished but m_reply = NULL")
     296            .arg(m_id));
     297}
     298
     299#ifndef QT_NO_OPENSSL
     300// signal from QNetworkReply
     301void NetStream::slotSslErrors(const QList<QSslError> &errors)
     302{
     303    QMutexLocker locker(&m_mutex);
     304
     305    if (m_reply)
     306    {
     307        bool bIgnore = true;
     308        Q_FOREACH(const QSslError &e, errors)
     309        {
     310            LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) SSL error %2: ")
     311                .arg(m_id).arg(e.error()) + e.errorString() );
     312            switch (e.error())
     313            {
     314#if 1 // The BBC use a self certified cert
     315            case QSslError::SelfSignedCertificateInChain:
     316                break;
     317#endif
     318            default:
     319                bIgnore = false;
     320                break;
     321            }
     322        }
     323
     324        if (bIgnore)
     325        {
     326            LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) SSL errors ignored").arg(m_id));
     327            m_reply->ignoreSslErrors(errors);
     328        }
     329    }
     330    else
     331        LOG(VB_GENERAL, LOG_ERR, LOC +
     332            QString("(%1) SSL error but m_reply = NULL").arg(m_id) );
     333}
     334#endif
     335
     336
     337/**
     338 * RingBuffer interface
     339 */
     340// static
     341bool NetStream::IsSupported(const QUrl &url)
     342{
     343    return url.isValid() &&
     344        (url.scheme() == "http" || url.scheme() == "https") &&
     345        !url.authority().isEmpty() &&
     346        !url.path().isEmpty();
     347}
     348
     349bool NetStream::IsOpen() const
     350{
     351    QMutexLocker locker(&m_mutex);
     352    return m_state > kClosed;
     353}
     354
     355void NetStream::Abort()
     356{
     357    QMutexLocker locker(&m_mutex);
     358
     359    if (m_event)
     360    {
     361        LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Cancelled").arg(m_id) );
     362        m_event->m_bCancelled = true;
     363        m_event = 0;
     364    }
     365
     366    if (m_reply && m_reply->isRunning())
     367    {
     368        LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Abort").arg(m_id) );
     369        locker.unlock();
     370        m_reply->abort();
     371        locker.relock();
     372    }
     373
     374    m_state = kFinished;
     375}
     376
     377int NetStream::safe_read(void *data, unsigned sz, unsigned millisecs /* = 0 */)
     378{
     379    QTime t; t.start();
     380    QMutexLocker locker(&m_mutex);
     381
     382    if (!m_reply)
     383        return -1;
     384
     385    qint64 avail;
     386    while ((avail = m_reply->bytesAvailable()) < sz && m_state < kFinished)
     387    {
     388        unsigned elapsed = t.elapsed();
     389        if (elapsed >= millisecs)
     390            break;
     391        m_ready.wait(&m_mutex, millisecs - elapsed);
     392    }
     393
     394    avail = m_reply->read(reinterpret_cast< char* >(data), sz);
     395    if (avail <= 0)
     396        return m_state >= kFinished ? 0 : -1; // 0= EOF
     397
     398    return (int)avail;
     399}
     400
     401qlonglong NetStream::Seek(qlonglong pos)
     402{
     403    LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Seek %2").arg(m_id).arg(pos) );
     404
     405    QMutexLocker locker(&m_mutex);
     406
     407    if (!m_reply)
     408        return -1;
     409
     410    if (!m_reply->seek(pos))
     411    {
     412        LOG(VB_FILE, LOG_WARNING, LOC + QString("(%1) Seek %2 failed")
     413            .arg(m_id).arg(pos) );
     414        return -1;
     415    }
     416
     417    return pos;
     418}
     419
     420qlonglong NetStream::GetReadPosition() const
     421{
     422    QMutexLocker locker(&m_mutex);
     423
     424    if (!m_reply)
     425        return -1;
     426
     427    return m_reply->pos();
     428}
     429
     430// Caller must hold m_mutex
     431qlonglong NetStream::ContentLength() const
     432{
     433    if (!m_reply)
     434        return -1;
     435
     436    bool ok;
     437    qlonglong len = m_reply->header(QNetworkRequest::ContentLengthHeader)
     438        .toLongLong(&ok);
     439    return ok ? len : m_reply->bytesAvailable();
     440}
     441
     442qlonglong NetStream::GetSize() const
     443{
     444    QMutexLocker locker(&m_mutex);
     445    return ContentLength();
     446}
     447
     448/**
     449 * Synchronous interface
     450 */
     451bool NetStream::WaitTillReady(unsigned long time)
     452{
     453    QMutexLocker locker(&m_mutex);
     454
     455    QTime t; t.start();
     456    while (m_state < kReady)
     457    {
     458        unsigned elapsed = t.elapsed();
     459        if (elapsed > time)
     460            return false;
     461
     462        m_ready.wait(&m_mutex, time - elapsed);
     463    }
     464
     465    return true;
     466}
     467
     468bool NetStream::WaitTillFinished(unsigned long time)
     469{
     470    QMutexLocker locker(&m_mutex);
     471
     472    QTime t; t.start();
     473    while (m_state < kFinished)
     474    {
     475        unsigned elapsed = t.elapsed();
     476        if (elapsed > time)
     477            return false;
     478
     479        m_finished.wait(&m_mutex, time - elapsed);
     480    }
     481
     482    return true;
     483}
     484
     485QNetworkReply::NetworkError NetStream::GetError() const
     486{
     487    QMutexLocker locker(&m_mutex);
     488    return !m_reply ? QNetworkReply::OperationCanceledError : m_reply->error();
     489}
     490
     491QString NetStream::GetErrorString() const
     492{
     493    QMutexLocker locker(&m_mutex);
     494    return !m_reply ? "Operation cancelled" : m_reply->errorString();
     495}
     496
     497qlonglong NetStream::BytesAvailable() const
     498{
     499    QMutexLocker locker(&m_mutex);
     500    return m_reply ? m_reply->bytesAvailable() : 0;
     501}
     502
     503QByteArray NetStream::ReadAll()
     504{
     505    QMutexLocker locker(&m_mutex);
     506    return m_reply ? m_reply->readAll() : QByteArray();
     507}
     508
     509/**
     510 * Asynchronous interface
     511 */
     512bool NetStream::isStarted() const
     513{
     514    QMutexLocker locker(&m_mutex);
     515    return m_state >= kStarted;
     516}
     517
     518bool NetStream::isReady() const
     519{
     520    QMutexLocker locker(&m_mutex);
     521    return m_state >= kReady;
     522}
     523
     524bool NetStream::isFinished() const
     525{
     526    QMutexLocker locker(&m_mutex);
     527    return m_state >= kFinished;
     528}
     529
     530/**
     531 * Public helpers
     532 */
     533// static
     534bool NetStream::isAvailable()
     535{
     536    return NAMThread::isAvailable();
     537}
     538
     539// Time when URI was last written to cache or invalid if not cached.
     540// static
     541QDateTime NetStream::GetLastModified(const QString &url)
     542{
     543    return NAMThread::GetLastModified(url);
     544}
     545
     546
     547/**
     548 * NetworkAccessManager event loop thread
     549 */
     550//static
     551NAMThread & NAMThread::manager()
     552{
     553    QMutexLocker locker(&s_mtx);
     554
     555    // Singleton
     556    static NAMThread thread;
     557    thread.start();
     558    return thread;
     559}
     560
     561NAMThread::NAMThread() : m_bQuit(false), m_nam(0)
     562{
     563    setObjectName("NAMThread");
     564
     565#ifndef QT_NO_OPENSSL
     566    // This ought to be done by the Qt lib but isn't in 4.7
     567    //Q_DECLARE_METATYPE(QList<QSslError>)
     568    qRegisterMetaType< QList<QSslError> >();
     569#endif
     570}
     571
     572// virtual
     573NAMThread::~NAMThread()
     574{
     575    QMutexLocker locker(&m_mutex);
     576    delete m_nam;
     577}
     578
     579// virtual
     580void NAMThread::run()
     581{
     582    LOG(VB_MHEG, LOG_INFO, LOC "NAMThread starting");
     583
     584    m_nam = new QNetworkAccessManager();
     585    m_nam->setObjectName("NetStream NAM");
     586
     587    // Setup cache
     588    QScopedPointer<QNetworkDiskCache> cache(new QNetworkDiskCache());
     589    cache->setCacheDirectory(
     590        QDesktopServices::storageLocation(QDesktopServices::CacheLocation) );
     591    m_nam->setCache(cache.take());
     592
     593    // Setup a network proxy e.g. for TOR: socks://localhost:9050
     594    // TODO get this from mythdb
     595    QString proxy(getenv("MYTHMHEG_PROXY"));
     596    if (!proxy.isEmpty())
     597    {
     598        QUrl url(proxy, QUrl::TolerantMode);
     599        QNetworkProxy::ProxyType type =
     600            url.scheme().isEmpty() ? QNetworkProxy::HttpProxy :
     601            url.scheme() == "socks" ? QNetworkProxy::Socks5Proxy :
     602            url.scheme() == "http" ? QNetworkProxy::HttpProxy :
     603            url.scheme() == "https" ? QNetworkProxy::HttpProxy :
     604            url.scheme() == "cache" ? QNetworkProxy::HttpCachingProxy :
     605            url.scheme() == "ftp" ? QNetworkProxy::FtpCachingProxy :
     606            QNetworkProxy::NoProxy;
     607        if (QNetworkProxy::NoProxy != type)
     608        {
     609            LOG(VB_MHEG, LOG_INFO, LOC "Using proxy: " + proxy);
     610            m_nam->setProxy(QNetworkProxy(
     611                type, url.host(), url.port(), url.userName(), url.password() ));
     612        }
     613        else
     614        {
     615            LOG(VB_MHEG, LOG_ERR, LOC + QString("Unknown proxy type %1")
     616                .arg(url.scheme()) );
     617        }
     618    }
     619
     620    // Quit when main app quits
     621    connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(quit()) );
     622
     623    m_running.release();
     624
     625    while(!m_bQuit)
     626    {
     627        // Process NAM events
     628        QCoreApplication::processEvents();
     629
     630        QMutexLocker locker(&m_mutex);
     631        m_work.wait(&m_mutex, 100);
     632        while (!m_workQ.isEmpty())
     633        {
     634            QScopedPointer< QEvent > ev(m_workQ.dequeue());
     635            locker.unlock();
     636            NewRequest(ev.data());
     637        }
     638    }
     639
     640    m_running.acquire();
     641
     642    delete m_nam;
     643    m_nam = 0;
     644
     645    LOG(VB_MHEG, LOG_INFO, LOC "NAMThread stopped");
     646}
     647
     648// slot
     649void NAMThread::quit()
     650{
     651    m_bQuit = true;
     652    QThread::quit();
     653}
     654
     655// static
     656void NAMThread::PostEvent(QEvent *event)
     657{
     658    NAMThread &m = manager();
     659    QMutexLocker locker(&m.m_mutex);
     660    m.m_workQ.enqueue(event);
     661}
     662
     663bool NAMThread::NewRequest(QEvent *event)
     664{
     665    NetStreamEvent *p = dynamic_cast< NetStreamEvent* >(event);
     666    if (!p)
     667    {
     668        LOG(VB_GENERAL, LOG_ERR, LOC "Invalid NetStreamEvent");
     669        return false;
     670    }
     671
     672    if (!p->m_bCancelled)
     673    {
     674        LOG(VB_FILE, LOG_DEBUG, LOC "get " + p->m_id );
     675        QNetworkReply *reply = m_nam->get(p->m_req);
     676        emit requestStarted(p->m_id, reply);
     677    }
     678    else
     679        LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) NetStreamEvent cancelled").arg(p->m_id) );
     680    return true;
     681}
     682
     683// static
     684bool NAMThread::isAvailable()
     685{
     686    NAMThread &m = manager();
     687
     688    if (!m.m_running.tryAcquire(1, 3000))
     689        return false;
     690
     691    m.m_running.release();
     692
     693    QMutexLocker locker(&m.m_mutex);
     694
     695    if (!m.m_nam)
     696        return false;
     697
     698#if QT_VERSION >= 0x040700
     699    switch (m.m_nam->networkAccessible())
     700    {
     701    case QNetworkAccessManager::Accessible: return true;
     702    case QNetworkAccessManager::NotAccessible: return false;
     703    case QNetworkAccessManager::UnknownAccessibility: return true;
     704    }
     705#else
     706    // can't test network accessibility so assume it is available
     707    return true;
     708#endif
     709    return false;
     710}
     711
     712// Time when URI was last written to cache or invalid if not cached.
     713// static
     714QDateTime NAMThread::GetLastModified(const QString &url)
     715{
     716    NAMThread &m = manager();
     717
     718    QMutexLocker locker(&m.m_mutex);
     719
     720    if (!m.m_nam)
     721        return QDateTime(); // Invalid
     722
     723    QAbstractNetworkCache *cache = m.m_nam->cache();
     724    if (!cache)
     725        return QDateTime(); // Invalid
     726
     727    QNetworkCacheMetaData meta = cache->metaData(QUrl(url));
     728    if (!meta.isValid())
     729    {
     730        LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetLastModified('%1') not in cache")
     731            .arg(url));
     732        return QDateTime(); // Invalid
     733    }
     734
     735    // Check if expired
     736    QDateTime const now(QDateTime::currentDateTime()); // local time
     737    QDateTime expire = meta.expirationDate();
     738    if (expire.isValid() && expire.toLocalTime() < now)
     739    {
     740        LOG(VB_FILE, LOG_INFO, LOC + QString("GetLastModified('%1') past expiration %2")
     741            .arg(url).arg(expire.toString()));
     742        return QDateTime(); // Invalid
     743    }
     744
     745    // Get time URI was modified (Last-Modified header)  NB this may be invalid
     746    QDateTime lastMod = meta.lastModified();
     747
     748    QNetworkCacheMetaData::RawHeaderList headers = meta.rawHeaders();
     749    Q_FOREACH(const QNetworkCacheMetaData::RawHeader &h, headers)
     750    {
     751        // RFC 1123 date format: Thu, 01 Dec 1994 16:00:00 GMT
     752        static const char kszFormat[] = "ddd, dd MMM yyyy HH:mm:ss 'GMT'";
     753
     754        QString const first(h.first.toLower());
     755        if (first == "cache-control")
     756        {
     757            QString const second(h.second.toLower());
     758            if (second == "no-cache" || second == "no-store")
     759            {
     760                LOG(VB_FILE, LOG_INFO, LOC +
     761                    QString("GetLastModified('%1') Cache-Control disabled").arg(url));
     762                cache->remove(QUrl(url));
     763                return QDateTime(); // Invalid
     764            }
     765        }
     766        else if (first == "date")
     767        {
     768            QDateTime d = QDateTime::fromString(h.second, kszFormat);
     769            if (!d.isValid())
     770            {
     771                LOG(VB_GENERAL, LOG_WARNING, LOC +
     772                    QString("GetLastModified invalid Date header '%1'")
     773                    .arg(h.second.constData()));
     774                continue;
     775            }
     776            d.setTimeSpec(Qt::UTC);
     777            lastMod = d;
     778        }
     779    }
     780
     781    LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetLastModified('%1') last modified %2")
     782        .arg(url).arg(lastMod.toString()));
     783    return lastMod;
     784}
     785
     786/* End of file */
  • new file mythtv/libs/libmythtv/netstream.h

    diff --git a/mythtv/libs/libmythtv/netstream.h b/mythtv/libs/libmythtv/netstream.h
    new file mode 100644
    index 0000000..c740013
    - +  
     1/* Network stream
     2 * Copyright 2011 Lawrence Rust <lvr at softsystem dot co dot uk>
     3 */
     4#ifndef NETSTREAM_H
     5#define NETSTREAM_H
     6
     7#include <QList>
     8#include <QString>
     9#include <QByteArray>
     10#include <QObject>
     11#include <QMutex>
     12#include <QSemaphore>
     13#include <QThread>
     14#include <QNetworkRequest>
     15#include <QNetworkReply>
     16#include <QSslError>
     17#include <QWaitCondition>
     18#include <QQueue>
     19#include <QDateTime>
     20
     21class QUrl;
     22class QNetworkAccessManager;
     23class NetStreamEvent;
     24
     25
     26/**
     27 * Stream content from a URI
     28 */
     29class NetStream : public QObject
     30{
     31    Q_OBJECT
     32    Q_DISABLE_COPY(NetStream)
     33
     34public:
     35    enum EMode { kNeverCache, kPreferCache, kAlwaysCache };
     36    NetStream(const QUrl &, EMode mode = kPreferCache);
     37    virtual ~NetStream();
     38
     39public:
     40    // RingBuffer interface
     41    static bool IsSupported(const QUrl &);
     42    bool IsOpen() const;
     43    void Abort();
     44    int safe_read(void *data, unsigned size, unsigned millisecs = 0);
     45    qlonglong Seek(qlonglong);
     46    qlonglong GetReadPosition() const;
     47    qlonglong GetSize() const;
     48
     49    // Properties
     50    QUrl Url() const { return m_request.url(); }
     51
     52    // Synchronous interface
     53    bool WaitTillReady(unsigned long millisecs);
     54    bool WaitTillFinished(unsigned long millisecs);
     55    QNetworkReply::NetworkError GetError() const;
     56    QString GetErrorString() const;
     57    qlonglong BytesAvailable() const;
     58    QByteArray ReadAll();
     59
     60    // Async interface
     61    bool isStarted() const;
     62    bool isReady() const;
     63    bool isFinished() const;
     64
     65signals:
     66    void ReadyRead(QObject*);
     67    void Finished(QObject*);
     68
     69public:
     70    // Time when a URI was last written to cache or invalid if not cached.
     71    static QDateTime GetLastModified(const QString &url);
     72    // Is the network accessible
     73    static bool isAvailable();
     74
     75    // Implementation
     76private slots:
     77    // NAMThread signals
     78    void slotRequestStarted(int, QNetworkReply *);
     79    // QNetworkReply signals
     80    void slotFinished();
     81#ifndef QT_NO_OPENSSL
     82    void slotSslErrors(const QList<QSslError> & errors);
     83#endif
     84    // QIODevice signals
     85    void slotReadyRead();
     86
     87private:
     88    bool Request(const QUrl &);
     89    qlonglong ContentLength() const;
     90
     91    const int m_id; // Unique request ID
     92
     93    mutable QMutex m_mutex; // Protects r/w access to the following data
     94    QNetworkRequest m_request;
     95    enum { kClosed, kPending, kStarted, kReady, kFinished } m_state;
     96    NetStreamEvent* m_event;
     97    QNetworkReply* m_reply;
     98    int m_nRedirections;
     99    QWaitCondition m_ready;
     100    QWaitCondition m_finished;
     101};
     102
     103
     104/**
     105 * Thread to process NetStream requests
     106 */
     107class NAMThread : public QThread
     108{
     109    Q_OBJECT
     110    Q_DISABLE_COPY(NAMThread)
     111
     112    // Use manager() to create
     113    NAMThread();
     114
     115public:
     116    static NAMThread & manager(); // Singleton
     117    virtual ~NAMThread();
     118
     119    static void PostEvent(QEvent *);
     120
     121    static bool isAvailable(); // is network usable
     122    static QDateTime GetLastModified(const QString &url);
     123
     124signals:
     125     void requestStarted(int, QNetworkReply *);
     126
     127    // Implementation
     128protected:
     129    virtual void run(); // QThread override
     130    bool NewRequest(QEvent *);
     131
     132private slots:
     133    void quit();
     134
     135private:
     136    volatile bool m_bQuit;
     137    QSemaphore m_running;
     138    mutable QMutex m_mutex; // Protects r/w access to the following data
     139    QNetworkAccessManager *m_nam;
     140    QQueue< QEvent * > m_workQ;
     141    QWaitCondition m_work;
     142};
     143
     144#endif /* ndef NETSTREAM_H */
  • mythtv/libs/libmythtv/ringbuffer.cpp

    diff --git a/mythtv/libs/libmythtv/ringbuffer.cpp b/mythtv/libs/libmythtv/ringbuffer.cpp
    index 6940300..a5fb4c1 100644
    a b bool RingBuffer::IsNearEnd(double fps, uint vvf) const 
    415415    // WARNING: readahead_frames can greatly overestimate or underestimate
    416416    //          the number of frames available in the read ahead buffer
    417417    //          when rh_frames is less than the keyframe distance.
     418    if (fps == 0.)
     419        return false;
    418420    double bytes_per_frame = kbits_per_sec * (1000.0/8.0) / fps;
     421    if (bytes_per_frame == 0.)
     422        return false;
    419423    double readahead_frames = sz / bytes_per_frame;
    420424
    421425    bool near_end = ((vvf + readahead_frames) < 10.0) || (sz < rbs*1.5);
    int RingBuffer::ReadBufAvail(void) const 
    454458    return ret;
    455459}
    456460
     461inline int RingBuffer::ReadBufUsed() const
     462{
     463    return (bufferSize - 1) - ReadBufFree();
     464}
     465
     466inline bool RingBuffer::ReadsAllowed() const
     467{
     468    return ateof || setswitchtonext || commserror ||
     469        // Ensure some hysteresis around fill_min
     470        ReadBufUsed() >= (readsallowed ? 1 : fill_min);
     471}
     472
    457473/** \fn RingBuffer::ResetReadAhead(long long)
    458474 *  \brief Restart the read-ahead thread at the 'newinternal' position.
    459475 *
    void RingBuffer::run(void) 
    896912            }
    897913        }
    898914
    899         int used = bufferSize - ReadBufFree();
    900 
    901915        bool reads_were_allowed = readsallowed;
    902916
    903         if ((0 == read_return) || (numfailures > 5) ||
    904             (readsallowed != (used >= fill_min || ateof ||
    905                               setswitchtonext || commserror)))
     917        if ((0 == read_return) || (numfailures > 5) || ReadsAllowed() != readsallowed)
    906918        {
    907919            // If readpos changes while the lock is released
    908920            // we should not handle the 0 read_return now.
    void RingBuffer::run(void) 
    913925
    914926            commserror |= (numfailures > 5);
    915927
    916             readsallowed = used >= fill_min || ateof ||
    917                 setswitchtonext || commserror;
     928            bool bReadsAllowed = ReadsAllowed();
     929            if (readsallowed != bReadsAllowed)
     930            {
     931                readsallowed = bReadsAllowed;
     932                LOG(VB_FILE, LOG_INFO, LOC + (bReadsAllowed ?
     933                    QString("Reads allowed: %1 bytes available").arg(ReadBufUsed()) :
     934                    QString("Rebuffering %1..%2").arg(ReadBufUsed()).arg(fill_min)) );
     935            }
    918936
    919937            if (0 == read_return && old_readpos == readpos)
    920938            {
    void RingBuffer::run(void) 
    937955
    938956            rwlock.unlock();
    939957            rwlock.lockForRead();
    940             used = bufferSize - ReadBufFree();
    941958        }
    942959
    943960        LOG(VB_FILE, LOG_DEBUG, LOC + "@ end of read ahead loop");
    944961
    945962        if (!readsallowed || commserror || ateof || setswitchtonext ||
    946             (wanttoread <= used && wanttoread > 0))
     963            (wanttoread <= ReadBufUsed() && wanttoread > 0))
    947964        {
    948965            // To give other threads a good chance to handle these
    949966            // conditions, even if they are only requesting a read lock
    void RingBuffer::run(void) 
    957974        {
    958975            // yield if we have nothing to do...
    959976            if (!request_pause && reads_were_allowed &&
    960                 (used >= fill_threshold || ateof || setswitchtonext))
     977                (ReadBufUsed() >= fill_threshold || ateof || setswitchtonext || ignoreliveeof))
    961978            {
    962979                generalWait.wait(&rwlock, 50);
    963980            }
    void RingBuffer::run(void) 
    967984                generalWait.wakeAll();
    968985                rwlock.unlock();
    969986                usleep(5 * 1000);
    970                 rwlock.lockForRead();           
     987                rwlock.lockForRead();
    971988            }
    972989        }
    973990    }
    bool RingBuffer::WaitForReadsAllowed(void) 
    10231040    while (!readsallowed && !stopreads &&
    10241041           !request_pause && !commserror && readaheadrunning)
    10251042    {
    1026         generalWait.wait(&rwlock, 1000);
    1027         if (!readsallowed && t.elapsed() > 1000)
     1043        // The timeout should allow for congestion of internet streamed media
     1044        if (t.elapsed() >= 30000)
    10281045        {
    1029             LOG(VB_GENERAL, LOG_WARNING, LOC +
    1030                 "Taking too long to be allowed to read..");
    1031 
    1032             if (t.elapsed() > 10000)
    1033             {
    1034                 LOG(VB_GENERAL, LOG_ERR, LOC + "Took more than 10 seconds to "
    1035                                                "be allowed to read, aborting.");
    1036                 return false;
    1037             }
     1046            LOG(VB_GENERAL, LOG_ERR, LOC +
     1047                QString("Waited %1 seconds to be allowed to read, aborting.")
     1048                .arg(t.elapsed()/1000) );
     1049            return false;
    10381050        }
     1051
     1052        generalWait.wait(&rwlock, 250);
    10391053    }
    10401054
    1041     return readsallowed;
     1055    if (t.elapsed() >= 500)
     1056    {
     1057        LOG(VB_GENERAL, LOG_WARNING, LOC +
     1058            QString("Waited %1 mS to be allowed to read (avail=%2 fill_min=%3)..")
     1059            .arg(t.elapsed()).arg(ReadBufAvail()).arg(fill_min) );
     1060    }
     1061    return true;
    10421062}
    10431063
    10441064bool RingBuffer::WaitForAvail(int count)
    bool RingBuffer::WaitForAvail(int count) 
    10621082        generalWait.wakeAll();
    10631083    }
    10641084
    1065     MythTimer t;
    1066     t.start();
    1067     while ((avail < count) && !stopreads &&
    1068            !request_pause && !commserror && readaheadrunning)
     1085    MythTimer t; t.start();
     1086    wanttoread = count;
     1087    while (avail < count && !stopreads && !request_pause &&
     1088            !commserror && readaheadrunning)
    10691089    {
    1070         wanttoread = count;
    1071         generalWait.wait(&rwlock, 250);
    1072         avail = ReadBufAvail();
    1073 
    1074         if (ateof && avail < count)
    1075             count = avail;
    1076 
    1077         if (avail < count)
     1090        uint elapsed = t.elapsed();
     1091        if (elapsed >= 10000)
    10781092        {
    10791093            int elapsed = t.elapsed();
    10801094            if (elapsed > 500 && low_buffers && avail >= fill_min)
    bool RingBuffer::WaitForAvail(int count) 
    11011115                return false;
    11021116            }
    11031117        }
     1118        else if (elapsed >= 100 && avail)
     1119        {
     1120            LOG(VB_GENERAL, LOG_INFO, LOC +
     1121                QString("Waited %1 mS for %2 bytes (wanted %3)")
     1122                .arg(elapsed).arg(avail).arg(count) );
     1123            count = avail;
     1124            generalWait.wakeAll();
     1125            break;
     1126        }
     1127
     1128        generalWait.wait(&rwlock, 100);
     1129        avail = ReadBufAvail();
    11041130    }
    11051131
    11061132    wanttoread = 0;
    int RingBuffer::ReadDirect(void *buf, int count, bool peek) 
    11621188        if (new_pos != old_pos)
    11631189        {
    11641190            LOG(VB_GENERAL, LOG_ERR, LOC +
    1165                 QString("Peek() Failed to return from new "
     1191                QString("Seek() Failed to return from new "
    11661192                        "position %1 to old position %2, now "
    11671193                        "at position %3")
    11681194                    .arg(old_pos - ret).arg(old_pos).arg(new_pos));
    int RingBuffer::ReadDirect(void *buf, int count, bool peek) 
    11821208 */
    11831209int RingBuffer::ReadPriv(void *buf, int count, bool peek)
    11841210{
    1185     QString loc_desc = QString("ReadPriv(..%1, %2)")
     1211    const QString loc_desc = QString("ReadPriv(..%1, %2)")
    11861212        .arg(count).arg(peek?"peek":"normal");
    1187     LOG(VB_FILE, LOG_DEBUG, LOC + loc_desc +
    1188         QString(" @%1 -- begin").arg(rbrpos));
    11891213
    11901214    rwlock.lockForRead();
     1215
     1216    LOG(VB_FILE, LOG_DEBUG, LOC + loc_desc +
     1217        QString(" @%1 avail=%2 -- begin").arg(rbrpos).arg(ReadBufAvail()));
     1218
    11911219    if (writemode)
    11921220    {
    11931221        LOG(VB_GENERAL, LOG_ERR, LOC + loc_desc +
    int RingBuffer::ReadPriv(void *buf, int count, bool peek) 
    12181246        if (request_pause || stopreads ||
    12191247            !readaheadrunning || (ignorereadpos >= 0))
    12201248        {
     1249            LOG(VB_FILE, LOG_DEBUG, LOC + loc_desc + " -- direct read");
    12211250            int ret = ReadDirect(buf, count, peek);
    12221251            LOG(VB_FILE, LOG_DEBUG, LOC + loc_desc +
    12231252                QString(": ReadDirect checksum %1")
    int RingBuffer::ReadPriv(void *buf, int count, bool peek) 
    12321261    if (!WaitForReadsAllowed())
    12331262    {
    12341263        LOG(VB_FILE, LOG_NOTICE, LOC + loc_desc + ": !WaitForReadsAllowed()");
    1235         rwlock.unlock();
    1236         stopreads = true; // this needs to be outside the lock
    1237         rwlock.lockForWrite();
    1238         wanttoread = 0;
     1264        // NB don't set stopreads or else the next ReadPriv will call ReadDirect
     1265        // which, if there's any readahead, will cause data to be returned out
     1266        // of sequence
    12391267        rwlock.unlock();
    12401268        return 0;
    12411269    }
  • mythtv/libs/libmythtv/ringbuffer.h

    diff --git a/mythtv/libs/libmythtv/ringbuffer.h b/mythtv/libs/libmythtv/ringbuffer.h
    index 45bd956..8c1a710 100644
    a b enum RingBufferType 
    3838    kRingBuffer_DVD,
    3939    kRingBuffer_BD,
    4040    kRingBuffer_HTTP,
     41    kRingBuffer_MHEG
    4142};
    4243
    4344class MTV_PUBLIC RingBuffer : protected MThread
    4445{
     46    friend class ICRingBuffer;
     47
    4548  public:
    4649    static RingBuffer *Create(const QString &lfilename, bool write,
    4750                              bool usereadahead = true,
    class MTV_PUBLIC RingBuffer : protected MThread 
    8487    virtual bool IsBookmarkAllowed(void) { return true; }
    8588    virtual int  BestBufferSize(void)   { return 32768; }
    8689    static QString BitrateToString(uint64_t rate, bool hz = false);
     90    RingBufferType GetType() const { return type; }
    8791
    8892    // DVD and bluray methods
    8993    bool IsDisc(void) const { return IsDVD() || IsBD(); }
    class MTV_PUBLIC RingBuffer : protected MThread 
    167171
    168172    int ReadBufFree(void) const;
    169173    int ReadBufAvail(void) const;
     174    int ReadBufUsed() const;
     175    bool ReadsAllowed() const;
    170176
    171177    void ResetReadAhead(long long newinternal);
    172178    void KillReadAheadThread(void);