Ticket #10019: iplayer-master.diff

File iplayer-master.diff, 124.4 KB (added by Lawrence Rust <lvr@…>, 8 years ago)

Fixed duration time and implemented seeking

  • 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/avformatdecoder.cpp

    diff --git a/mythtv/libs/libmythtv/avformatdecoder.cpp b/mythtv/libs/libmythtv/avformatdecoder.cpp
    index 89cc07b..c70a472 100644
    a b int AvFormatDecoder::ScanStreams(bool novideo) 
    21512151        }
    21522152    }
    21532153
     2154    if ((uint)ic->bit_rate > bitrate)
     2155        bitrate = (uint)ic->bit_rate;
     2156
    21542157    if (bitrate > 0)
    21552158    {
    21562159        bitrate = (bitrate + 999) / 1000;
  • 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..d23bc3c
    - +  
     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, NetStream::kNeverCache));
     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    if (!m_stream)
     87        return -1;
     88
     89    // lockForWrite takes priority over lockForRead, so this will
     90    // take priority over the lockForRead in the read ahead thread.
     91    if (!has_lock)
     92        rwlock.lockForWrite();
     93
     94    poslock.lockForWrite();
     95
     96    long long ret;
     97
     98    // Optimize no-op seeks
     99    if (readaheadrunning &&
     100        ((whence == SEEK_SET && pos == readpos) ||
     101         (whence == SEEK_CUR && pos == 0)))
     102    {
     103        ret = readpos;
     104
     105        poslock.unlock();
     106        if (!has_lock)
     107            rwlock.unlock();
     108
     109        return ret;
     110    }
     111
     112    switch (whence)
     113    {
     114        case SEEK_SET:
     115            break;
     116        case SEEK_CUR:
     117            pos += m_stream->GetReadPosition();
     118            break;
     119        case SEEK_END:
     120            pos += m_stream->GetSize();
     121            break;
     122        default:
     123            errno = EINVAL;
     124            ret = -1;
     125            goto err;
     126    }
     127
     128    ret = m_stream->Seek(pos);
     129    if (ret >= 0)
     130    {
     131        readpos = ret;
     132
     133        ignorereadpos = -1;
     134
     135        if (readaheadrunning)
     136            ResetReadAhead(readpos);
     137
     138        readAdjust = 0;
     139    }
     140
     141err:
     142    poslock.unlock();
     143
     144    generalWait.wakeAll();
     145
     146    if (!has_lock)
     147        rwlock.unlock();
     148
     149    return ret;
     150}
     151
     152int ICRingBuffer::safe_read(void *data, uint sz)
     153{
     154    return m_stream ? m_stream->safe_read(data, sz, 1000) : (ateof = true, 0);
     155}
     156
     157long long ICRingBuffer::GetRealFileSize(void) const
     158{
     159    return m_stream ? m_stream->GetSize() : -1;
     160}
     161
     162// Take ownership of parent RingBuffer
     163RingBuffer *ICRingBuffer::Take()
     164{
     165    RingBuffer *parent = m_parent;
     166    if (parent && IsOpen())
     167        parent->Unpause();
     168    m_parent = 0;
     169    return parent;
     170}
     171
     172// 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..dc7585d
    - +  
     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 false;  }
     24    virtual bool IsSeekingAllowed(void) { return true; }
     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 7ed2e4b..24cd1a6 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 b26cbd0..27974ad 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 405fff9..902b778 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 { 
    430432        SOURCES += dsmcc.cpp                dsmcccache.cpp
    431433        SOURCES += dsmccbiop.cpp            dsmccobjcarousel.cpp
    432434
     435         # MHEG interaction channel
     436        HEADERS += mhegic.h                 netstream.h
     437        SOURCES += mhegic.cpp               netstream.cpp
     438
    433439        # MHEG/MHI stuff
    434440        HEADERS += interactivetv.h          mhi.h
    435441        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 8370a72..1115be4 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) 
    359361// Called by the engine to check for the presence of an object in the carousel.
    360362bool MHIContext::CheckCarouselObject(QString objectPath)
    361363{
     364    if (objectPath.startsWith("http:") || objectPath.startsWith("https:"))
     365    {
     366        // TODO verify access to server in carousel file auth.servers
     367        // TODO use TLS cert from carousel auth.tls.<x>
     368        return m_ic.CheckFile(objectPath);
     369    }
     370
    362371    QStringList path = objectPath.split(QChar('/'), QString::SkipEmptyParts);
    363372    QByteArray result; // Unused
    364373    int res = m_dsmcc->GetDSMCCObject(path, result);
    bool MHIContext::CheckCarouselObject(QString objectPath) 
    368377// Called by the engine to request data from the carousel.
    369378bool MHIContext::GetCarouselData(QString objectPath, QByteArray &result)
    370379{
     380    bool const isIC = objectPath.startsWith("http:") || objectPath.startsWith("https:");
     381
    371382    // Get the path components.  The string will normally begin with "//"
    372383    // since this is an absolute path but that will be removed by split.
    373384    QStringList path = objectPath.split(QChar('/'), QString::SkipEmptyParts);
    bool MHIContext::GetCarouselData(QString objectPath, QByteArray &result) 
    376387    // the result.
    377388
    378389    QMutexLocker locker(&m_runLock);
     390    bool bReported = false;
    379391    QTime t; t.start();
    380392    while (!m_stop)
    381393    {
    382394        locker.unlock();
    383395
    384         int res = m_dsmcc->GetDSMCCObject(path, result);
    385         if (res == 0)
    386             return true; // Found it
    387         else if (res < 0)
    388             return false; // Not there.
    389         else if (t.elapsed() > 60000) // TODO get this from carousel info
    390             return false; // Not there.
     396        if (isIC)
     397        {
     398            // TODO verify access to server in carousel file auth.servers
     399            // TODO use TLS cert from carousel file auth.tls.<x>
     400            switch (m_ic.GetFile(objectPath, result))
     401            {
     402            case MHInteractionChannel::kSuccess:
     403                if (bReported)
     404                    LOG(VB_MHEG, LOG_INFO, QString("[mhi] Received %1").arg(objectPath));
     405                return true;
     406            case MHInteractionChannel::kError:
     407                if (bReported)
     408                    LOG(VB_MHEG, LOG_INFO, QString("[mhi] Not found %1").arg(objectPath));
     409                return false;
     410            case MHInteractionChannel::kPending:
     411                break;
     412            }
     413        }
     414        else
     415        {
     416            int res = m_dsmcc->GetDSMCCObject(path, result);
     417            if (res == 0)
     418            {
     419                if (bReported)
     420                    LOG(VB_MHEG, LOG_INFO, QString("[mhi] Received %1").arg(objectPath));
     421                return true; // Found it
     422            }
     423            // NB don't exit if -1 (not present) is returned as the object may
     424            // arrive later.  Exiting can cause the inital app to not be found
     425        }
     426
     427        if (t.elapsed() > 60000) // TODO get this from carousel info
     428             return false; // Not there.
    391429        // Otherwise we block.
    392         // Process DSMCC packets then block for a second or until we receive
     430        if (!bReported)
     431        {
     432            bReported = true;
     433            LOG(VB_MHEG, LOG_INFO, QString("[mhi] Waiting for %1").arg(objectPath));
     434        }
     435        // Process DSMCC packets then block for a while or until we receive
    393436        // some more packets.  We should eventually find out if this item is
    394437        // present.
    395438        ProcessDSMCCQueue();
    396439
    397440        locker.relock();
    398         if (!m_stop)
    399             m_engine_wait.wait(locker.mutex(), 1000);
     441        m_engine_wait.wait(locker.mutex(), 300);
    400442    }
    401443    return false; // Stop has been set.  Say the object isn't present.
    402444}
    403445
    404 // Called from tv_play when a key is pressed.
    405 // If it is one in the current profile we queue it for the engine
    406 // and return true otherwise we return false.
    407 bool MHIContext::OfferKey(QString key)
     446// Mapping from key name & UserInput register to UserInput EventData
     447class MHKeyLookup
    408448{
    409     int action = 0;
    410     QMutexLocker locker(&m_keyLock);
     449    typedef QPair< QString, int /*UserInput register*/ > key_t;
     450
     451public:
     452    MHKeyLookup();
     453
     454    int Find(const QString &name, int reg) const
     455        { return m_map.value(key_t(name,reg), 0); }
    411456
     457private:
     458    void key(const QString &name, int code, int r1,
     459        int r2=0, int r3=0, int r4=0, int r5=0, int r6=0, int r7=0, int r8=0, int r9=0);
     460
     461    QHash<key_t,int /*EventData*/ > m_map;
     462};
     463
     464void MHKeyLookup::key(const QString &name, int code, int r1,
     465    int r2, int r3, int r4, int r5, int r6, int r7, int r8, int r9)
     466{
     467    m_map.insert(key_t(name,r1), code);
     468    if (r2 > 0)
     469        m_map.insert(key_t(name,r2), code);
     470    if (r3 > 0)
     471        m_map.insert(key_t(name,r3), code);
     472    if (r4 > 0)
     473        m_map.insert(key_t(name,r4), code);
     474    if (r5 > 0)
     475        m_map.insert(key_t(name,r5), code);
     476    if (r6 > 0)
     477        m_map.insert(key_t(name,r6), code);
     478    if (r7 > 0)
     479        m_map.insert(key_t(name,r7), code);
     480    if (r8 > 0)
     481        m_map.insert(key_t(name,r8), code);
     482    if (r9 > 0)
     483        m_map.insert(key_t(name,r9), code);
     484}
     485
     486MHKeyLookup::MHKeyLookup()
     487{
    412488    // This supports the UK and NZ key profile registers.
    413489    // The UK uses 3, 4 and 5 and NZ 13, 14 and 15.  These are
    414490    // similar but the NZ profile also provides an EPG key.
     491    // ETSI ES 202 184 V2.2.1 (2011-03) adds group 6 for ICE.
     492    // The BBC use group 7 for ICE
     493    key(ACTION_UP,           1, 4,5,6,7,14,15);
     494    key(ACTION_DOWN,         2, 4,5,6,7,14,15);
     495    key(ACTION_LEFT,         3, 4,5,6,7,14,15);
     496    key(ACTION_RIGHT,        4, 4,5,6,7,14,15);
     497    key(ACTION_0,            5, 4,6,7,14);
     498    key(ACTION_1,            6, 4,6,7,14);
     499    key(ACTION_2,            7, 4,6,7,14);
     500    key(ACTION_3,            8, 4,6,7,14);
     501    key(ACTION_4,            9, 4,6,7,14);
     502    key(ACTION_5,           10, 4,6,7,14);
     503    key(ACTION_6,           11, 4,6,7,14);
     504    key(ACTION_7,           12, 4,6,7,14);
     505    key(ACTION_8,           13, 4,6,7,14);
     506    key(ACTION_9,           14, 4,6,7,14);
     507    key(ACTION_SELECT,      15, 4,5,6,7,14,15);
     508    key(ACTION_TEXTEXIT,    16, 3,4,5,6,7,13,14,15); // 16= Cancel
     509    // 17= help
     510    // 18..99 reserved by DAVIC
     511    key(ACTION_MENURED,    100, 3,4,5,6,7,13,14,15);
     512    key(ACTION_MENUGREEN,  101, 3,4,5,6,7,13,14,15);
     513    key(ACTION_MENUYELLOW, 102, 3,4,5,6,7,13,14,15);
     514    key(ACTION_MENUBLUE,   103, 3,4,5,6,7,13,14,15);
     515    key(ACTION_MENUTEXT,   104, 3,4,5,6,7);
     516    key(ACTION_MENUTEXT,   105, 13,14,15); // NB from original Myth code
     517    // 105..119 reserved for future spec
     518    key(ACTION_STOP,       120, 6,7);
     519    key(ACTION_PLAY,       121, 6,7);
     520    key(ACTION_PAUSE,      122, 6,7);
     521    key(ACTION_JUMPFFWD,   123, 6,7); // 123= Skip Forward
     522    key(ACTION_JUMPRWND,   124, 6,7); // 124= Skip Back
     523#if 0 // These conflict with left & right
     524    key(ACTION_SEEKFFWD,   125, 6,7); // 125= Fast Forward
     525    key(ACTION_SEEKRWND,   126, 6,7); // 126= Rewind
     526#endif
     527    key(ACTION_PLAYBACK,   127, 6,7);
     528    // 128..256 reserved for future spec
     529    // 257..299 vendor specific
     530    key(ACTION_MENUEPG,    300, 13,14,15);
     531    // 301.. Vendor specific
     532}
    415533
    416     if (key == ACTION_UP)
    417     {
    418         if (m_keyProfile == 4 || m_keyProfile == 5 ||
    419             m_keyProfile == 14 || m_keyProfile == 15)
    420             action = 1;
    421     }
    422     else if (key == ACTION_DOWN)
    423     {
    424         if (m_keyProfile == 4 || m_keyProfile == 5 ||
    425             m_keyProfile == 14 || m_keyProfile == 15)
    426             action = 2;
    427     }
    428     else if (key == ACTION_LEFT)
    429     {
    430         if (m_keyProfile == 4 || m_keyProfile == 5 ||
    431             m_keyProfile == 14 || m_keyProfile == 15)
    432             action = 3;
    433     }
    434     else if (key == ACTION_RIGHT)
    435     {
    436         if (m_keyProfile == 4 || m_keyProfile == 5 ||
    437             m_keyProfile == 14 || m_keyProfile == 15)
    438             action = 4;
    439     }
    440     else if (key == ACTION_0 || key == ACTION_1 || key == ACTION_2 ||
    441              key == ACTION_3 || key == ACTION_4 || key == ACTION_5 ||
    442              key == ACTION_6 || key == ACTION_7 || key == ACTION_8 ||
    443              key == ACTION_9)
    444     {
    445         if (m_keyProfile == 4 || m_keyProfile == 14)
    446             action = key.toInt() + 5;
    447     }
    448     else if (key == ACTION_SELECT)
    449     {
    450         if (m_keyProfile == 4 || m_keyProfile == 5 ||
    451             m_keyProfile == 14 || m_keyProfile == 15)
    452             action = 15;
    453     }
    454     else if (key == ACTION_TEXTEXIT)
    455         action = 16;
    456     else if (key == ACTION_MENURED)
    457         action = 100;
    458     else if (key == ACTION_MENUGREEN)
    459         action = 101;
    460     else if (key == ACTION_MENUYELLOW)
    461         action = 102;
    462     else if (key == ACTION_MENUBLUE)
    463         action = 103;
    464     else if (key == ACTION_MENUTEXT)
    465         action = m_keyProfile > 12 ? 105 : 104;
    466     else if (key == ACTION_MENUEPG)
    467         action = m_keyProfile > 12 ? 300 : 0;
    468 
    469     if (action != 0)
    470     {
    471         m_keyQueue.enqueue(action);
    472         LOG(VB_GENERAL, LOG_INFO, QString("Adding MHEG key %1:%2:%3").arg(key)
    473                 .arg(action).arg(m_keyQueue.size()));
    474         locker.unlock();
    475         QMutexLocker locker2(&m_runLock);
    476         m_engine_wait.wakeAll();
    477         return true;
    478     }
    479 
    480     return false;
     534// Called from tv_play when a key is pressed.
     535// If it is one in the current profile we queue it for the engine
     536// and return true otherwise we return false.
     537bool MHIContext::OfferKey(QString key)
     538{
     539    static const MHKeyLookup s_keymap;
     540    int action = s_keymap.Find(key, m_keyProfile);
     541    if (action == 0)
     542        return false;
     543 
     544    LOG(VB_GENERAL, LOG_INFO, QString("[mhi] Adding MHEG key %1:%2:%3")
     545        .arg(key).arg(action).arg(m_keyQueue.size()) );
     546    { QMutexLocker locker(&m_keyLock);
     547    m_keyQueue.enqueue(action);}
     548    QMutexLocker locker2(&m_runLock);
     549    m_engine_wait.wakeAll();
     550    return true;
    481551}
    482552
    483553void MHIContext::Reinit(const QRect &display)
    void MHIContext::Reinit(const QRect &display) 
    492562
    493563void MHIContext::SetInputRegister(int num)
    494564{
     565    LOG(VB_MHEG, LOG_INFO, QString("[mhi] SetInputRegister %1").arg(num));
    495566    QMutexLocker locker(&m_keyLock);
    496567    m_keyQueue.clear();
    497568    m_keyProfile = num;
    498569}
    499570
     571int MHIContext::GetICStatus()
     572{
     573   // 0= Active, 1= Inactive, 2= Disabled
     574    return m_ic.status();
     575}
    500576
    501577// Called by the video player to redraw the image.
    502578void MHIContext::UpdateOSD(InteractiveScreen *osdWindow,
    int MHIContext::GetChannelIndex(const QString &str) 
    701777            nResult = query.value(0).toInt();
    702778    }
    703779    else if (str == "rec://svc/cur")
    704         nResult = m_currentStream;
     780        nResult = m_currentStream > 0 ? m_currentStream : m_currentChannel;
    705781    else if (str == "rec://svc/def")
    706782        nResult = m_currentChannel;
    707783    else if (str.startsWith("rec://"))
    int MHIContext::GetChannelIndex(const QString &str) 
    717793bool MHIContext::GetServiceInfo(int channelId, int &netId, int &origNetId,
    718794                                int &transportId, int &serviceId)
    719795{
    720     LOG(VB_MHEG, LOG_INFO, QString("[mhi] GetServiceInfo %1").arg(channelId));
    721796    MSqlQuery query(MSqlQuery::InitCon());
    722797    query.prepare("SELECT networkid, transportid, serviceid "
    723798                  "FROM channel, dtv_multiplex "
    bool MHIContext::GetServiceInfo(int channelId, int &netId, int &origNetId, 
    730805        origNetId = netId; // We don't have this in the database.
    731806        transportId = query.value(1).toInt();
    732807        serviceId = query.value(2).toInt();
     808        LOG(VB_MHEG, LOG_INFO, QString("[mhi] GetServiceInfo %1 => NID=%2 TID=%3 SID=%4")
     809            .arg(channelId).arg(netId).arg(transportId).arg(serviceId));
    733810        return true;
    734811    }
    735     else return false;
     812
     813    LOG(VB_MHEG, LOG_WARNING, QString("[mhi] GetServiceInfo %1 failed").arg(channelId));
     814    return false;
    736815}
    737816
    738817bool MHIContext::TuneTo(int channel, int tuneinfo)
    739818{
    740     LOG(VB_MHEG, LOG_INFO, QString("[mhi] TuneTo %1 0x%2")
    741         .arg(channel).arg(tuneinfo,0,16));
    742 
    743819    if (!m_isLive)
     820    {
     821        LOG(VB_MHEG, LOG_WARNING, QString("[mhi] Can't TuneTo %1 0x%2 while not live")
     822            .arg(channel).arg(tuneinfo,0,16));
    744823        return false; // Can't tune if this is a recording.
     824    }
    745825
     826    LOG(VB_GENERAL, LOG_INFO, QString("[mhi] TuneTo %1 0x%2")
     827        .arg(channel).arg(tuneinfo,0,16));
    746828    m_tuneinfo.append(tuneinfo);
    747829
    748830    // Post an event requesting a channel change.
    bool MHIContext::TuneTo(int channel, int tuneinfo) 
    755837    return true;
    756838}
    757839
    758 // Begin playing audio from the specified stream
    759 bool MHIContext::BeginAudio(const QString &stream, int tag)
     840
     841// Begin playing the specified stream
     842bool MHIContext::BeginStream(const QString &stream, MHStream *notify)
    760843{
    761     LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginAudio %1 %2").arg(stream).arg(tag));
     844    LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginStream %1 0x%2")
     845        .arg(stream).arg((quintptr)notify,0,16));
     846
     847    m_audioTag = -1;
     848    m_videoTag = -1;
     849    m_notify = notify;
     850
     851    if (stream.startsWith("http://") || stream.startsWith("https://"))
     852    {
     853        m_currentStream = -1;
     854
     855        // The url is sometimes only http:// during stream startup
     856        if (QUrl(stream).authority().isEmpty())
     857            return false;
     858
     859        return m_parent->GetNVP()->SetStream(stream);
     860    }
    762861
    763862    int chan = GetChannelIndex(stream);
    764863    if (chan < 0)
    765864        return false;
    766 
     865    if (VERBOSE_LEVEL_CHECK(VB_MHEG, LOG_ANY))
     866    {
     867        int netId, origNetId, transportId, serviceId;
     868        GetServiceInfo(chan, netId, origNetId, transportId, serviceId);
     869    }
     870 
    767871    if (chan != m_currentStream)
    768872    {
    769         // We have to tune to the channel where the audio is to be found.
     873        // We have to tune to the channel where the stream is to be found.
    770874        // Because the audio and video are both components of an MHEG stream
    771875        // they will both be on the same channel.
    772876        m_currentStream = chan;
    773         m_audioTag = tag;
    774877        return TuneTo(chan, kTuneKeepChnl|kTuneQuietly|kTuneKeepApp);
    775878    }
     879 
     880    return true;
     881}
    776882
    777     if (tag < 0)
    778         return true; // Leave it at the default.
    779     else if (m_parent->GetNVP())
    780         return m_parent->GetNVP()->SetAudioByComponentTag(tag);
    781     else
     883void MHIContext::EndStream()
     884{
     885    LOG(VB_MHEG, LOG_INFO, QString("[mhi] EndStream 0x%1")
     886        .arg((quintptr)m_notify,0,16) );
     887
     888    m_notify = 0;
     889    (void)m_parent->GetNVP()->SetStream(QString());
     890}
     891
     892// Callback from MythPlayer when a stream starts or stops
     893bool MHIContext::StreamStarted(bool bStarted)
     894{
     895    if (!m_engine || !m_notify)
    782896        return false;
     897
     898    LOG(VB_MHEG, LOG_INFO, QString("[mhi] Stream 0x%1 %2")
     899        .arg((quintptr)m_notify,0,16).arg(bStarted ? "started" : "stopped"));
     900
     901    m_engine->StreamStarted(m_notify, bStarted);
     902    if (!bStarted)
     903        m_notify = 0;
     904    return m_currentStream == -1; // Return true if it's an http stream
    783905}
    784906
     907// Begin playing audio
     908bool MHIContext::BeginAudio(int tag)
     909{
     910    LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginAudio %1").arg(tag));
     911
     912    if (tag < 0)
     913        return true; // Leave it at the default.
     914
     915    m_audioTag = tag;
     916    if (m_parent->GetNVP())
     917        return m_parent->GetNVP()->SetAudioByComponentTag(tag);
     918    return false;
     919 }
     920 
    785921// Stop playing audio
    786 void MHIContext::StopAudio(void)
     922void MHIContext::StopAudio()
    787923{
    788924    // Do nothing at the moment.
    789925}
    790 
     926 
    791927// Begin displaying video from the specified stream
    792 bool MHIContext::BeginVideo(const QString &stream, int tag)
     928bool MHIContext::BeginVideo(int tag)
    793929{
    794     LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginVideo %1 %2").arg(stream).arg(tag));
     930    LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginVideo %1").arg(tag));
    795931
    796     int chan = GetChannelIndex(stream);
    797     if (chan < 0)
    798         return false;
    799     if (chan != m_currentStream)
    800     {
    801         // We have to tune to the channel where the video is to be found.
    802         m_currentStream = chan;
    803         m_videoTag = tag;
    804         return TuneTo(chan, kTuneKeepChnl|kTuneQuietly|kTuneKeepApp);
    805     }
    806932    if (tag < 0)
    807933        return true; // Leave it at the default.
    808     else if (m_parent->GetNVP())
     934 
     935    m_videoTag = tag;
     936    if (m_parent->GetNVP())
    809937        return m_parent->GetNVP()->SetVideoByComponentTag(tag);
    810 
    811938    return false;
    812939}
    813 
    814 // Stop displaying video
    815 void MHIContext::StopVideo(void)
     940 
     941 // Stop displaying video
     942void MHIContext::StopVideo()
    816943{
    817944    // Do nothing at the moment.
    818945}
     946 
     947// Get current stream position, -1 if unknown
     948long MHIContext::GetStreamPos()
     949{
     950    return m_parent->GetNVP() ? m_parent->GetNVP()->GetStreamPos() : -1;
     951}
     952
     953// Get current stream size, -1 if unknown
     954long MHIContext::GetStreamMaxPos()
     955{
     956    return m_parent->GetNVP() ? m_parent->GetNVP()->GetStreamMaxPos() : -1;
     957}
     958
     959// Set current stream position
     960long MHIContext::SetStreamPos(long pos)
     961{
     962    return m_parent->GetNVP() ? m_parent->GetNVP()->SetStreamPos(pos) : -1;
     963}
     964
     965// Play or pause a stream
     966void MHIContext::StreamPlay(bool play)
     967{
     968    if (m_parent->GetNVP())
     969        m_parent->GetNVP()->StreamPlay(play);
     970}
    819971
    820972// Create a new object to draw dynamic line art.
    821973MHDLADisplay *MHIContext::CreateDynamicLineArt(
    void MHIContext::DrawRect(int xPos, int yPos, int width, int height, 
    8671019// and usually that will be the same as the origin of the bounding
    8681020// box (clipRect).
    8691021void MHIContext::DrawImage(int x, int y, const QRect &clipRect,
    870                            const QImage &qImage)
     1022                           const QImage &qImage, bool bScaled /* = false */)
    8711023{
    8721024    if (qImage.isNull())
    8731025        return;
    8741026
    8751027    QRect imageRect(x, y, qImage.width(), qImage.height());
    876     QRect displayRect = QRect(clipRect.x(), clipRect.y(),
    877                               clipRect.width(), clipRect.height()) & imageRect;
     1028    QRect displayRect = clipRect & imageRect;
    8781029
    879     if (displayRect == imageRect) // No clipping required
     1030    if (bScaled || displayRect == imageRect) // No clipping required
    8801031    {
    8811032        QImage q_scaled =
    8821033            qImage.scaled(
    void MHIContext::DrawImage(int x, int y, const QRect &clipRect, 
    8901041    else if (!displayRect.isEmpty())
    8911042    { // We must clip the image.
    8921043        QImage clipped = qImage.convertToFormat(QImage::Format_ARGB32)
    893             .copy(displayRect.x() - x, displayRect.y() - y,
    894                   displayRect.width(), displayRect.height());
     1044            .copy(displayRect.translated(-x, -y));
    8951045        QImage q_scaled =
    8961046            clipped.scaled(
    8971047                SCALED_X(displayRect.width()),
    void MHIBitmap::Draw(int x, int y, QRect rect, bool tiled) 
    14711621                tiledImage.setPixel(i, j, m_image.pixel(i % m_image.width(), j % m_image.height()));
    14721622            }
    14731623        }
    1474         m_parent->DrawImage(rect.x(), rect.y(), rect, tiledImage);
     1624        m_parent->DrawImage(rect.x(), rect.y(), rect, tiledImage, true);
    14751625    }
    14761626    else
    14771627    {
    1478         m_parent->DrawImage(x, y, rect, m_image);
     1628        // NB THe BBC expects bitmaps to be scaled, not clipped
     1629        m_parent->DrawImage(x, y, rect, m_image, true);
    14791630    }
    14801631}
    14811632
    void MHIBitmap::CreateFromPNG(const unsigned char *data, int length) 
    14941645    m_opaque = ! m_image.hasAlphaChannel();
    14951646}
    14961647
     1648// Create a bitmap from JPEG.
     1649//virtual
     1650void MHIBitmap::CreateFromJPEG(const unsigned char *data, int length)
     1651{
     1652    m_image = QImage();
     1653
     1654    if (!m_image.loadFromData(data, length, "JPG"))
     1655    {
     1656        m_image = QImage();
     1657        return;
     1658    }
     1659
     1660    // Assume that if it has an alpha buffer then it's partly transparent.
     1661    m_opaque = ! m_image.hasAlphaChannel();
     1662}
     1663
    14971664// Convert an MPEG I-frame into a bitmap.  This is used as the way of
    14981665// sending still pictures.  We convert the image to a QImage even
    14991666// 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 36341f0..e54023b 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 5e93d4f..b5e32d2 100644
    a b using namespace std; 
    5858#include "mythimage.h"
    5959#include "mythuiimage.h"
    6060#include "mythlogging.h"
     61#include "icringbuffer.h"
    6162
    6263extern "C" {
    6364#include "vbitext/vbi.h"
    int MythPlayer::OpenFile(uint retries) 
    876877    if (!player_ctx || !player_ctx->buffer)
    877878        return -1;
    878879
    879     livetv = player_ctx->tvchain;
     880    livetv = player_ctx->tvchain && player_ctx->buffer->LiveMode();
    880881
    881882    if (player_ctx->tvchain &&
    882883        player_ctx->tvchain->GetCardType(player_ctx->tvchain->GetCurPos()) ==
    void MythPlayer::DisplayPauseFrame(void) 
    20012002    SetBuffering(false);
    20022003
    20032004    RefreshPauseFrame();
     2005    PreProcessNormalFrame(); // Allow interactiveTV to draw on pause frame
    20042006
    20052007    osdLock.lock();
    20062008    videofiltersLock.lock();
    void MythPlayer::SwitchToProgram(void) 
    24182420        return;
    24192421    }
    24202422
     2423    if (player_ctx->buffer->GetType() == ICRingBuffer::kRingBufferType)
     2424    {
     2425        // Restore original ringbuffer
     2426        ICRingBuffer *ic = dynamic_cast< ICRingBuffer* >(player_ctx->buffer);
     2427        player_ctx->buffer = ic->Take();
     2428        delete ic;
     2429    }
     2430
    24212431    player_ctx->buffer->OpenFile(
    24222432        pginfo->GetPlaybackURL(), RingBuffer::kLiveTVOpenTimeout);
    24232433
    void MythPlayer::JumpToProgram(void) 
    25252535
    25262536    SendMythSystemPlayEvent("PLAY_CHANGED", pginfo);
    25272537
     2538    if (player_ctx->buffer->GetType() == ICRingBuffer::kRingBufferType)
     2539    {
     2540        // Restore original ringbuffer
     2541        ICRingBuffer *ic = dynamic_cast< ICRingBuffer* >(player_ctx->buffer);
     2542        player_ctx->buffer = ic->Take();
     2543        delete ic;
     2544    }
     2545
    25282546    player_ctx->buffer->OpenFile(
    25292547        pginfo->GetPlaybackURL(), RingBuffer::kLiveTVOpenTimeout);
    25302548
    void MythPlayer::EventLoop(void) 
    27172735        JumpToProgram();
    27182736    }
    27192737
     2738    // Change interactive stream if requested
     2739    { QMutexLocker locker(&itvLock);
     2740    if (!m_newStream.isEmpty())
     2741    {
     2742        QString stream = m_newStream;
     2743        m_newStream.clear();
     2744        locker.unlock();
     2745        JumpToStream(stream);
     2746    }}
     2747
    27202748    // Disable fastforward if we are too close to the end of the buffer
    27212749    if (ffrew_skip > 1 && (CalcMaxFFTime(100, false) < 100))
    27222750    {
    void MythPlayer::EventLoop(void) 
    27532781    }
    27542782
    27552783    // Handle end of file
    2756     if (GetEof())
     2784    if (GetEof() && !allpaused)
    27572785    {
    2758         if (player_ctx->tvchain)
     2786#ifdef USING_MHEG
     2787        if (interactiveTV && interactiveTV->StreamStarted(false))
    27592788        {
    2760             if (!allpaused && player_ctx->tvchain->HasNext())
    2761             {
    2762                 LOG(VB_GENERAL, LOG_NOTICE, LOC + "LiveTV forcing JumpTo 1");
    2763                 player_ctx->tvchain->JumpToNext(true, 1);
    2764                 return;
    2765             }
     2789            Pause();
     2790            return;
    27662791        }
    2767         else if (!allpaused)
     2792#endif
     2793        if (player_ctx->tvchain && player_ctx->tvchain->HasNext())
    27682794        {
    2769             SetPlaying(false);
     2795            LOG(VB_GENERAL, LOG_NOTICE, LOC + "LiveTV forcing JumpTo 1");
     2796            player_ctx->tvchain->JumpToNext(true, 1);
    27702797            return;
    27712798        }
     2799
     2800        SetPlaying(false);
     2801        return;
    27722802    }
    27732803
    27742804    // Handle rewind
    bool MythPlayer::SetVideoByComponentTag(int tag) 
    47924822    return false;
    47934823}
    47944824
     4825static inline double SafeFPS(DecoderBase *decoder)
     4826{
     4827    if (!decoder)
     4828        return 25;
     4829    double fps = decoder->GetFPS();
     4830    return fps > 0 ? fps : 25.0;
     4831}
     4832
     4833// Called from MHIContext::Begin/End/Stream on the MHIContext::StartMHEGEngine thread
     4834bool MythPlayer::SetStream(const QString &stream)
     4835{
     4836    // The stream name is empty if the stream is closing
     4837    LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("SetStream '%1'").arg(stream));
     4838
     4839    QMutexLocker locker(&itvLock);
     4840    m_newStream = stream;
     4841    m_newStream.detach();
     4842    // Stream will be changed by JumpToStream called from EventLoop
     4843    // If successful will call interactiveTV->StreamStarted();
     4844
     4845    if (stream.isEmpty() && player_ctx->tvchain &&
     4846        player_ctx->buffer->GetType() == ICRingBuffer::kRingBufferType)
     4847    {
     4848        // Restore livetv
     4849        SetEof(true);
     4850        player_ctx->tvchain->JumpToNext(false, 1);
     4851        player_ctx->tvchain->JumpToNext(true, 1);
     4852    }
     4853
     4854    return !stream.isEmpty();
     4855}
     4856
     4857// Called from EventLoop pn the main application thread
     4858void MythPlayer::JumpToStream(const QString &stream)
     4859{
     4860    LOG(VB_PLAYBACK, LOG_INFO, LOC + "JumpToStream - begin");
     4861
     4862    if (stream.isEmpty())
     4863        return; // Shouldn't happen
     4864
     4865    Pause();
     4866    ResetCaptions();
     4867
     4868    ProgramInfo pginfo(stream);
     4869    SetPlayingInfo(pginfo);
     4870
     4871    if (player_ctx->buffer->GetType() != ICRingBuffer::kRingBufferType)
     4872        player_ctx->buffer = new ICRingBuffer(stream, player_ctx->buffer);
     4873    else
     4874        player_ctx->buffer->OpenFile(stream);
     4875
     4876    if (!player_ctx->buffer->IsOpen())
     4877    {
     4878        LOG(VB_GENERAL, LOG_ERR, LOC + "JumpToStream buffer OpenFile failed");
     4879        SetEof(true);
     4880        SetErrored(QObject::tr("Error opening remote stream buffer"));
     4881        return;
     4882    }
     4883
     4884    watchingrecording = false;
     4885    totalLength = 0;
     4886    totalFrames = 0;
     4887    totalDuration = 0;
     4888
     4889    if (OpenFile(120) < 0) // 120 retries ~= 60 seconds
     4890    {
     4891        LOG(VB_GENERAL, LOG_ERR, LOC + "JumpToStream OpenFile failed.");
     4892        SetEof(true);
     4893        SetErrored(QObject::tr("Error opening remote stream"));
     4894        return;
     4895    }
     4896
     4897    if (totalLength == 0)
     4898    {
     4899        long long len = player_ctx->buffer->GetRealFileSize();
     4900        totalLength = (int)(len / ((decoder->GetRawBitrate() * 1000) / 8));
     4901        totalFrames = (int)(totalLength * SafeFPS(decoder));
     4902    }
     4903    LOG(VB_PLAYBACK, LOG_INFO, LOC +
     4904        QString("JumpToStream length %1 bytes @ %2 Kbps = %3 Secs, %4 frames @ %5 fps")
     4905        .arg(player_ctx->buffer->GetRealFileSize()).arg(decoder->GetRawBitrate())
     4906        .arg(totalLength).arg(totalFrames).arg(decoder->GetFPS()) );
     4907
     4908    SetEof(false);
     4909
     4910    // the bitrate is reset by player_ctx->buffer->OpenFile()...
     4911    player_ctx->buffer->UpdateRawBitrate(decoder->GetRawBitrate());
     4912    decoder->SetProgramInfo(pginfo);
     4913
     4914    Play();
     4915    ChangeSpeed();
     4916
     4917    player_ctx->SetPlayerChangingBuffers(false);
     4918#ifdef USING_MHEG
     4919    if (interactiveTV) interactiveTV->StreamStarted();
     4920#endif
     4921
     4922    LOG(VB_PLAYBACK, LOG_INFO, LOC + "JumpToStream - end");
     4923}
     4924
     4925long MythPlayer::GetStreamPos()
     4926{
     4927    return (long)((1000 * GetFramesPlayed()) / SafeFPS(decoder));
     4928}
     4929
     4930long MythPlayer::GetStreamMaxPos()
     4931{
     4932    long maxpos = (long)(1000 * (totalDuration > 0 ? totalDuration : totalLength));
     4933    long pos = GetStreamPos();
     4934    return maxpos > pos ? maxpos : pos;
     4935}
     4936
     4937long MythPlayer::SetStreamPos(long ms)
     4938{
     4939    uint64_t frameNum = (uint64_t)((ms * SafeFPS(decoder)) / 1000);
     4940    LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("SetStreamPos %1 mS = frame %2, now=%3")
     4941        .arg(ms).arg(frameNum).arg(GetFramesPlayed()) );
     4942    JumpToFrame(frameNum);
     4943    return ms;
     4944}
     4945
     4946void MythPlayer::StreamPlay(bool play)
     4947{
     4948    if (play)
     4949        Play();
     4950    else
     4951        Pause();
     4952}
     4953
    47954954/** \fn MythPlayer::SetDecoder(DecoderBase*)
    47964955 *  \brief Sets the stream decoder, deleting any existing recorder.
    47974956 */
  • mythtv/libs/libmythtv/mythplayer.h

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

    diff --git a/mythtv/libs/libmythtv/ringbuffer.h b/mythtv/libs/libmythtv/ringbuffer.h
    index 45bd956..cbe9384 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(); }