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@…>, 9 years 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 */