Ticket #10019: 0009-freemheg-Add-InteractionChannel-streaming-from-netwo.patch
| File 0009-freemheg-Add-InteractionChannel-streaming-from-netwo.patch, 97.5 KB (added by , 14 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 44 44 class MHUnimplementedAction: public MHElemAction 45 45 { 46 46 public: 47 MHUnimplementedAction(int nTag): MHElemAction("") 47 MHUnimplementedAction(int nTag): MHElemAction(""), m_nTag(nTag) 48 48 { 49 m_nTag = nTag;49 MHLOG(MHLogWarning, QString("WARN Unimplemented action %1").arg(m_nTag) ); 50 50 } 51 51 virtual void Initialise(MHParseNode *, MHEngine *) {} 52 52 virtual void PrintMe(FILE *fd, int /*nTabs*/) const … … void MHActionSequence::Initialise(MHParseNode *p, MHEngine *engine) 297 297 pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); 298 298 break; // Stream 299 299 case C_SET_COUNTER_POSITION: 300 pAction = new MH UnimplementedAction(pElemAction->GetTagNo());300 pAction = new MHSetCounterPosition; 301 301 break; // Stream 302 302 case C_SET_COUNTER_TRIGGER: 303 303 pAction = new MHUnimplementedAction(pElemAction->GetTagNo()); … … void MHActionSequence::Initialise(MHParseNode *p, MHEngine *engine) 357 357 pAction = new MHSetSliderValue; 358 358 break; 359 359 case C_SET_SPEED: 360 pAction = new MH UnimplementedAction(pElemAction->GetTagNo());360 pAction = new MHSetSpeed; 361 361 break; // ? 362 362 case C_SET_TIMER: 363 363 pAction = new MHSetTimer; … … void MHActionSequence::Initialise(MHParseNode *p, MHEngine *engine) 442 442 pAction = new MHSetSliderParameters; 443 443 break; 444 444 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 445 453 default: 446 MHLOG(MHLogWarning, QString(" Unknown action %1").arg(pElemAction->GetTagNo()));454 MHLOG(MHLogWarning, QString("WARN Unknown action %1").arg(pElemAction->GetTagNo())); 447 455 // Future proofing: ignore any actions that we don't know about. 448 456 // Obviously these can only arise in the binary coding. 449 457 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 588 588 } 589 589 else 590 590 { 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 591 593 MHUnion result; 592 594 MHRoot *pBase = engine->FindObject(m_Indirect); 593 595 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 184 184 { 185 185 public: 186 186 MHObjectRef *GetReference(); // Return the indirect reference or fail if it's direct 187 protected:188 187 bool m_fIsDirect; 188 protected: 189 189 MHObjectRef m_Indirect; 190 190 }; 191 191 -
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 32 32 #include "Logging.h" 33 33 #include "freemheg.h" 34 34 #include "Visible.h" // For MHInteractible 35 #include "Stream.h" 35 36 36 37 #include <stdio.h> 37 38 #include <stdlib.h> … … int MHEngine::RunAll() 111 112 112 113 if (! Launch(startObj)) 113 114 { 114 MHLOG(MHLog Warning, "MHEG engine auto-boot failed");115 MHLOG(MHLogNotifications, "NOTE Engine auto-boot failed"); 115 116 return -1; 116 117 } 117 118 } … … MHGroup *MHEngine::ParseProgram(QByteArray &text) 242 243 return pRes; 243 244 } 244 245 245 // Launch and Spawn 246 bool MHEngine::Launch(const MHObjectRef &target, bool fIsSpawn) 246 // Determine protocol for a file 247 enum EProtocol { kProtoUnknown, kProtoDSM, kProtoCI, kProtoHTTP, kProtoHybrid }; 248 static EProtocol PathProtocol(const QString& csPath) 247 249 { 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; 249 258 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 } 254 265 266 // Launch and Spawn 267 bool MHEngine::Launch(const MHObjectRef &target, bool fIsSpawn) 268 { 255 269 if (m_fInTransition) 256 270 { 257 MHLOG(MHLogWarning, " Launch during transition - ignoring");271 MHLOG(MHLogWarning, "WARN Launch during transition - ignoring"); 258 272 return false; 259 273 } 260 274 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. 262 277 263 278 // Check that the file exists before we commit to the transition. 264 279 // This may block if we cannot be sure whether the object is present. 280 QByteArray text; 265 281 if (! m_Context->GetCarouselData(csPath, text)) 266 282 { 267 if (CurrentApp()) 268 { 269 EventTriggered(CurrentApp(), EventEngineEvent, 2); // GroupIDRefError 270 } 271 283 if (!m_fBooting) 284 EngineEvent(2); // GroupIDRefError 272 285 return false; 273 286 } 274 287 … … void MHEngine::Quit() 351 364 { 352 365 if (m_fInTransition) 353 366 { 354 MHLOG(MHLogWarning, " Quit during transition - ignoring");367 MHLOG(MHLogWarning, "WARN Quit during transition - ignoring"); 355 368 return; 356 369 } 357 370 … … void MHEngine::TransitionToScene(const MHObjectRef &target) 395 408 if (m_fInTransition) 396 409 { 397 410 // TransitionTo is not allowed in OnStartUp or OnCloseDown actions. 398 MHLOG(MHLogWarning, " TransitionTo during transition - ignoring");411 MHLOG(MHLogWarning, "WARN TransitionTo during transition - ignoring"); 399 412 return; 400 413 } 401 414 … … void MHEngine::TransitionToScene(const MHObjectRef &target) 405 418 } 406 419 407 420 QString csPath = GetPathName(target.m_GroupId); 408 QByteArray text;409 421 410 422 // 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 414 427 return; 415 428 } 416 429 … … void MHEngine::TransitionToScene(const MHObjectRef &target) 482 495 m_Interacting = 0; 483 496 484 497 // Switch to the new scene. 485 CurrentApp()->m_pCurrentScene = (MHScene *) pProgram;498 CurrentApp()->m_pCurrentScene = static_cast< MHScene* >(pProgram); 486 499 SetInputRegister(CurrentScene()->m_nEventReg); 487 500 m_redrawRegion = QRegion(0, 0, CurrentScene()->m_nSceneCoordX, CurrentScene()->m_nSceneCoordY); // Redraw the whole screen 488 501 … … void MHEngine::SetInputRegister(int nReg) 504 517 // Create a canonical path name. The rules are given in the UK MHEG document. 505 518 QString MHEngine::GetPathName(const MHOctetString &str) 506 519 { 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) 524 521 return QString(); 525 }526 522 527 if (csPath.left(1) == "~") 523 QString csPath = QString::fromUtf8((const char *)str.Bytes(), str.Size()); 524 switch (PathProtocol(csPath)) 528 525 { 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; 530 534 } 531 535 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("//")) 534 541 { 535 542 // Add the current application's path name 536 543 if (CurrentApp()) … … MHRoot *MHEngine::FindObject(const MHObjectRef &oRef, bool failOnNotFound) 589 596 // an object that may or may not exist at a particular time. 590 597 // Another case was a call to CallActionSlot with an object reference variable 591 598 // 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)); 593 600 throw "FindObject failed"; 594 601 } 595 602 … … void MHEngine::RunActions() 609 616 { 610 617 if ((__mhlogoptions & MHLogActions) && __mhlogStream != 0) // Debugging 611 618 { 612 fprintf(__mhlogStream, " Action - ");619 fprintf(__mhlogStream, "[freemheg] Action - "); 613 620 pAction->PrintMe(__mhlogStream, 0); 614 621 fflush(__mhlogStream); 615 622 } … … void MHEngine::EventTriggered(MHRoot *pSource, enum EventType ev, const MHUnion 670 677 case EventUserInput: 671 678 case EventFocusMoved: // UK MHEG. Generated by HyperText class 672 679 case EventSliderValueChanged: // UK MHEG. Generated by Slider class 680 default: 673 681 { 674 682 // Asynchronous events. Add to the event queue. 675 683 MHAsynchEvent *pEvent = new MHAsynchEvent; … … void MHEngine::EventTriggered(MHRoot *pSource, enum EventType ev, const MHUnion 678 686 pEvent->eventData = evData; 679 687 m_EventQueue.enqueue(pEvent); 680 688 } 689 break; 681 690 } 682 691 } 683 692 … … void MHEngine::GenerateUserAction(int nCode) 921 930 case 101: // Green 922 931 case 102: // Yellow 923 932 case 103: // Blue 933 case 300: // EPG 924 934 EventTriggered(pScene, EventEngineEvent, nCode); 925 935 break; 926 936 } … … void MHEngine::GenerateUserAction(int nCode) 939 949 940 950 void MHEngine::EngineEvent(int nCode) 941 951 { 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 958 void MHEngine::StreamStarted(MHStream *stream, bool bStarted) 959 { 960 EventTriggered(stream, bStarted ? EventStreamPlaying : EventStreamStopped); 943 961 } 944 962 945 963 // Called by an ingredient wanting external content. … … void MHEngine::RequestExternalContent(MHIngredient *pRequester) 954 972 955 973 // Remove any existing content requests for this ingredient. 956 974 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 deal960 // with that separately.961 if (csPath.isEmpty())962 {963 MHLOG(MHLogWarning, "RequestExternalContent empty path");964 return;965 }966 975 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)) 970 978 { 971 979 // 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 } 973 999 } 974 1000 else 975 1001 { 976 1002 // 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)) ); 978 1005 MHExternContent *pContent = new MHExternContent; 979 1006 pContent->m_FileName = csPath; 980 1007 pContent->m_pRequester = pRequester; … … void MHEngine::CancelExternalContentRequest(MHIngredient *pRequester) 995 1022 996 1023 if (pContent->m_pRequester == pRequester) 997 1024 { 998 delete pContent; 1025 MHLOG(MHLogNotifications, QString("Cancelled wait for %1") 1026 .arg(pRequester->m_ObjectReference.Printable()) ); 999 1027 it = m_ExternContentTable.erase(it); 1028 delete pContent; 1000 1029 return; 1001 1030 } 1002 1031 else … … void MHEngine::CancelExternalContentRequest(MHIngredient *pRequester) 1009 1038 // See if we can satisfy any of the outstanding requests. 1010 1039 void MHEngine::CheckContentRequests() 1011 1040 { 1012 QList<MHExternContent *>::iterator it = m_ExternContentTable.begin(); 1013 MHExternContent *pContent; 1014 1041 QList<MHExternContent*>::iterator it = m_ExternContentTable.begin(); 1015 1042 while (it != m_ExternContentTable.end()) 1016 1043 { 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)) 1022 1046 { 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)) 1025 1052 { 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 {} 1030 1064 } 1031 catch (char const *)1065 else 1032 1066 { 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 1033 1073 } 1034 1074 1035 // Remove from the list.1036 1075 delete pContent; 1037 it = m_ExternContentTable.erase(it);1038 1076 } 1039 1077 else if (pContent->m_time.elapsed() > 60000) // TODO Get this from carousel 1040 1078 { 1041 MHLOG(MHLogWarning, QString("CheckContentRequests %1 timed out") 1042 .arg(pContent->m_FileName)); 1043 delete pContent; 1079 // Remove from the list. 1044 1080 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; 1046 1091 } 1047 1092 else 1048 1093 { … … bool MHEngine::GetEngineSupport(const MHOctetString &feature) 1121 1166 QString csFeat = QString::fromUtf8((const char *)feature.Bytes(), feature.Size()); 1122 1167 QStringList strings = csFeat.split(QRegExp("[\\(\\,\\)]")); 1123 1168 1169 MHLOG(MHLogNotifications, "NOTE GetEngineSupport " + csFeat); 1170 1124 1171 if (strings[0] == "ApplicationStacking" || strings[0] == "ASt") 1125 1172 { 1126 1173 return true; … … bool MHEngine::GetEngineSupport(const MHOctetString &feature) 1241 1288 // We support bitmaps that are partially off screen (don't we?) 1242 1289 if (strings[0] == "BitmapDecodeOffset" || strings[0] == "BDO") 1243 1290 { 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")) 1245 1296 { 1246 1297 return true; 1247 1298 } … … bool MHEngine::GetEngineSupport(const MHOctetString &feature) 1285 1336 } 1286 1337 } 1287 1338 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 1288 1349 // Otherwise return false. 1289 1350 return false; 1290 1351 } … … FILE *__mhlogStream = NULL; 1442 1503 void __mhlog(QString logtext) 1443 1504 { 1444 1505 QByteArray tmp = logtext.toAscii(); 1445 fprintf(__mhlogStream, " %s\n", tmp.constData());1506 fprintf(__mhlogStream, "[freemheg] %s\n", tmp.constData()); 1446 1507 } 1447 1508 1448 1509 // 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 { 117 117 // Generate a UserAction event i.e. a key press. 118 118 virtual void GenerateUserAction(int nCode); 119 119 virtual void EngineEvent(int nCode); 120 virtual void StreamStarted(MHStream*, bool bStarted); 120 121 121 122 // Called from an ingredient to request a load of external content. 122 123 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 43 43 virtual void Stop(MHEngine *engine); 44 44 45 45 // Additional actions for stream components. 46 virtual void SetStreamRef(MHEngine *, const MHContentRef &) {}47 46 virtual void BeginPlaying(MHEngine *) {} 48 47 virtual void StopPlaying(MHEngine *) {} 49 48 }; -
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 29 29 #include "Logging.h" 30 30 #include "freemheg.h" 31 31 32 #include <QStringList> 32 33 #include <sys/timeb.h> 33 34 #ifdef __FreeBSD__ 34 35 #include <sys/time.h> … … static int GetInt(MHParameter *parm, MHEngine *engine) 138 139 return un.m_nIntVal; 139 140 } 140 141 142 // Return a bool value. May throw an exception if it isn't the correct type. 143 static 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 141 151 // Extract a string value. 142 152 static void GetString(MHParameter *parm, MHOctetString &str, MHEngine *engine) 143 153 { … … void MHResidentProgram::CallProgram(bool fIsFork, const MHObjectRef &success, co 738 748 else if (m_Name.Equal("SSM")) // SetSubtitleMode 739 749 { 740 750 // 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); 742 759 } 743 760 744 761 else if (m_Name.Equal("WAI")) // WhoAmI … … void MHResidentProgram::CallProgram(bool fIsFork, const MHObjectRef &success, co 798 815 799 816 else if (m_Name.Equal("SBI")) // SetBroadcastInterrupt 800 817 { 801 // Required for InteractionChannelExtension818 // Required for NativeApplicationExtension 802 819 // 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); 804 828 } 805 829 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); 810 910 } 811 911 812 912 else … … void MHCall::PrintArgs(FILE *fd, int nTabs) const 908 1008 m_Parameters.GetAt(i)->PrintMe(fd, 0); 909 1009 } 910 1010 911 fprintf(fd, " ) \n");1011 fprintf(fd, " )"); 912 1012 } 913 1013 914 1014 void 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 44 44 // An action was attempted on an object of a class which doesn't support this. 45 45 void MHRoot::InvalidAction(const char *actionName) 46 46 { 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())); 48 48 throw "Invalid Action"; 49 49 } 50 50 -
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 175 175 virtual void ScaleVideo(int /*xScale*/, int /*yScale*/, MHEngine *) { InvalidAction("ScaleVideo"); } 176 176 virtual void SetVideoDecodeOffset(int /*newXOffset*/, int /*newYOffset*/, MHEngine *) { InvalidAction("SetVideoDecodeOffset"); } 177 177 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"); } 178 182 179 183 // Actions on Interactibles. 180 184 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) 65 65 m_Multiplex.Append(pRtGraph); 66 66 pRtGraph->Initialise(pItem, engine); 67 67 } 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 } 70 74 } 71 75 } 72 76 … … void MHStream::Activation(MHEngine *engine) 158 162 MHPresentable::Activation(engine); 159 163 160 164 // 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. 166 167 m_fRunning = true; 167 168 engine->EventTriggered(this, EventIsRunning); 168 169 } … … void MHStream::Deactivation(MHEngine *engine) 174 175 return; 175 176 } 176 177 177 // Stop playing all active Stream components178 for (int i = 0; i < m_Multiplex.Size(); i++)179 {180 m_Multiplex.GetAt(i)->StopPlaying(engine);181 }182 183 178 MHPresentable::Deactivation(engine); 179 StopPlaying(engine); 184 180 } 185 181 186 182 // The MHEG corrigendum allows SetData to be targeted to a stream so … … void MHStream::Deactivation(MHEngine *engine) 188 184 void MHStream::ContentPreparation(MHEngine *engine) 189 185 { 190 186 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); 196 189 } 197 190 198 // TODO: Generate StreamPlaying and StreamStopped events. These are supposed199 // to be generated as the first and last frames are displayed.200 201 191 // Return an object if there is a matching component. 202 192 MHRoot *MHStream::FindByObjectNo(int n) 203 193 { … … MHRoot *MHStream::FindByObjectNo(int n) 219 209 return NULL; 220 210 } 221 211 212 void 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 227 void 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 236 void MHStream::GetCounterPosition(MHRoot *pResult, MHEngine *engine) 237 { 238 // StreamCounterUnits (mS) 239 pResult->SetVariableValue((int)engine->GetContext()->GetStreamPos()); 240 } 241 242 void MHStream::GetCounterMaxPosition(MHRoot *pResult, MHEngine *engine) 243 { 244 // StreamCounterUnits (mS) 245 pResult->SetVariableValue((int)engine->GetContext()->GetStreamMaxPos()); 246 } 247 248 void MHStream::SetCounterPosition(int pos, MHEngine *engine) 249 { 250 // StreamCounterUnits (mS) 251 engine->GetContext()->SetStreamPos(pos); 252 } 253 254 void MHStream::SetSpeed(int speed, MHEngine *engine) 255 { 256 engine->GetContext()->StreamPlay(speed); 257 } 258 222 259 MHAudio::MHAudio() 223 260 { 224 261 m_nComponentTag = 0; … … void MHAudio::Activation(MHEngine *engine) 275 312 m_fRunning = true; 276 313 engine->EventTriggered(this, EventIsRunning); 277 314 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); 290 317 } 291 318 292 319 // Deactivation for Audio is defined in the corrigendum … … void MHAudio::Deactivation(MHEngine *engine) 308 335 MHPresentable::Deactivation(engine); 309 336 } 310 337 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 321 338 void MHAudio::BeginPlaying(MHEngine *engine) 322 339 { 323 340 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); 337 343 } 338 344 339 345 void MHAudio::StopPlaying(MHEngine *engine) … … void MHVideo::Activation(MHEngine *engine) 491 497 } 492 498 493 499 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); 507 502 } 508 503 509 504 void MHVideo::Deactivation(MHEngine *engine) … … void MHVideo::Deactivation(MHEngine *engine) 521 516 } 522 517 } 523 518 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 534 519 void MHVideo::BeginPlaying(MHEngine *engine) 535 520 { 536 521 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); 550 524 } 551 525 552 526 void MHVideo::StopPlaying(MHEngine *engine) … … void MHRTGraphics::PrintMe(FILE *fd, int nTabs) const 581 555 MHVisible::PrintMe(fd, nTabs); 582 556 // 583 557 } 558 559 // Fix for MHActionGenericObjectRef 560 void 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 44 44 virtual void ContentPreparation(MHEngine *engine); 45 45 46 46 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 47 57 protected: 48 58 MHOwnPtrSequence <MHPresentable> m_Multiplex; 49 59 enum Storage { ST_Mem = 1, ST_Stream = 2 } m_nStorage; … … class MHAudio : public MHPresentable 62 72 virtual void Activation(MHEngine *engine); 63 73 virtual void Deactivation(MHEngine *engine); 64 74 65 virtual void SetStreamRef(MHEngine *, const MHContentRef &);66 75 virtual void BeginPlaying(MHEngine *engine); 67 76 virtual void StopPlaying(MHEngine *engine); 68 77 … … class MHAudio : public MHPresentable 71 80 int m_nOriginalVol; 72 81 73 82 bool m_fStreamPlaying; 74 MHContentRef m_streamContentRef;75 83 }; 76 84 77 85 class MHVideo : public MHVisible … … class MHVideo : public MHVisible 97 105 virtual void SetVideoDecodeOffset(int newXOffset, int newYOffset, MHEngine *); 98 106 virtual void GetVideoDecodeOffset(MHRoot *pXOffset, MHRoot *pYOffset, MHEngine *); 99 107 100 virtual void SetStreamRef(MHEngine *, const MHContentRef &);101 108 virtual void BeginPlaying(MHEngine *engine); 102 109 virtual void StopPlaying(MHEngine *engine); 103 110 … … class MHVideo : public MHVisible 109 116 int m_nDecodeWidth, m_nDecodeHeight; 110 117 111 118 bool m_fStreamPlaying; 112 MHContentRef m_streamContentRef;113 119 }; 114 120 115 121 // Real-time graphics - not needed for UK MHEG. … … class MHGetVideoDecodeOffset: public MHActionObjectRef2 146 152 virtual void CallAction(MHEngine *engine, MHRoot *pTarget, MHRoot *pArg1, MHRoot *pArg2) { pTarget->GetVideoDecodeOffset(pArg1, pArg2, engine); } 147 153 }; 148 154 155 class MHActionGenericObjectRefFix: public MHActionGenericObjectRef 156 { 157 public: 158 MHActionGenericObjectRefFix(const char *name) : MHActionGenericObjectRef(name) {} 159 virtual void Perform(MHEngine *engine); 160 }; 161 162 class MHGetCounterPosition: public MHActionGenericObjectRefFix 163 { 164 public: 165 MHGetCounterPosition(): MHActionGenericObjectRefFix(":GetCounterPosition") {} 166 virtual void CallAction(MHEngine *engine, MHRoot *pTarget, MHRoot *pArg) 167 { pTarget->GetCounterPosition(pArg, engine); } 168 }; 169 170 class MHGetCounterMaxPosition: public MHActionGenericObjectRefFix 171 { 172 public: 173 MHGetCounterMaxPosition(): MHActionGenericObjectRefFix(":GetCounterMaxPosition") {} 174 virtual void CallAction(MHEngine *engine, MHRoot *pTarget, MHRoot *pArg) 175 { pTarget->GetCounterMaxPosition(pArg, engine); } 176 }; 177 178 class MHSetCounterPosition: public MHActionInt 179 { 180 public: 181 MHSetCounterPosition(): MHActionInt(":SetCounterPosition") {} 182 virtual void CallAction(MHEngine *engine, MHRoot *pTarget, int nArg) 183 { pTarget->SetCounterPosition(nArg, engine); } 184 }; 185 186 187 class MHSetSpeed: public MHElemAction 188 { 189 typedef MHElemAction base; 190 public: 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 } 202 protected: 203 virtual void PrintArgs(FILE *fd, int) const { m_Argument.PrintMe(fd, 0); } 204 MHGenericInteger m_Argument; 205 }; 206 149 207 150 208 #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 22 22 #if !defined(FREEMHEG_H) 23 23 #define FREEMHEG_H 24 24 25 #include <QtGlobal> 26 #include <QString> 27 #include <QByteArray> 25 28 #include <QRegion> 29 #include <QRect> 30 #include <QSize> 26 31 27 32 #include <stdio.h> 28 33 #include <stdlib.h> … … class MHTextDisplay; 32 37 class MHBitmapDisplay; 33 38 class MHContext; 34 39 class MHEG; 40 class MHStream; 35 41 36 42 // Called to create a new instance of the module. 37 43 extern MHEG *MHCreateEngine(MHContext *context); … … class MHEG 51 57 // Generate a UserAction event i.e. a key press. 52 58 virtual void GenerateUserAction(int nCode) = 0; 53 59 virtual void EngineEvent(int) = 0; 60 virtual void StreamStarted(MHStream*, bool bStarted = true) = 0; 54 61 }; 55 62 56 63 // Logging control … … class MHContext 128 135 // the m_stopped condition if we have. 129 136 virtual bool CheckStop(void) = 0; 130 137 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; 133 144 // Stop playing audio 134 virtual void StopAudio( void) = 0;135 // Begin displaying video from the specified stream136 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; 137 148 // 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; 139 158 140 159 // Get the context id strings. 141 160 virtual const char *GetReceiverId(void) = 0; 142 161 virtual const char *GetDSMCCId(void) = 0; 162 163 // InteractionChannel 164 virtual int GetICStatus() = 0; // 0= Active, 1= Inactive, 2= Disabled 143 165 }; 144 166 145 167 // 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 { 390 390 SOURCES += dsmcc.cpp dsmcccache.cpp 391 391 SOURCES += dsmccbiop.cpp dsmccobjcarousel.cpp 392 392 393 # MHEG interaction channel 394 HEADERS += mhegic.h netstream.h 395 SOURCES += mhegic.cpp netstream.cpp 396 393 397 # MHEG/MHI stuff 394 398 HEADERS += interactivetv.h mhi.h 395 399 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> 8 using 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 25 MHInteractionChannel::MHInteractionChannel(QObject* parent) : QObject(parent) 26 { 27 setObjectName("MHInteractionChannel"); 28 moveToThread(&NAMThread::manager()); 29 } 30 31 // virtual 32 MHInteractionChannel::~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 43 MHInteractionChannel::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 61 static inline bool isCached(const QString& csPath) 62 { 63 return NetStream::GetLastModified(csPath).isValid(); 64 } 65 66 // Is a file ready to read? 67 bool 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 99 MHInteractionChannel::EResult 100 MHInteractionChannel::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 158 void 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 12 class QByteArray; 13 class NetStream; 14 15 class MHInteractionChannel : public QObject 16 { 17 Q_OBJECT 18 Q_DISABLE_COPY(MHInteractionChannel) 19 20 public: 21 MHInteractionChannel(QObject* parent = 0); 22 virtual ~MHInteractionChannel(); 23 24 // Properties 25 public: 26 // Get network status 27 enum EStatus { kActive = 0, kInactive, kDisabled }; 28 static EStatus status(); 29 30 // Operations 31 public: 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 39 private slots: 40 // NetStream signals 41 void slotFinished(QObject*); 42 43 private: 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 1 3 #include <unistd.h> 2 4 3 5 #include <QRegion> 4 6 #include <QBitArray> 5 7 #include <QVector> 8 #include <QUrl> 6 9 7 #include "mhi.h"8 10 #include "interactivescreen.h" 9 11 #include "mythpainter.h" 10 12 #include "mythimage.h" … … void MHIContext::NetworkBootRequested(void) 358 360 // Called by the engine to check for the presence of an object in the carousel. 359 361 bool MHIContext::CheckCarouselObject(QString objectPath) 360 362 { 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 361 370 QStringList path = objectPath.split(QChar('/'), QString::SkipEmptyParts); 362 371 QByteArray result; // Unused 363 372 int res = m_dsmcc->GetDSMCCObject(path, result); … … bool MHIContext::CheckCarouselObject(QString objectPath) 367 376 // Called by the engine to request data from the carousel. 368 377 bool MHIContext::GetCarouselData(QString objectPath, QByteArray &result) 369 378 { 379 bool const isIC = objectPath.startsWith("http:") || objectPath.startsWith("https:"); 380 370 381 // Get the path components. The string will normally begin with "//" 371 382 // since this is an absolute path but that will be removed by split. 372 383 QStringList path = objectPath.split(QChar('/'), QString::SkipEmptyParts); … … bool MHIContext::GetCarouselData(QString objectPath, QByteArray &result) 375 386 // the result. 376 387 377 388 QMutexLocker locker(&m_runLock); 389 bool bReported = false; 378 390 QTime t; t.start(); 379 391 while (!m_stop) 380 392 { 381 393 locker.unlock(); 382 394 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. 390 432 // 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 392 439 // some more packets. We should eventually find out if this item is 393 440 // present. 394 441 ProcessDSMCCQueue(); 395 442 396 443 locker.relock(); 397 if (!m_stop) 398 m_engine_wait.wait(locker.mutex(), 1000); 444 m_engine_wait.wait(locker.mutex(), 300); 399 445 } 400 446 return false; // Stop has been set. Say the object isn't present. 401 447 } 402 448 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 450 class MHKeyLookup 407 451 { 408 int action = 0; 409 QMutexLocker locker(&m_keyLock); 452 typedef QPair< QString, int /*UserInput register*/ > key_t; 453 454 public: 455 MHKeyLookup(); 456 457 int Find(const QString &name, int reg) const 458 { return m_map.value(key_t(name,reg), 0); } 410 459 460 private: 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 467 void 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 489 MHKeyLookup::MHKeyLookup() 490 { 411 491 // This supports the UK and NZ key profile registers. 412 492 // The UK uses 3, 4 and 5 and NZ 13, 14 and 15. These are 413 493 // 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 } 414 536 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. 540 bool 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; 480 554 } 481 555 482 556 void MHIContext::Reinit(const QRect &display) … … void MHIContext::Reinit(const QRect &display) 491 565 492 566 void MHIContext::SetInputRegister(int num) 493 567 { 568 LOG(VB_MHEG, LOG_INFO, QString("[mhi] SetInputRegister %1").arg(num)); 494 569 QMutexLocker locker(&m_keyLock); 495 570 m_keyQueue.clear(); 496 571 m_keyProfile = num; 497 572 } 498 573 574 int MHIContext::GetICStatus() 575 { 576 // 0= Active, 1= Inactive, 2= Disabled 577 return m_ic.status(); 578 } 499 579 500 580 // Called by the video player to redraw the image. 501 581 void MHIContext::UpdateOSD(InteractiveScreen *osdWindow, … … int MHIContext::GetChannelIndex(const QString &str) 700 780 nResult = query.value(0).toInt(); 701 781 } 702 782 else if (str == "rec://svc/cur") 703 nResult = m_currentStream ;783 nResult = m_currentStream > 0 ? m_currentStream : m_currentChannel; 704 784 else if (str == "rec://svc/def") 705 785 nResult = m_currentChannel; 706 786 else if (str.startsWith("rec://")) … … int MHIContext::GetChannelIndex(const QString &str) 716 796 bool MHIContext::GetServiceInfo(int channelId, int &netId, int &origNetId, 717 797 int &transportId, int &serviceId) 718 798 { 719 LOG(VB_MHEG, LOG_INFO, QString("[mhi] GetServiceInfo %1").arg(channelId));720 799 MSqlQuery query(MSqlQuery::InitCon()); 721 800 query.prepare("SELECT networkid, transportid, serviceid " 722 801 "FROM channel, dtv_multiplex " … … bool MHIContext::GetServiceInfo(int channelId, int &netId, int &origNetId, 729 808 origNetId = netId; // We don't have this in the database. 730 809 transportId = query.value(1).toInt(); 731 810 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)); 732 813 return true; 733 814 } 734 else return false; 815 816 LOG(VB_MHEG, LOG_WARNING, QString("[mhi] GetServiceInfo %1 failed").arg(channelId)); 817 return false; 735 818 } 736 819 737 820 bool MHIContext::TuneTo(int channel, int tuneinfo) 738 821 { 739 LOG(VB_MHEG, LOG_INFO, QString("[mhi] TuneTo %1 0x%2")740 .arg(channel).arg(tuneinfo,0,16));741 742 822 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)); 743 826 return false; // Can't tune if this is a recording. 827 } 744 828 829 LOG(VB_GENERAL, LOG_INFO, QString("[mhi] TuneTo %1 0x%2") 830 .arg(channel).arg(tuneinfo,0,16)); 745 831 m_tuneinfo.append(tuneinfo); 746 832 747 833 // Post an event requesting a channel change. … … bool MHIContext::TuneTo(int channel, int tuneinfo) 754 840 return true; 755 841 } 756 842 757 // Begin playing audio from the specified stream 758 bool MHIContext::BeginAudio(const QString &stream, int tag) 843 844 // Begin playing the specified stream 845 bool MHIContext::BeginStream(const QString &stream, MHStream *notify) 759 846 { 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 } 761 864 762 865 int chan = GetChannelIndex(stream); 763 866 if (chan < 0) 764 867 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 766 874 if (chan != m_currentStream) 767 875 { 768 // We have to tune to the channel where the audiois to be found.876 // We have to tune to the channel where the stream is to be found. 769 877 // Because the audio and video are both components of an MHEG stream 770 878 // they will both be on the same channel. 771 879 m_currentStream = chan; 772 m_audioTag = tag;773 880 return TuneTo(chan, kTuneKeepChnl|kTuneQuietly|kTuneKeepApp); 774 881 } 882 883 return true; 884 } 775 885 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 886 void 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 896 bool MHIContext::StreamStarted(bool bStarted) 897 { 898 if (!m_engine || !m_notify) 781 899 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 782 908 } 783 909 910 // Begin playing audio 911 bool 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 784 924 // Stop playing audio 785 void MHIContext::StopAudio( void)925 void MHIContext::StopAudio() 786 926 { 787 927 // Do nothing at the moment. 788 928 } 789 929 790 930 // Begin displaying video from the specified stream 791 bool MHIContext::BeginVideo( const QString &stream,int tag)931 bool MHIContext::BeginVideo(int tag) 792 932 { 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)); 794 934 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 }805 935 if (tag < 0) 806 936 return true; // Leave it at the default. 807 else if (m_parent->GetNVP()) 937 938 m_videoTag = tag; 939 if (m_parent->GetNVP()) 808 940 return m_parent->GetNVP()->SetVideoByComponentTag(tag); 809 810 941 return false; 811 942 } 812 813 // Stop displaying video814 void MHIContext::StopVideo( void)943 944 // Stop displaying video 945 void MHIContext::StopVideo() 815 946 { 816 947 // Do nothing at the moment. 817 948 } 949 950 // Get current stream position, -1 if unknown 951 long MHIContext::GetStreamPos() 952 { 953 return m_parent->GetNVP() ? m_parent->GetNVP()->GetStreamPos() : -1; 954 } 955 956 // Get current stream size, -1 if unknown 957 long MHIContext::GetStreamMaxPos() 958 { 959 return m_parent->GetNVP() ? m_parent->GetNVP()->GetStreamMaxPos() : -1; 960 } 961 962 // Set current stream position 963 long MHIContext::SetStreamPos(long pos) 964 { 965 return m_parent->GetNVP() ? m_parent->GetNVP()->SetStreamPos(pos) : -1; 966 } 967 968 // Play or pause a stream 969 void MHIContext::StreamPlay(bool play) 970 { 971 if (m_parent->GetNVP()) 972 m_parent->GetNVP()->StreamPlay(play); 973 } 818 974 819 975 // Create a new object to draw dynamic line art. 820 976 MHDLADisplay *MHIContext::CreateDynamicLineArt( … … void MHIContext::DrawRect(int xPos, int yPos, int width, int height, 866 1022 // and usually that will be the same as the origin of the bounding 867 1023 // box (clipRect). 868 1024 void MHIContext::DrawImage(int x, int y, const QRect &clipRect, 869 const QImage &qImage )1025 const QImage &qImage, bool bScaled /* = false */) 870 1026 { 871 1027 if (qImage.isNull()) 872 1028 return; 873 1029 874 1030 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; 877 1032 878 if ( displayRect == imageRect) // No clipping required1033 if (bScaled || displayRect == imageRect) // No clipping required 879 1034 { 880 1035 QImage q_scaled = 881 1036 qImage.scaled( … … void MHIContext::DrawImage(int x, int y, const QRect &clipRect, 889 1044 else if (!displayRect.isEmpty()) 890 1045 { // We must clip the image. 891 1046 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)); 894 1048 QImage q_scaled = 895 1049 clipped.scaled( 896 1050 SCALED_X(displayRect.width()), … … void MHIBitmap::Draw(int x, int y, QRect rect, bool tiled) 1470 1624 tiledImage.setPixel(i, j, m_image.pixel(i % m_image.width(), j % m_image.height())); 1471 1625 } 1472 1626 } 1473 m_parent->DrawImage(rect.x(), rect.y(), rect, tiledImage );1627 m_parent->DrawImage(rect.x(), rect.y(), rect, tiledImage, true); 1474 1628 } 1475 1629 else 1476 1630 { 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); 1478 1633 } 1479 1634 } 1480 1635 -
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; 21 21 #include "../libmythfreemheg/freemheg.h" 22 22 #include "interactivetv.h" 23 23 #include "dsmcc.h" 24 #include "mhegic.h" 24 25 #include "mythcontext.h" 25 26 #include "mythdbcon.h" 26 27 #include "mythdeque.h" … … class MHIContext : public MHContext, public QRunnable 99 100 virtual void DrawBackground(const QRegion ®); 100 101 virtual void DrawVideo(const QRect &videoRect, const QRect &displayRect); 101 102 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); 103 104 104 105 virtual int GetChannelIndex(const QString &str); 105 106 /// Get netId etc from the channel index. … … class MHIContext : public MHContext, public QRunnable 107 108 int &transportId, int &serviceId); 108 109 virtual bool TuneTo(int channel, int tuneinfo); 109 110 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); 112 118 /// Stop playing audio 113 virtual void StopAudio( void);114 /// Begin displaying video from the specified stream115 virtual bool BeginVideo( const QString &stream,int tag);119 virtual void StopAudio(); 120 /// Begin displaying video 121 virtual bool BeginVideo(int tag); 116 122 /// 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); 118 132 119 133 // Get the context id strings. The format of these strings is specified 120 134 // by the UK MHEG profile. … … class MHIContext : public MHContext, public QRunnable 123 137 virtual const char *GetDSMCCId(void) 124 138 { return "DSMMYT001"; } // DSMCC version. 125 139 140 // InteractionChannel 141 virtual int GetICStatus(); // 0= Active, 1= Inactive, 2= Disabled 142 126 143 // Operations used by the display classes 127 144 // Add an item to the display vector 128 145 void AddToDisplay(const QImage &image, int x, int y); … … class MHIContext : public MHContext, public QRunnable 150 167 QMutex m_dsmccLock; 151 168 MythDeque<DSMCCPacket*> m_dsmccQueue; 152 169 170 MHInteractionChannel m_ic; // Interaction channel 171 MHStream *m_notify; 172 153 173 QMutex m_keyLock; 154 174 MythDeque<int> m_keyQueue; 155 175 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> 8 using 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 */ 45 static QAtomicInt s_nRequest(1); // Unique NetStream request ID 46 static QMutex s_mtx; // Guard local static data e.g. NAMThread singleton 47 48 49 /* 50 * Private types 51 */ 52 // Custom event posted to NAMThread 53 class NetStreamEvent : public QEvent 54 { 55 public: 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 */ 72 NetStream::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 98 NetStream::~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 112 static 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 124 static 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 132 bool 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 182 void 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 218 void 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 241 void 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 301 void 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 341 bool 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 349 bool NetStream::IsOpen() const 350 { 351 QMutexLocker locker(&m_mutex); 352 return m_state > kClosed; 353 } 354 355 void 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 377 int 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 401 qlonglong 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 420 qlonglong 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 431 qlonglong 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 442 qlonglong NetStream::GetSize() const 443 { 444 QMutexLocker locker(&m_mutex); 445 return ContentLength(); 446 } 447 448 /** 449 * Synchronous interface 450 */ 451 bool 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 468 bool 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 485 QNetworkReply::NetworkError NetStream::GetError() const 486 { 487 QMutexLocker locker(&m_mutex); 488 return !m_reply ? QNetworkReply::OperationCanceledError : m_reply->error(); 489 } 490 491 QString NetStream::GetErrorString() const 492 { 493 QMutexLocker locker(&m_mutex); 494 return !m_reply ? "Operation cancelled" : m_reply->errorString(); 495 } 496 497 qlonglong NetStream::BytesAvailable() const 498 { 499 QMutexLocker locker(&m_mutex); 500 return m_reply ? m_reply->bytesAvailable() : 0; 501 } 502 503 QByteArray NetStream::ReadAll() 504 { 505 QMutexLocker locker(&m_mutex); 506 return m_reply ? m_reply->readAll() : QByteArray(); 507 } 508 509 /** 510 * Asynchronous interface 511 */ 512 bool NetStream::isStarted() const 513 { 514 QMutexLocker locker(&m_mutex); 515 return m_state >= kStarted; 516 } 517 518 bool NetStream::isReady() const 519 { 520 QMutexLocker locker(&m_mutex); 521 return m_state >= kReady; 522 } 523 524 bool NetStream::isFinished() const 525 { 526 QMutexLocker locker(&m_mutex); 527 return m_state >= kFinished; 528 } 529 530 /** 531 * Public helpers 532 */ 533 // static 534 bool 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 541 QDateTime NetStream::GetLastModified(const QString &url) 542 { 543 return NAMThread::GetLastModified(url); 544 } 545 546 547 /** 548 * NetworkAccessManager event loop thread 549 */ 550 //static 551 NAMThread & NAMThread::manager() 552 { 553 QMutexLocker locker(&s_mtx); 554 555 // Singleton 556 static NAMThread thread; 557 thread.start(); 558 return thread; 559 } 560 561 NAMThread::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 573 NAMThread::~NAMThread() 574 { 575 QMutexLocker locker(&m_mutex); 576 delete m_nam; 577 } 578 579 // virtual 580 void 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 649 void NAMThread::quit() 650 { 651 m_bQuit = true; 652 QThread::quit(); 653 } 654 655 // static 656 void NAMThread::PostEvent(QEvent *event) 657 { 658 NAMThread &m = manager(); 659 QMutexLocker locker(&m.m_mutex); 660 m.m_workQ.enqueue(event); 661 } 662 663 bool 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 684 bool 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 709 QDateTime 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 21 class QUrl; 22 class QNetworkAccessManager; 23 class NetStreamEvent; 24 25 26 /** 27 * Stream content from a URI 28 */ 29 class NetStream : public QObject 30 { 31 Q_OBJECT 32 Q_DISABLE_COPY(NetStream) 33 34 public: 35 enum EMode { kNeverCache, kPreferCache, kAlwaysCache }; 36 NetStream(const QUrl &, EMode mode = kPreferCache); 37 virtual ~NetStream(); 38 39 public: 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 65 signals: 66 void ReadyRead(QObject*); 67 void Finished(QObject*); 68 69 public: 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 76 private 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 87 private: 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 */ 107 class NAMThread : public QThread 108 { 109 Q_OBJECT 110 Q_DISABLE_COPY(NAMThread) 111 112 // Use manager() to create 113 NAMThread(); 114 115 public: 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 124 signals: 125 void requestStarted(int, QNetworkReply *); 126 127 // Implementation 128 protected: 129 virtual void run(); // QThread override 130 bool NewRequest(QEvent *); 131 132 private slots: 133 void quit(); 134 135 private: 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 */
