Ticket #10019: 0009-freemheg-Add-InteractionChannel-streaming-from-netwo.patch

File 0009-freemheg-Add-InteractionChannel-streaming-from-netwo.patch, 97.5 KB (added by Lawrence Rust <lvr@…>, 22 months ago)
  • mythtv/libs/libmythfreemheg/Actions.cpp

    From 1def8cf26559ab5fc5d0e55f4f745254783b4838 Mon Sep 17 00:00:00 2001
    From: Lawrence Rust <lvr@softsystem.co.uk>
    Date: Wed, 27 Jul 2011 13:05:59 +0200
    Subject: [PATCH 9/9] freemheg: Add InteractionChannel streaming from network URI's
    
    This patch adds BBC iPlayer functionality to the MHEG library.
    
    NB This patch must be applied in conjunction with that to MythPlayer
    which supports Interactive TV program streams.
    
    Signed-off-by: Lawrence Rust <lvr@softsystem.co.uk>
    ---
     mythtv/libs/libmythfreemheg/Actions.cpp     |   18 +-
     mythtv/libs/libmythfreemheg/BaseClasses.cpp |    2 +
     mythtv/libs/libmythfreemheg/BaseClasses.h   |    2 +-
     mythtv/libs/libmythfreemheg/Engine.cpp      |  231 +++++---
     mythtv/libs/libmythfreemheg/Engine.h        |    1 +
     mythtv/libs/libmythfreemheg/Presentable.h   |    1 -
     mythtv/libs/libmythfreemheg/Programs.cpp    |  116 ++++-
     mythtv/libs/libmythfreemheg/Root.cpp        |    2 +-
     mythtv/libs/libmythfreemheg/Root.h          |    4 +
     mythtv/libs/libmythfreemheg/Stream.cpp      |  169 +++----
     mythtv/libs/libmythfreemheg/Stream.h        |   66 +++-
     mythtv/libs/libmythfreemheg/freemheg.h      |   34 +-
     mythtv/libs/libmythtv/libmythtv.pro         |    4 +
     mythtv/libs/libmythtv/mhegic.cpp            |  183 +++++++
     mythtv/libs/libmythtv/mhegic.h              |   50 ++
     mythtv/libs/libmythtv/mhi.cpp               |  407 ++++++++++-----
     mythtv/libs/libmythtv/mhi.h                 |   34 +-
     mythtv/libs/libmythtv/netstream.cpp         |  781 +++++++++++++++++++++++++++
     mythtv/libs/libmythtv/netstream.h           |  144 +++++
     19 files changed, 1913 insertions(+), 336 deletions(-)
     create mode 100644 mythtv/libs/libmythtv/mhegic.cpp
     create mode 100644 mythtv/libs/libmythtv/mhegic.h
     create mode 100644 mythtv/libs/libmythtv/netstream.cpp
     create mode 100644 mythtv/libs/libmythtv/netstream.h
    
    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/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/freemheg.h

    diff --git a/mythtv/libs/libmythfreemheg/freemheg.h b/mythtv/libs/libmythfreemheg/freemheg.h
    index 327818a..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. 
  • mythtv/libs/libmythtv/libmythtv.pro

    diff --git a/mythtv/libs/libmythtv/libmythtv.pro b/mythtv/libs/libmythtv/libmythtv.pro
    index 8e40e03..e3145ff 100644
    a b using_frontend { 
    390390        SOURCES += dsmcc.cpp                dsmcccache.cpp 
    391391        SOURCES += dsmccbiop.cpp            dsmccobjcarousel.cpp 
    392392 
     393         # MHEG interaction channel 
     394        HEADERS += mhegic.h                 netstream.h 
     395        SOURCES += mhegic.cpp               netstream.cpp 
     396 
    393397        # MHEG/MHI stuff 
    394398        HEADERS += interactivetv.h          mhi.h 
    395399        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 d268fb4..61b4cf3 100644
    a b  
     1#include "mhi.h" 
     2 
    13#include <unistd.h> 
    24 
    35#include <QRegion> 
    46#include <QBitArray> 
    57#include <QVector> 
     8#include <QUrl> 
    69 
    7 #include "mhi.h" 
    810#include "interactivescreen.h" 
    911#include "mythpainter.h" 
    1012#include "mythimage.h" 
    void MHIContext::NetworkBootRequested(void) 
    358360// Called by the engine to check for the presence of an object in the carousel. 
    359361bool MHIContext::CheckCarouselObject(QString objectPath) 
    360362{ 
     363    if (objectPath.startsWith("http:") || objectPath.startsWith("https:")) 
     364    { 
     365        // TODO verify access to server in carousel file auth.servers 
     366        // TODO use TLS cert from carousel auth.tls.<x> 
     367        return m_ic.CheckFile(objectPath); 
     368    } 
     369 
    361370    QStringList path = objectPath.split(QChar('/'), QString::SkipEmptyParts); 
    362371    QByteArray result; // Unused 
    363372    int res = m_dsmcc->GetDSMCCObject(path, result); 
    bool MHIContext::CheckCarouselObject(QString objectPath) 
    367376// Called by the engine to request data from the carousel. 
    368377bool MHIContext::GetCarouselData(QString objectPath, QByteArray &result) 
    369378{ 
     379    bool const isIC = objectPath.startsWith("http:") || objectPath.startsWith("https:"); 
     380 
    370381    // Get the path components.  The string will normally begin with "//" 
    371382    // since this is an absolute path but that will be removed by split. 
    372383    QStringList path = objectPath.split(QChar('/'), QString::SkipEmptyParts); 
    bool MHIContext::GetCarouselData(QString objectPath, QByteArray &result) 
    375386    // the result. 
    376387 
    377388    QMutexLocker locker(&m_runLock); 
     389    bool bReported = false; 
    378390    QTime t; t.start(); 
    379391    while (!m_stop) 
    380392    { 
    381393        locker.unlock(); 
    382394 
    383         int res = m_dsmcc->GetDSMCCObject(path, result); 
    384         if (res == 0) 
    385             return true; // Found it 
    386         else if (res < 0) 
    387             return false; // Not there. 
    388         else if (t.elapsed() > 60000) // TODO get this from carousel info 
    389             return false; // Not there. 
     395        if (isIC) 
     396        { 
     397            // TODO verify access to server in carousel file auth.servers 
     398            // TODO use TLS cert from carousel file auth.tls.<x> 
     399            switch (m_ic.GetFile(objectPath, result)) 
     400            { 
     401            case MHInteractionChannel::kSuccess: 
     402                if (bReported) 
     403                    LOG(VB_MHEG, LOG_INFO, QString("[mhi] Received %1").arg(objectPath)); 
     404                return true; 
     405            case MHInteractionChannel::kError: 
     406                if (bReported) 
     407                    LOG(VB_MHEG, LOG_INFO, QString("[mhi] Not found %1").arg(objectPath)); 
     408                return false; 
     409            case MHInteractionChannel::kPending: 
     410                break; 
     411            } 
     412        } 
     413        else 
     414        { 
     415            int res = m_dsmcc->GetDSMCCObject(path, result); 
     416            if (res == 0) 
     417            { 
     418                if (bReported) 
     419                    LOG(VB_MHEG, LOG_INFO, QString("[mhi] Received %1").arg(objectPath)); 
     420                return true; // Found it 
     421            } 
     422            else if (res < 0) 
     423            { 
     424                if (bReported) 
     425                    LOG(VB_MHEG, LOG_INFO, QString("[mhi] Not found %1").arg(objectPath)); 
     426                return false; // Not there. 
     427            } 
     428        } 
     429 
     430        if (t.elapsed() > 60000) // TODO get this from carousel info 
     431             return false; // Not there. 
    390432        // Otherwise we block. 
    391         // Process DSMCC packets then block for a second or until we receive 
     433        if (!bReported) 
     434        { 
     435            bReported = true; 
     436            LOG(VB_MHEG, LOG_INFO, QString("[mhi] Waiting for %1").arg(objectPath)); 
     437        } 
     438        // Process DSMCC packets then block for a while or until we receive 
    392439        // some more packets.  We should eventually find out if this item is 
    393440        // present. 
    394441        ProcessDSMCCQueue(); 
    395442 
    396443        locker.relock(); 
    397         if (!m_stop) 
    398             m_engine_wait.wait(locker.mutex(), 1000); 
     444        m_engine_wait.wait(locker.mutex(), 300); 
    399445    } 
    400446    return false; // Stop has been set.  Say the object isn't present. 
    401447} 
    402448 
    403 // Called from tv_play when a key is pressed. 
    404 // If it is one in the current profile we queue it for the engine 
    405 // and return true otherwise we return false. 
    406 bool MHIContext::OfferKey(QString key) 
     449// Mapping from key name & UserInput register to UserInput EventData 
     450class MHKeyLookup 
    407451{ 
    408     int action = 0; 
    409     QMutexLocker locker(&m_keyLock); 
     452    typedef QPair< QString, int /*UserInput register*/ > key_t; 
     453 
     454public: 
     455    MHKeyLookup(); 
     456 
     457    int Find(const QString &name, int reg) const 
     458        { return m_map.value(key_t(name,reg), 0); } 
    410459 
     460private: 
     461    void key(const QString &name, int code, int r1, 
     462        int r2=0, int r3=0, int r4=0, int r5=0, int r6=0, int r7=0, int r8=0, int r9=0); 
     463 
     464    QHash<key_t,int /*EventData*/ > m_map; 
     465}; 
     466 
     467void MHKeyLookup::key(const QString &name, int code, int r1, 
     468    int r2, int r3, int r4, int r5, int r6, int r7, int r8, int r9) 
     469{ 
     470    m_map.insert(key_t(name,r1), code); 
     471    if (r2 > 0)  
     472        m_map.insert(key_t(name,r2), code); 
     473    if (r3 > 0)  
     474        m_map.insert(key_t(name,r3), code); 
     475    if (r4 > 0)  
     476        m_map.insert(key_t(name,r4), code); 
     477    if (r5 > 0)  
     478        m_map.insert(key_t(name,r5), code); 
     479    if (r6 > 0)  
     480        m_map.insert(key_t(name,r6), code); 
     481    if (r7 > 0)  
     482        m_map.insert(key_t(name,r7), code); 
     483    if (r8 > 0)  
     484        m_map.insert(key_t(name,r8), code); 
     485    if (r9 > 0)  
     486        m_map.insert(key_t(name,r9), code); 
     487} 
     488 
     489MHKeyLookup::MHKeyLookup() 
     490{ 
    411491    // This supports the UK and NZ key profile registers. 
    412492    // The UK uses 3, 4 and 5 and NZ 13, 14 and 15.  These are 
    413493    // similar but the NZ profile also provides an EPG key. 
     494    // ETSI ES 202 184 V2.2.1 (2011-03) adds group 6 for ICE. 
     495    // The BBC use group 7 for ICE 
     496    key(ACTION_UP,           1, 4,5,6,7,14,15); 
     497    key(ACTION_DOWN,         2, 4,5,6,7,14,15); 
     498    key(ACTION_LEFT,         3, 4,5,6,7,14,15); 
     499    key(ACTION_RIGHT,        4, 4,5,6,7,14,15); 
     500    key(ACTION_0,            5, 4,6,7,14); 
     501    key(ACTION_1,            6, 4,6,7,14); 
     502    key(ACTION_2,            7, 4,6,7,14); 
     503    key(ACTION_3,            8, 4,6,7,14); 
     504    key(ACTION_4,            9, 4,6,7,14); 
     505    key(ACTION_5,           10, 4,6,7,14); 
     506    key(ACTION_6,           11, 4,6,7,14); 
     507    key(ACTION_7,           12, 4,6,7,14); 
     508    key(ACTION_8,           13, 4,6,7,14); 
     509    key(ACTION_9,           14, 4,6,7,14); 
     510    key(ACTION_SELECT,      15, 4,5,6,7,14,15); 
     511    key(ACTION_TEXTEXIT,    16, 3,4,5,6,7,13,14,15); // 16= Cancel 
     512    // 17= help 
     513    // 18..99 reserved by DAVIC 
     514    key(ACTION_MENURED,    100, 3,4,5,6,7,13,14,15); 
     515    key(ACTION_MENUGREEN,  101, 3,4,5,6,7,13,14,15); 
     516    key(ACTION_MENUYELLOW, 102, 3,4,5,6,7,13,14,15); 
     517    key(ACTION_MENUBLUE,   103, 3,4,5,6,7,13,14,15); 
     518    key(ACTION_MENUTEXT,   104, 3,4,5,6,7); 
     519    key(ACTION_MENUTEXT,   105, 13,14,15); // NB from original Myth code 
     520    // 105..119 reserved for future spec 
     521    key(ACTION_STOP,       120, 6,7); 
     522    key(ACTION_PLAY,       121, 6,7); 
     523    key(ACTION_PAUSE,      122, 6,7); 
     524    key(ACTION_JUMPFFWD,   123, 6,7); // 123= Skip Forward 
     525    key(ACTION_JUMPRWND,   124, 6,7); // 124= Skip Back 
     526#if 0 // These conflict with left & right 
     527    key(ACTION_SEEKFFWD,   125, 6,7); // 125= Fast Forward 
     528    key(ACTION_SEEKRWND,   126, 6,7); // 126= Rewind 
     529#endif 
     530    key(ACTION_PLAYBACK,   127, 6,7); 
     531    // 128..256 reserved for future spec 
     532    // 257..299 vendor specific 
     533    key(ACTION_MENUEPG,    300, 13,14,15); 
     534    // 301.. Vendor specific 
     535} 
    414536 
    415     if (key == ACTION_UP) 
    416     { 
    417         if (m_keyProfile == 4 || m_keyProfile == 5 || 
    418             m_keyProfile == 14 || m_keyProfile == 15) 
    419             action = 1; 
    420     } 
    421     else if (key == ACTION_DOWN) 
    422     { 
    423         if (m_keyProfile == 4 || m_keyProfile == 5 || 
    424             m_keyProfile == 14 || m_keyProfile == 15) 
    425             action = 2; 
    426     } 
    427     else if (key == ACTION_LEFT) 
    428     { 
    429         if (m_keyProfile == 4 || m_keyProfile == 5 || 
    430             m_keyProfile == 14 || m_keyProfile == 15) 
    431             action = 3; 
    432     } 
    433     else if (key == ACTION_RIGHT) 
    434     { 
    435         if (m_keyProfile == 4 || m_keyProfile == 5 || 
    436             m_keyProfile == 14 || m_keyProfile == 15) 
    437             action = 4; 
    438     } 
    439     else if (key == ACTION_0 || key == ACTION_1 || key == ACTION_2 || 
    440              key == ACTION_3 || key == ACTION_4 || key == ACTION_5 || 
    441              key == ACTION_6 || key == ACTION_7 || key == ACTION_8 || 
    442              key == ACTION_9) 
    443     { 
    444         if (m_keyProfile == 4 || m_keyProfile == 14) 
    445             action = key.toInt() + 5; 
    446     } 
    447     else if (key == ACTION_SELECT) 
    448     { 
    449         if (m_keyProfile == 4 || m_keyProfile == 5 || 
    450             m_keyProfile == 14 || m_keyProfile == 15) 
    451             action = 15; 
    452     } 
    453     else if (key == ACTION_TEXTEXIT) 
    454         action = 16; 
    455     else if (key == ACTION_MENURED) 
    456         action = 100; 
    457     else if (key == ACTION_MENUGREEN) 
    458         action = 101; 
    459     else if (key == ACTION_MENUYELLOW) 
    460         action = 102; 
    461     else if (key == ACTION_MENUBLUE) 
    462         action = 103; 
    463     else if (key == ACTION_MENUTEXT) 
    464         action = m_keyProfile > 12 ? 105 : 104; 
    465     else if (key == ACTION_MENUEPG) 
    466         action = m_keyProfile > 12 ? 300 : 0; 
    467  
    468     if (action != 0) 
    469     { 
    470         m_keyQueue.enqueue(action); 
    471         LOG(VB_GENERAL, LOG_INFO, QString("Adding MHEG key %1:%2:%3").arg(key) 
    472                 .arg(action).arg(m_keyQueue.size())); 
    473         locker.unlock(); 
    474         QMutexLocker locker2(&m_runLock); 
    475         m_engine_wait.wakeAll(); 
    476         return true; 
    477     } 
    478  
    479     return false; 
     537// Called from tv_play when a key is pressed. 
     538// If it is one in the current profile we queue it for the engine 
     539// and return true otherwise we return false. 
     540bool MHIContext::OfferKey(QString key) 
     541{ 
     542    static const MHKeyLookup s_keymap; 
     543    int action = s_keymap.Find(key, m_keyProfile); 
     544    if (action == 0) 
     545        return false; 
     546  
     547    LOG(VB_GENERAL, LOG_INFO, QString("[mhi] Adding MHEG key %1:%2:%3") 
     548        .arg(key).arg(action).arg(m_keyQueue.size()) ); 
     549    { QMutexLocker locker(&m_keyLock); 
     550    m_keyQueue.enqueue(action);} 
     551    QMutexLocker locker2(&m_runLock); 
     552    m_engine_wait.wakeAll(); 
     553    return true; 
    480554} 
    481555 
    482556void MHIContext::Reinit(const QRect &display) 
    void MHIContext::Reinit(const QRect &display) 
    491565 
    492566void MHIContext::SetInputRegister(int num) 
    493567{ 
     568    LOG(VB_MHEG, LOG_INFO, QString("[mhi] SetInputRegister %1").arg(num)); 
    494569    QMutexLocker locker(&m_keyLock); 
    495570    m_keyQueue.clear(); 
    496571    m_keyProfile = num; 
    497572} 
    498573 
     574int MHIContext::GetICStatus() 
     575{ 
     576   // 0= Active, 1= Inactive, 2= Disabled 
     577    return m_ic.status(); 
     578} 
    499579 
    500580// Called by the video player to redraw the image. 
    501581void MHIContext::UpdateOSD(InteractiveScreen *osdWindow, 
    int MHIContext::GetChannelIndex(const QString &str) 
    700780            nResult = query.value(0).toInt(); 
    701781    } 
    702782    else if (str == "rec://svc/cur") 
    703         nResult = m_currentStream; 
     783        nResult = m_currentStream > 0 ? m_currentStream : m_currentChannel; 
    704784    else if (str == "rec://svc/def") 
    705785        nResult = m_currentChannel; 
    706786    else if (str.startsWith("rec://")) 
    int MHIContext::GetChannelIndex(const QString &str) 
    716796bool MHIContext::GetServiceInfo(int channelId, int &netId, int &origNetId, 
    717797                                int &transportId, int &serviceId) 
    718798{ 
    719     LOG(VB_MHEG, LOG_INFO, QString("[mhi] GetServiceInfo %1").arg(channelId)); 
    720799    MSqlQuery query(MSqlQuery::InitCon()); 
    721800    query.prepare("SELECT networkid, transportid, serviceid " 
    722801                  "FROM channel, dtv_multiplex " 
    bool MHIContext::GetServiceInfo(int channelId, int &netId, int &origNetId, 
    729808        origNetId = netId; // We don't have this in the database. 
    730809        transportId = query.value(1).toInt(); 
    731810        serviceId = query.value(2).toInt(); 
     811        LOG(VB_MHEG, LOG_INFO, QString("[mhi] GetServiceInfo %1 => NID=%2 TID=%3 SID=%4") 
     812            .arg(channelId).arg(netId).arg(transportId).arg(serviceId)); 
    732813        return true; 
    733814    } 
    734     else return false; 
     815 
     816    LOG(VB_MHEG, LOG_WARNING, QString("[mhi] GetServiceInfo %1 failed").arg(channelId)); 
     817    return false; 
    735818} 
    736819 
    737820bool MHIContext::TuneTo(int channel, int tuneinfo) 
    738821{ 
    739     LOG(VB_MHEG, LOG_INFO, QString("[mhi] TuneTo %1 0x%2") 
    740         .arg(channel).arg(tuneinfo,0,16)); 
    741  
    742822    if (!m_isLive) 
     823    { 
     824        LOG(VB_MHEG, LOG_WARNING, QString("[mhi] Can't TuneTo %1 0x%2 while not live") 
     825            .arg(channel).arg(tuneinfo,0,16)); 
    743826        return false; // Can't tune if this is a recording. 
     827    } 
    744828 
     829    LOG(VB_GENERAL, LOG_INFO, QString("[mhi] TuneTo %1 0x%2") 
     830        .arg(channel).arg(tuneinfo,0,16)); 
    745831    m_tuneinfo.append(tuneinfo); 
    746832 
    747833    // Post an event requesting a channel change. 
    bool MHIContext::TuneTo(int channel, int tuneinfo) 
    754840    return true; 
    755841} 
    756842 
    757 // Begin playing audio from the specified stream 
    758 bool MHIContext::BeginAudio(const QString &stream, int tag) 
     843 
     844// Begin playing the specified stream 
     845bool MHIContext::BeginStream(const QString &stream, MHStream *notify) 
    759846{ 
    760     LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginAudio %1 %2").arg(stream).arg(tag)); 
     847    LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginStream %1 0x%2") 
     848        .arg(stream).arg((quintptr)notify,0,16)); 
     849 
     850    m_audioTag = -1; 
     851    m_videoTag = -1; 
     852    m_notify = notify; 
     853 
     854    if (stream.startsWith("http://") || stream.startsWith("https://")) 
     855    { 
     856        m_currentStream = -1; 
     857 
     858        // The url is sometimes only http:// during stream startup 
     859        if (QUrl(stream).authority().isEmpty()) 
     860            return false; 
     861 
     862        return m_parent->GetNVP()->SetStream(stream); 
     863    } 
    761864 
    762865    int chan = GetChannelIndex(stream); 
    763866    if (chan < 0) 
    764867        return false; 
    765  
     868    if (VERBOSE_LEVEL_CHECK(VB_MHEG, LOG_ANY)) 
     869    { 
     870        int netId, origNetId, transportId, serviceId; 
     871        GetServiceInfo(chan, netId, origNetId, transportId, serviceId); 
     872    } 
     873  
    766874    if (chan != m_currentStream) 
    767875    { 
    768         // We have to tune to the channel where the audio is to be found. 
     876        // We have to tune to the channel where the stream is to be found. 
    769877        // Because the audio and video are both components of an MHEG stream 
    770878        // they will both be on the same channel. 
    771879        m_currentStream = chan; 
    772         m_audioTag = tag; 
    773880        return TuneTo(chan, kTuneKeepChnl|kTuneQuietly|kTuneKeepApp); 
    774881    } 
     882  
     883    return true; 
     884} 
    775885 
    776     if (tag < 0) 
    777         return true; // Leave it at the default. 
    778     else if (m_parent->GetNVP()) 
    779         return m_parent->GetNVP()->SetAudioByComponentTag(tag); 
    780     else 
     886void MHIContext::EndStream() 
     887{ 
     888    LOG(VB_MHEG, LOG_INFO, QString("[mhi] EndStream 0x%1") 
     889        .arg((quintptr)m_notify,0,16) ); 
     890 
     891    m_notify = 0; 
     892    (void)m_parent->GetNVP()->SetStream(QString()); 
     893} 
     894 
     895// Callback from MythPlayer when a stream starts or stops 
     896bool MHIContext::StreamStarted(bool bStarted) 
     897{ 
     898    if (!m_engine || !m_notify) 
    781899        return false; 
     900 
     901    LOG(VB_MHEG, LOG_INFO, QString("[mhi] Stream 0x%1 %2") 
     902        .arg((quintptr)m_notify,0,16).arg(bStarted ? "started" : "stopped")); 
     903 
     904    m_engine->StreamStarted(m_notify, bStarted); 
     905    if (!bStarted) 
     906        m_notify = 0; 
     907    return m_currentStream == -1; // Return true if it's an http stream 
    782908} 
    783909 
     910// Begin playing audio 
     911bool MHIContext::BeginAudio(int tag) 
     912{ 
     913    LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginAudio %1").arg(tag)); 
     914 
     915    if (tag < 0) 
     916        return true; // Leave it at the default. 
     917 
     918    m_audioTag = tag; 
     919    if (m_parent->GetNVP()) 
     920        return m_parent->GetNVP()->SetAudioByComponentTag(tag); 
     921    return false; 
     922 } 
     923  
    784924// Stop playing audio 
    785 void MHIContext::StopAudio(void) 
     925void MHIContext::StopAudio() 
    786926{ 
    787927    // Do nothing at the moment. 
    788928} 
    789  
     929  
    790930// Begin displaying video from the specified stream 
    791 bool MHIContext::BeginVideo(const QString &stream, int tag) 
     931bool MHIContext::BeginVideo(int tag) 
    792932{ 
    793     LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginVideo %1 %2").arg(stream).arg(tag)); 
     933    LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginVideo %1").arg(tag)); 
    794934 
    795     int chan = GetChannelIndex(stream); 
    796     if (chan < 0) 
    797         return false; 
    798     if (chan != m_currentStream) 
    799     { 
    800         // We have to tune to the channel where the video is to be found. 
    801         m_currentStream = chan; 
    802         m_videoTag = tag; 
    803         return TuneTo(chan, kTuneKeepChnl|kTuneQuietly|kTuneKeepApp); 
    804     } 
    805935    if (tag < 0) 
    806936        return true; // Leave it at the default. 
    807     else if (m_parent->GetNVP()) 
     937  
     938    m_videoTag = tag; 
     939    if (m_parent->GetNVP()) 
    808940        return m_parent->GetNVP()->SetVideoByComponentTag(tag); 
    809  
    810941    return false; 
    811942} 
    812  
    813 // Stop displaying video 
    814 void MHIContext::StopVideo(void) 
     943  
     944 // Stop displaying video 
     945void MHIContext::StopVideo() 
    815946{ 
    816947    // Do nothing at the moment. 
    817948} 
     949  
     950// Get current stream position, -1 if unknown 
     951long MHIContext::GetStreamPos() 
     952{ 
     953    return m_parent->GetNVP() ? m_parent->GetNVP()->GetStreamPos() : -1; 
     954} 
     955 
     956// Get current stream size, -1 if unknown 
     957long MHIContext::GetStreamMaxPos() 
     958{ 
     959    return m_parent->GetNVP() ? m_parent->GetNVP()->GetStreamMaxPos() : -1; 
     960} 
     961 
     962// Set current stream position 
     963long MHIContext::SetStreamPos(long pos) 
     964{ 
     965    return m_parent->GetNVP() ? m_parent->GetNVP()->SetStreamPos(pos) : -1; 
     966} 
     967 
     968// Play or pause a stream 
     969void MHIContext::StreamPlay(bool play) 
     970{ 
     971    if (m_parent->GetNVP()) 
     972        m_parent->GetNVP()->StreamPlay(play); 
     973} 
    818974 
    819975// Create a new object to draw dynamic line art. 
    820976MHDLADisplay *MHIContext::CreateDynamicLineArt( 
    void MHIContext::DrawRect(int xPos, int yPos, int width, int height, 
    8661022// and usually that will be the same as the origin of the bounding 
    8671023// box (clipRect). 
    8681024void MHIContext::DrawImage(int x, int y, const QRect &clipRect, 
    869                            const QImage &qImage) 
     1025                           const QImage &qImage, bool bScaled /* = false */) 
    8701026{ 
    8711027    if (qImage.isNull()) 
    8721028        return; 
    8731029 
    8741030    QRect imageRect(x, y, qImage.width(), qImage.height()); 
    875     QRect displayRect = QRect(clipRect.x(), clipRect.y(), 
    876                               clipRect.width(), clipRect.height()) & imageRect; 
     1031    QRect displayRect = clipRect & imageRect; 
    8771032 
    878     if (displayRect == imageRect) // No clipping required 
     1033    if (bScaled || displayRect == imageRect) // No clipping required 
    8791034    { 
    8801035        QImage q_scaled = 
    8811036            qImage.scaled( 
    void MHIContext::DrawImage(int x, int y, const QRect &clipRect, 
    8891044    else if (!displayRect.isEmpty()) 
    8901045    { // We must clip the image. 
    8911046        QImage clipped = qImage.convertToFormat(QImage::Format_ARGB32) 
    892             .copy(displayRect.x() - x, displayRect.y() - y, 
    893                   displayRect.width(), displayRect.height()); 
     1047            .copy(displayRect.translated(-x, -y)); 
    8941048        QImage q_scaled = 
    8951049            clipped.scaled( 
    8961050                SCALED_X(displayRect.width()), 
    void MHIBitmap::Draw(int x, int y, QRect rect, bool tiled) 
    14701624                tiledImage.setPixel(i, j, m_image.pixel(i % m_image.width(), j % m_image.height())); 
    14711625            } 
    14721626        } 
    1473         m_parent->DrawImage(rect.x(), rect.y(), rect, tiledImage); 
     1627        m_parent->DrawImage(rect.x(), rect.y(), rect, tiledImage, true); 
    14741628    } 
    14751629    else 
    14761630    { 
    1477         m_parent->DrawImage(x, y, rect, m_image); 
     1631        // NB THe BBC expects bitmaps to be scaled, not clipped 
     1632        m_parent->DrawImage(x, y, rect, m_image, true); 
    14781633    } 
    14791634} 
    14801635 
  • mythtv/libs/libmythtv/mhi.h

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

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