Ticket #10019: iplayer.diff
| File iplayer.diff, 130.9 KB (added by , 14 years ago) |
|---|
-
mythtv/libs/libmythfreemheg/ASN1Codes.h
diff --git a/mythtv/libs/libmythfreemheg/ASN1Codes.h b/mythtv/libs/libmythfreemheg/ASN1Codes.h index 53ea5aa..8cc353a 100644
a b 280 280 #define C_SET_BITMAP_DECODE_OFFSET 246 281 281 #define C_GET_BITMAP_DECODE_OFFSET 247 282 282 #define C_SET_SLIDER_PARAMETERS 248 283 // Added in ETSI ES 202 184 V2.1.1 (2010-01) 284 #define C_GET_DESKTOP_COLOUR 249 285 #define C_SET_DESKTOP_COLOUR 250 286 #define C_GET_COUNTER_POSITION 251 287 #define C_GET_COUNTER_MAX_POSITION 252 283 288 284 289 // Pseudo-codes. These are encoded into the link condition in binary but it's convenient 285 290 // to give them codes here since that way we can include them in the same lookup table. -
mythtv/libs/libmythfreemheg/Actions.cpp
diff --git a/mythtv/libs/libmythfreemheg/Actions.cpp b/mythtv/libs/libmythfreemheg/Actions.cpp index 75ede6a..041f6d2 100644
a b 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/Bitmap.cpp
diff --git a/mythtv/libs/libmythfreemheg/Bitmap.cpp b/mythtv/libs/libmythfreemheg/Bitmap.cpp index 42c9df1..522175d 100644
a b void MHBitmap::ContentPreparation(MHEngine *engine) 127 127 MHERROR("Bitmap must contain a content"); 128 128 } 129 129 130 if (m_ContentType == IN_IncludedContent) // We can't handle included content at the moment. 131 { 132 MHERROR("Included content in bitmap is not implemented"); 133 } 130 if (m_ContentType == IN_IncludedContent) 131 CreateContent(m_IncludedContent.Bytes(), m_IncludedContent.Size(), engine); 134 132 } 135 133 136 134 // Decode the content. … … void MHBitmap::ContentArrived(const unsigned char *data, int length, MHEngine *e 143 141 return; // Shouldn't happen. 144 142 } 145 143 144 CreateContent(data, length, engine); 145 // Now signal that the content is available. 146 engine->EventTriggered(this, EventContentAvailable); 147 } 148 149 void MHBitmap::CreateContent(const unsigned char *data, int length, MHEngine *engine) 150 { 151 QRegion updateArea = GetVisibleArea(); // If there's any content already we have to redraw it. 152 146 153 int nCHook = m_nContentHook; 147 154 148 155 if (nCHook == 0) … … void MHBitmap::ContentArrived(const unsigned char *data, int length, MHEngine *e 162 169 { 163 170 m_pContent->CreateFromMPEG(data, length); 164 171 } 165 172 else if (nCHook == 6) // JPEG ISO/IEC 10918-1, JFIF file 173 { 174 m_pContent->CreateFromJPEG(data, length); 175 } 166 176 else 167 177 { 178 // 1,3,5,8 are reserved. 7= H.264 Intra Frame 168 179 MHERROR(QString("Unknown bitmap content hook %1").arg(nCHook)); 169 180 } 170 181 171 182 updateArea += GetVisibleArea(); // Redraw this bitmap. 172 183 engine->Redraw(updateArea); // Mark for redrawing 173 174 // Now signal that the content is available.175 engine->EventTriggered(this, EventContentAvailable);176 184 } 177 185 178 186 179 180 187 // Set the transparency. 181 188 void MHBitmap::SetTransparency(int nTransPerCent, MHEngine *) 182 189 { -
mythtv/libs/libmythfreemheg/Bitmap.h
diff --git a/mythtv/libs/libmythfreemheg/Bitmap.h b/mythtv/libs/libmythfreemheg/Bitmap.h index 06d5f0b..e37c67d 100644
a b class MHBitmap : public MHVisible 69 69 int m_nXDecodeOffset, m_nYDecodeOffset; 70 70 71 71 MHBitmapDisplay *m_pContent; // Pointer to current image if any. 72 73 void CreateContent(const unsigned char *p, int s, MHEngine *engine); 72 74 }; 73 75 74 76 // Actions. -
mythtv/libs/libmythfreemheg/Engine.cpp
diff --git a/mythtv/libs/libmythfreemheg/Engine.cpp b/mythtv/libs/libmythfreemheg/Engine.cpp index 3ef0825..175c7c0 100644
a b 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/TokenGroup.cpp
diff --git a/mythtv/libs/libmythfreemheg/TokenGroup.cpp b/mythtv/libs/libmythfreemheg/TokenGroup.cpp index b24160b..b147952 100644
a b void MHTokenGroupItem::PrintMe(FILE *fd, int nTabs) const 68 68 for (int i = 0; i < m_ActionSlots.Size(); i++) 69 69 { 70 70 PrintTabs(fd, nTabs + 2); 71 fprintf(fd, "( \n");71 fprintf(fd, "( // slot %d\n", i); 72 72 MHActionSequence *pActions = m_ActionSlots.GetAt(i); 73 73 74 74 if (pActions->Size() == 0) -
mythtv/libs/libmythfreemheg/Variables.h
diff --git a/mythtv/libs/libmythfreemheg/Variables.h b/mythtv/libs/libmythfreemheg/Variables.h index 544a913..2363b06 100644
a b class MHDivide: public MHIntegerAction { 205 205 public: 206 206 MHDivide(): MHIntegerAction(":Divide") {} 207 207 protected: 208 virtual int DoOp(int arg1, int arg2) { return arg1/arg2; } // What about divide by zero? 208 virtual int DoOp(int arg1, int arg2) { 209 if (arg2 == 0) throw "Divide by 0"; 210 return arg1/arg2; 211 } 209 212 }; 210 213 211 214 class MHModulo: public MHIntegerAction { 212 215 public: 213 216 MHModulo(): MHIntegerAction(":Modulo") {} 214 217 protected: 215 virtual int DoOp(int arg1, int arg2) { return arg 1%arg2; } // What about divide by zero?218 virtual int DoOp(int arg1, int arg2) { return arg2 ? arg1%arg2 : 0; } 216 219 }; 217 220 218 221 // Append - -
mythtv/libs/libmythfreemheg/Visible.cpp
diff --git a/mythtv/libs/libmythfreemheg/Visible.cpp b/mythtv/libs/libmythfreemheg/Visible.cpp index 1d69a39..8ee4e3f 100644
a b void MHVisible::Deactivation(MHEngine *engine) 164 164 MHRgba MHVisible::GetColour(const MHColour &colour) 165 165 { 166 166 int red = 0, green = 0, blue = 0, alpha = 0; 167 int cSize = colour.m_ColStr.Size(); 168 169 if (cSize != 4) 167 if (colour.IsSet()) 170 168 { 171 MHLOG(MHLogWarning, QString("Colour string has length %1 not 4.").arg(cSize)); 172 } 169 int cSize = colour.m_ColStr.Size(); 173 170 174 // Just in case the length is short we handle those properly. 175 if (cSize > 0) 176 { 177 red = colour.m_ColStr.GetAt(0); 178 } 171 if (cSize != 4) 172 { 173 MHLOG(MHLogWarning, QString("Colour string has length %1 not 4.").arg(cSize)); 174 } 179 175 180 if (cSize > 1) 181 { 182 green = colour.m_ColStr.GetAt(1); 183 } 176 // Just in case the length is short we handle those properly. 177 if (cSize > 0) 178 { 179 red = colour.m_ColStr.GetAt(0); 180 } 184 181 185 if (cSize > 2)186 {187 blue = colour.m_ColStr.GetAt(2);188 }182 if (cSize > 1) 183 { 184 green = colour.m_ColStr.GetAt(1); 185 } 189 186 190 if (cSize > 3) 191 { 192 alpha = 255 - colour.m_ColStr.GetAt(3); // Convert transparency to alpha 187 if (cSize > 2) 188 { 189 blue = colour.m_ColStr.GetAt(2); 190 } 191 192 if (cSize > 3) 193 { 194 alpha = 255 - colour.m_ColStr.GetAt(3); // Convert transparency to alpha 195 } 193 196 } 194 197 195 198 return MHRgba(red, green, blue, alpha); -
mythtv/libs/libmythfreemheg/freemheg.h
diff --git a/mythtv/libs/libmythfreemheg/freemheg.h b/mythtv/libs/libmythfreemheg/freemheg.h index 0b61f65..8a9a983 100644
a b 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. … … class MHBitmapDisplay 191 213 // Creation functions 192 214 virtual void CreateFromPNG(const unsigned char *data, int length) = 0; 193 215 virtual void CreateFromMPEG(const unsigned char *data, int length) = 0; 216 virtual void CreateFromJPEG(const unsigned char *data, int length) = 0; 194 217 // Scale the bitmap. Only used for image derived from MPEG I-frames. 195 218 virtual void ScaleImage(int newWidth, int newHeight) = 0; 196 219 // Information about the image. -
mythtv/libs/libmythtv/fileringbuffer.cpp
diff --git a/mythtv/libs/libmythtv/fileringbuffer.cpp b/mythtv/libs/libmythtv/fileringbuffer.cpp index 944c98d..1449883 100644
a b bool FileRingBuffer::OpenFile(const QString &lfilename, uint retry_ms) 356 356 commserror = false; 357 357 numfailures = 0; 358 358 359 rawbitrate = 8000; 359 // The initial bitrate needs to be set with consideration for low bit rate 360 // streams (e.g. radio @ 64Kbps) such that fill_min bytes are received 361 // in a reasonable time period to enable decoders to peek the first few KB 362 // to determine type & settings. 363 if (is_local) 364 rawbitrate = 256; // Allow for radio 365 else 366 rawbitrate = 128; // remotefile 367 360 368 CalcReadAheadThresh(); 361 369 362 370 bool ok = fd2 >= 0 || remotefile; … … int FileRingBuffer::safe_read(int fd, void *data, uint sz) 487 495 */ 488 496 int FileRingBuffer::safe_read(RemoteFile *rf, void *data, uint sz) 489 497 { 490 int ret = rf->Read(data, sz); 491 if (ret < 0) 492 { 493 LOG(VB_GENERAL, LOG_ERR, LOC + 494 "safe_read(RemoteFile* ...): read failed"); 495 496 poslock.lockForRead(); 497 rf->Seek(internalreadpos - readAdjust, SEEK_SET); 498 poslock.unlock(); 499 numfailures++; 500 } 501 else if (ret == 0) 498 for (int retries = 0; ; ++retries) 502 499 { 503 LOG(VB_FILE, LOG_INFO, LOC + 504 "safe_read(RemoteFile* ...): at EOF"); 500 int ret = rf->Read(data, sz); 501 if (ret > 0) 502 return ret; 503 else if (ret < 0) 504 { 505 LOG(VB_GENERAL, LOG_ERR, LOC + 506 "safe_read(RemoteFile* ...): read failed"); 507 508 poslock.lockForRead(); 509 rf->Seek(internalreadpos - readAdjust, SEEK_SET); 510 poslock.unlock(); 511 numfailures++; 512 return ret; 513 } 514 // Retry for 300mS if liveTV for low bit rate (radio) streams 515 else if (!livetvchain || retries >= 5) 516 break; 517 518 usleep(60000); 505 519 } 506 520 507 return ret; 521 LOG(VB_FILE, LOG_INFO, LOC + 522 "safe_read(RemoteFile* ...): at EOF"); 523 return 0; 508 524 } 509 525 510 526 long long FileRingBuffer::GetReadPosition(void) const -
new file mythtv/libs/libmythtv/icringbuffer.cpp
diff --git a/mythtv/libs/libmythtv/icringbuffer.cpp b/mythtv/libs/libmythtv/icringbuffer.cpp new file mode 100644 index 0000000..f099de2
- + 1 #include "icringbuffer.h" 2 3 #include <stdio.h> // SEEK_SET 4 5 #include <QScopedPointer> 6 #include <QWriteLocker> 7 8 #include "netstream.h" 9 #include "mythlogging.h" 10 11 12 #define LOC QString("ICRingBuf ") 13 14 15 ICRingBuffer::ICRingBuffer(const QString &url, RingBuffer *parent) 16 : RingBuffer(kRingBufferType), m_stream(0), m_parent(parent) 17 { 18 startreadahead = true; 19 OpenFile(url); 20 } 21 22 ICRingBuffer::~ICRingBuffer() 23 { 24 delete m_stream; 25 delete m_parent; 26 } 27 28 bool ICRingBuffer::IsOpen(void) const 29 { 30 return m_stream ? m_stream->IsOpen() : false; 31 } 32 33 bool ICRingBuffer::OpenFile(const QString &url, uint retry_ms) 34 { 35 if (!NetStream::IsSupported(url)) 36 { 37 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Unsupported URL %1").arg(url) ); 38 return false; 39 } 40 41 QScopedPointer<NetStream> stream(new NetStream(url)); 42 if (!stream || !stream->IsOpen()) 43 { 44 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to open %1").arg(url) ); 45 return false; 46 } 47 48 if (!stream->WaitTillReady(30000)) 49 { 50 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Stream not ready%1").arg(url) ); 51 return false; 52 } 53 54 if (m_parent) 55 m_parent->Pause(); 56 57 QWriteLocker locker(&rwlock); 58 59 safefilename = url; 60 filename = url; 61 62 delete m_stream; 63 m_stream = stream.take(); 64 65 // The initial bitrate needs to be set with consideration for low bit rate 66 // streams (e.g. radio @ 64Kbps) such that fill_min bytes are received 67 // in a reasonable time period to enable decoders to peek the first few KB 68 // to determine type & settings. 69 rawbitrate = 128; // remotefile 70 CalcReadAheadThresh(); 71 72 locker.unlock(); 73 Reset(true, false, true); 74 75 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Opened %1").arg(url)); 76 return true; 77 } 78 79 long long ICRingBuffer::GetReadPosition(void) const 80 { 81 return m_stream ? m_stream->GetReadPosition() : 0; 82 } 83 84 long long ICRingBuffer::Seek(long long pos, int whence, bool has_lock) 85 { 86 return m_stream ? (whence == SEEK_SET ? m_stream->Seek(pos) : -1) : -1; 87 } 88 89 int ICRingBuffer::safe_read(void *data, uint sz) 90 { 91 return m_stream ? m_stream->safe_read(data, sz, 10) : (ateof = true, 0); 92 } 93 94 long long ICRingBuffer::GetRealFileSize(void) const 95 { 96 return m_stream ? m_stream->GetSize() : -1; 97 } 98 99 // Take ownership of parent RingBuffer 100 RingBuffer *ICRingBuffer::Take() 101 { 102 RingBuffer *parent = m_parent; 103 m_parent = 0; 104 return parent; 105 } 106 107 // End of file -
new file mythtv/libs/libmythtv/icringbuffer.h
diff --git a/mythtv/libs/libmythtv/icringbuffer.h b/mythtv/libs/libmythtv/icringbuffer.h new file mode 100644 index 0000000..15d4b1c
- + 1 #ifndef ICRINGBUFFER_H 2 #define ICRINGBUFFER_H 3 4 #include "ringbuffer.h" 5 6 class NetStream; 7 8 class ICRingBuffer : public RingBuffer 9 { 10 public: 11 static enum RingBufferType const kRingBufferType = kRingBuffer_MHEG; 12 13 ICRingBuffer(const QString &url, RingBuffer *parent = 0); 14 virtual ~ICRingBuffer(); 15 16 // RingBuffer implementation 17 virtual bool IsOpen(void) const; 18 virtual long long GetReadPosition(void) const; 19 virtual bool OpenFile(const QString &url, 20 uint retry_ms = kDefaultOpenTimeout); 21 virtual long long Seek(long long pos, int whence, bool has_lock); 22 virtual long long GetRealFileSize(void) const; 23 virtual bool IsStreamed(void) { return true; } 24 virtual bool IsSeekingAllowed(void) { return false; } 25 virtual bool IsBookmarkAllowed(void) { return false; } 26 27 protected: 28 virtual int safe_read(void *data, uint sz); 29 30 // Operations 31 public: 32 // Take ownership of parent RingBuffer 33 RingBuffer *Take(); 34 35 private: 36 NetStream *m_stream; 37 RingBuffer *m_parent; // parent RingBuffer 38 }; 39 40 #endif // ICRINGBUFFER_H -
mythtv/libs/libmythtv/interactivetv.cpp
diff --git a/mythtv/libs/libmythtv/interactivetv.cpp b/mythtv/libs/libmythtv/interactivetv.cpp index 2cdbea0..d1efa87 100644
a b InteractiveTV::InteractiveTV(MythPlayer *nvp) 17 17 { 18 18 Restart(0, 0, false); 19 19 20 if (VERBOSE_LEVEL_CHECK(VB_MHEG, LOG_ANY)) 21 { 22 MHSetLogging(stdout, MHLogAll); 23 } 24 else 25 { 26 MHSetLogging(stdout, MHLogError); 27 } 20 MHSetLogging(stdout, 21 VERBOSE_LEVEL_CHECK(VB_MHEG, LOG_DEBUG) ? MHLogAll : 22 VERBOSE_LEVEL_CHECK(VB_MHEG, LOG_ANY) ? 23 MHLogError | MHLogWarning | MHLogNotifications /*| MHLogLinks | MHLogActions | MHLogDetail*/ : 24 MHLogError | MHLogWarning ); 28 25 } 29 26 30 27 InteractiveTV::~InteractiveTV() … … void InteractiveTV::SetNetBootInfo(const unsigned char *data, uint length) 79 76 { 80 77 m_context->SetNetBootInfo(data, length); 81 78 } 79 80 bool InteractiveTV::StreamStarted(bool bStarted) 81 { 82 return m_context->StreamStarted(bStarted); 83 } -
mythtv/libs/libmythtv/interactivetv.h
diff --git a/mythtv/libs/libmythtv/interactivetv.h b/mythtv/libs/libmythtv/interactivetv.h index c8e00e6..ba426de 100644
a b class InteractiveTV 39 39 40 40 // Get the initial component tags. 41 41 void GetInitialStreams(int &audioTag, int &videoTag); 42 // Called when a stream starts or stops. Returns true if event is handled 43 bool StreamStarted(bool bStarted = true); 42 44 43 45 MythPlayer *GetNVP(void) { return m_nvp; } 44 46 -
mythtv/libs/libmythtv/libmythtv.pro
diff --git a/mythtv/libs/libmythtv/libmythtv.pro b/mythtv/libs/libmythtv/libmythtv.pro index 36fd87c..7d8ff73 100644
a b HEADERS += mythsystemevent.h 165 165 HEADERS += avfringbuffer.h ThreadedFileWriter.h 166 166 HEADERS += ringbuffer.h fileringbuffer.h 167 167 HEADERS += dvdringbuffer.h bdringbuffer.h 168 HEADERS += streamingringbuffer.h metadataimagehelper.h 168 HEADERS += streamingringbuffer.h icringbuffer.h 169 HEADERS += metadataimagehelper.h 169 170 170 171 SOURCES += recordinginfo.cpp 171 172 SOURCES += dbcheck.cpp … … SOURCES += mythsystemevent.cpp 193 194 SOURCES += avfringbuffer.cpp ThreadedFileWriter.cpp 194 195 SOURCES += ringbuffer.cpp fileringBuffer.cpp 195 196 SOURCES += dvdringbuffer.cpp bdringbuffer.cpp 196 SOURCES += streamingringbuffer.cpp metadataimagehelper.cpp 197 SOURCES += streamingringbuffer.cpp icringbuffer.cpp 198 SOURCES += metadataimagehelper.cpp 197 199 198 200 # DiSEqC 199 201 HEADERS += diseqc.h diseqcsettings.h … … using_frontend { 428 430 SOURCES += dsmcc.cpp dsmcccache.cpp 429 431 SOURCES += dsmccbiop.cpp dsmccobjcarousel.cpp 430 432 433 # MHEG interaction channel 434 HEADERS += mhegic.h netstream.h 435 SOURCES += mhegic.cpp netstream.cpp 436 431 437 # MHEG/MHI stuff 432 438 HEADERS += interactivetv.h mhi.h 433 439 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 1631495..ca8e9d0 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 // NB don't exit if -1 (not present) is returned as the object may 423 // arrive later. Exiting can cause the inital app to not be found 424 } 425 426 if (t.elapsed() > 60000) // TODO get this from carousel info 427 return false; // Not there. 390 428 // Otherwise we block. 391 // Process DSMCC packets then block for a second or until we receive 429 if (!bReported) 430 { 431 bReported = true; 432 LOG(VB_MHEG, LOG_INFO, QString("[mhi] Waiting for %1").arg(objectPath)); 433 } 434 // Process DSMCC packets then block for a while or until we receive 392 435 // some more packets. We should eventually find out if this item is 393 436 // present. 394 437 ProcessDSMCCQueue(); 395 438 396 439 locker.relock(); 397 if (!m_stop) 398 m_engine_wait.wait(locker.mutex(), 1000); 440 m_engine_wait.wait(locker.mutex(), 300); 399 441 } 400 442 return false; // Stop has been set. Say the object isn't present. 401 443 } 402 444 403 // Called from tv_play when a key is pressed. 404 // If it is one in the current profile we queue it for the engine 405 // and return true otherwise we return false. 406 bool MHIContext::OfferKey(QString key) 445 // Mapping from key name & UserInput register to UserInput EventData 446 class MHKeyLookup 407 447 { 408 int action = 0; 409 QMutexLocker locker(&m_keyLock); 448 typedef QPair< QString, int /*UserInput register*/ > key_t; 449 450 public: 451 MHKeyLookup(); 452 453 int Find(const QString &name, int reg) const 454 { return m_map.value(key_t(name,reg), 0); } 410 455 456 private: 457 void key(const QString &name, int code, int r1, 458 int r2=0, int r3=0, int r4=0, int r5=0, int r6=0, int r7=0, int r8=0, int r9=0); 459 460 QHash<key_t,int /*EventData*/ > m_map; 461 }; 462 463 void MHKeyLookup::key(const QString &name, int code, int r1, 464 int r2, int r3, int r4, int r5, int r6, int r7, int r8, int r9) 465 { 466 m_map.insert(key_t(name,r1), code); 467 if (r2 > 0) 468 m_map.insert(key_t(name,r2), code); 469 if (r3 > 0) 470 m_map.insert(key_t(name,r3), code); 471 if (r4 > 0) 472 m_map.insert(key_t(name,r4), code); 473 if (r5 > 0) 474 m_map.insert(key_t(name,r5), code); 475 if (r6 > 0) 476 m_map.insert(key_t(name,r6), code); 477 if (r7 > 0) 478 m_map.insert(key_t(name,r7), code); 479 if (r8 > 0) 480 m_map.insert(key_t(name,r8), code); 481 if (r9 > 0) 482 m_map.insert(key_t(name,r9), code); 483 } 484 485 MHKeyLookup::MHKeyLookup() 486 { 411 487 // This supports the UK and NZ key profile registers. 412 488 // The UK uses 3, 4 and 5 and NZ 13, 14 and 15. These are 413 489 // similar but the NZ profile also provides an EPG key. 490 // ETSI ES 202 184 V2.2.1 (2011-03) adds group 6 for ICE. 491 // The BBC use group 7 for ICE 492 key(ACTION_UP, 1, 4,5,6,7,14,15); 493 key(ACTION_DOWN, 2, 4,5,6,7,14,15); 494 key(ACTION_LEFT, 3, 4,5,6,7,14,15); 495 key(ACTION_RIGHT, 4, 4,5,6,7,14,15); 496 key(ACTION_0, 5, 4,6,7,14); 497 key(ACTION_1, 6, 4,6,7,14); 498 key(ACTION_2, 7, 4,6,7,14); 499 key(ACTION_3, 8, 4,6,7,14); 500 key(ACTION_4, 9, 4,6,7,14); 501 key(ACTION_5, 10, 4,6,7,14); 502 key(ACTION_6, 11, 4,6,7,14); 503 key(ACTION_7, 12, 4,6,7,14); 504 key(ACTION_8, 13, 4,6,7,14); 505 key(ACTION_9, 14, 4,6,7,14); 506 key(ACTION_SELECT, 15, 4,5,6,7,14,15); 507 key(ACTION_TEXTEXIT, 16, 3,4,5,6,7,13,14,15); // 16= Cancel 508 // 17= help 509 // 18..99 reserved by DAVIC 510 key(ACTION_MENURED, 100, 3,4,5,6,7,13,14,15); 511 key(ACTION_MENUGREEN, 101, 3,4,5,6,7,13,14,15); 512 key(ACTION_MENUYELLOW, 102, 3,4,5,6,7,13,14,15); 513 key(ACTION_MENUBLUE, 103, 3,4,5,6,7,13,14,15); 514 key(ACTION_MENUTEXT, 104, 3,4,5,6,7); 515 key(ACTION_MENUTEXT, 105, 13,14,15); // NB from original Myth code 516 // 105..119 reserved for future spec 517 key(ACTION_STOP, 120, 6,7); 518 key(ACTION_PLAY, 121, 6,7); 519 key(ACTION_PAUSE, 122, 6,7); 520 key(ACTION_JUMPFFWD, 123, 6,7); // 123= Skip Forward 521 key(ACTION_JUMPRWND, 124, 6,7); // 124= Skip Back 522 #if 0 // These conflict with left & right 523 key(ACTION_SEEKFFWD, 125, 6,7); // 125= Fast Forward 524 key(ACTION_SEEKRWND, 126, 6,7); // 126= Rewind 525 #endif 526 key(ACTION_PLAYBACK, 127, 6,7); 527 // 128..256 reserved for future spec 528 // 257..299 vendor specific 529 key(ACTION_MENUEPG, 300, 13,14,15); 530 // 301.. Vendor specific 531 } 414 532 415 if (key == ACTION_UP) 416 { 417 if (m_keyProfile == 4 || m_keyProfile == 5 || 418 m_keyProfile == 14 || m_keyProfile == 15) 419 action = 1; 420 } 421 else if (key == ACTION_DOWN) 422 { 423 if (m_keyProfile == 4 || m_keyProfile == 5 || 424 m_keyProfile == 14 || m_keyProfile == 15) 425 action = 2; 426 } 427 else if (key == ACTION_LEFT) 428 { 429 if (m_keyProfile == 4 || m_keyProfile == 5 || 430 m_keyProfile == 14 || m_keyProfile == 15) 431 action = 3; 432 } 433 else if (key == ACTION_RIGHT) 434 { 435 if (m_keyProfile == 4 || m_keyProfile == 5 || 436 m_keyProfile == 14 || m_keyProfile == 15) 437 action = 4; 438 } 439 else if (key == ACTION_0 || key == ACTION_1 || key == ACTION_2 || 440 key == ACTION_3 || key == ACTION_4 || key == ACTION_5 || 441 key == ACTION_6 || key == ACTION_7 || key == ACTION_8 || 442 key == ACTION_9) 443 { 444 if (m_keyProfile == 4 || m_keyProfile == 14) 445 action = key.toInt() + 5; 446 } 447 else if (key == ACTION_SELECT) 448 { 449 if (m_keyProfile == 4 || m_keyProfile == 5 || 450 m_keyProfile == 14 || m_keyProfile == 15) 451 action = 15; 452 } 453 else if (key == ACTION_TEXTEXIT) 454 action = 16; 455 else if (key == ACTION_MENURED) 456 action = 100; 457 else if (key == ACTION_MENUGREEN) 458 action = 101; 459 else if (key == ACTION_MENUYELLOW) 460 action = 102; 461 else if (key == ACTION_MENUBLUE) 462 action = 103; 463 else if (key == ACTION_MENUTEXT) 464 action = m_keyProfile > 12 ? 105 : 104; 465 else if (key == ACTION_MENUEPG) 466 action = m_keyProfile > 12 ? 300 : 0; 467 468 if (action != 0) 469 { 470 m_keyQueue.enqueue(action); 471 LOG(VB_GENERAL, LOG_INFO, QString("Adding MHEG key %1:%2:%3").arg(key) 472 .arg(action).arg(m_keyQueue.size())); 473 locker.unlock(); 474 QMutexLocker locker2(&m_runLock); 475 m_engine_wait.wakeAll(); 476 return true; 477 } 478 479 return false; 533 // Called from tv_play when a key is pressed. 534 // If it is one in the current profile we queue it for the engine 535 // and return true otherwise we return false. 536 bool MHIContext::OfferKey(QString key) 537 { 538 static const MHKeyLookup s_keymap; 539 int action = s_keymap.Find(key, m_keyProfile); 540 if (action == 0) 541 return false; 542 543 LOG(VB_GENERAL, LOG_INFO, QString("[mhi] Adding MHEG key %1:%2:%3") 544 .arg(key).arg(action).arg(m_keyQueue.size()) ); 545 { QMutexLocker locker(&m_keyLock); 546 m_keyQueue.enqueue(action);} 547 QMutexLocker locker2(&m_runLock); 548 m_engine_wait.wakeAll(); 549 return true; 480 550 } 481 551 482 552 void MHIContext::Reinit(const QRect &display) … … void MHIContext::Reinit(const QRect &display) 491 561 492 562 void MHIContext::SetInputRegister(int num) 493 563 { 564 LOG(VB_MHEG, LOG_INFO, QString("[mhi] SetInputRegister %1").arg(num)); 494 565 QMutexLocker locker(&m_keyLock); 495 566 m_keyQueue.clear(); 496 567 m_keyProfile = num; 497 568 } 498 569 570 int MHIContext::GetICStatus() 571 { 572 // 0= Active, 1= Inactive, 2= Disabled 573 return m_ic.status(); 574 } 499 575 500 576 // Called by the video player to redraw the image. 501 577 void MHIContext::UpdateOSD(InteractiveScreen *osdWindow, … … int MHIContext::GetChannelIndex(const QString &str) 700 776 nResult = query.value(0).toInt(); 701 777 } 702 778 else if (str == "rec://svc/cur") 703 nResult = m_currentStream ;779 nResult = m_currentStream > 0 ? m_currentStream : m_currentChannel; 704 780 else if (str == "rec://svc/def") 705 781 nResult = m_currentChannel; 706 782 else if (str.startsWith("rec://")) … … int MHIContext::GetChannelIndex(const QString &str) 716 792 bool MHIContext::GetServiceInfo(int channelId, int &netId, int &origNetId, 717 793 int &transportId, int &serviceId) 718 794 { 719 LOG(VB_MHEG, LOG_INFO, QString("[mhi] GetServiceInfo %1").arg(channelId));720 795 MSqlQuery query(MSqlQuery::InitCon()); 721 796 query.prepare("SELECT networkid, transportid, serviceid " 722 797 "FROM channel, dtv_multiplex " … … bool MHIContext::GetServiceInfo(int channelId, int &netId, int &origNetId, 729 804 origNetId = netId; // We don't have this in the database. 730 805 transportId = query.value(1).toInt(); 731 806 serviceId = query.value(2).toInt(); 807 LOG(VB_MHEG, LOG_INFO, QString("[mhi] GetServiceInfo %1 => NID=%2 TID=%3 SID=%4") 808 .arg(channelId).arg(netId).arg(transportId).arg(serviceId)); 732 809 return true; 733 810 } 734 else return false; 811 812 LOG(VB_MHEG, LOG_WARNING, QString("[mhi] GetServiceInfo %1 failed").arg(channelId)); 813 return false; 735 814 } 736 815 737 816 bool MHIContext::TuneTo(int channel, int tuneinfo) 738 817 { 739 LOG(VB_MHEG, LOG_INFO, QString("[mhi] TuneTo %1 0x%2")740 .arg(channel).arg(tuneinfo,0,16));741 742 818 if (!m_isLive) 819 { 820 LOG(VB_MHEG, LOG_WARNING, QString("[mhi] Can't TuneTo %1 0x%2 while not live") 821 .arg(channel).arg(tuneinfo,0,16)); 743 822 return false; // Can't tune if this is a recording. 823 } 744 824 825 LOG(VB_GENERAL, LOG_INFO, QString("[mhi] TuneTo %1 0x%2") 826 .arg(channel).arg(tuneinfo,0,16)); 745 827 m_tuneinfo.append(tuneinfo); 746 828 747 829 // Post an event requesting a channel change. … … bool MHIContext::TuneTo(int channel, int tuneinfo) 754 836 return true; 755 837 } 756 838 757 // Begin playing audio from the specified stream 758 bool MHIContext::BeginAudio(const QString &stream, int tag) 839 840 // Begin playing the specified stream 841 bool MHIContext::BeginStream(const QString &stream, MHStream *notify) 759 842 { 760 LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginAudio %1 %2").arg(stream).arg(tag)); 843 LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginStream %1 0x%2") 844 .arg(stream).arg((quintptr)notify,0,16)); 845 846 m_audioTag = -1; 847 m_videoTag = -1; 848 m_notify = notify; 849 850 if (stream.startsWith("http://") || stream.startsWith("https://")) 851 { 852 m_currentStream = -1; 853 854 // The url is sometimes only http:// during stream startup 855 if (QUrl(stream).authority().isEmpty()) 856 return false; 857 858 return m_parent->GetNVP()->SetStream(stream); 859 } 761 860 762 861 int chan = GetChannelIndex(stream); 763 862 if (chan < 0) 764 863 return false; 765 864 if (VERBOSE_LEVEL_CHECK(VB_MHEG, LOG_ANY)) 865 { 866 int netId, origNetId, transportId, serviceId; 867 GetServiceInfo(chan, netId, origNetId, transportId, serviceId); 868 } 869 766 870 if (chan != m_currentStream) 767 871 { 768 // We have to tune to the channel where the audiois to be found.872 // We have to tune to the channel where the stream is to be found. 769 873 // Because the audio and video are both components of an MHEG stream 770 874 // they will both be on the same channel. 771 875 m_currentStream = chan; 772 m_audioTag = tag;773 876 return TuneTo(chan, kTuneKeepChnl|kTuneQuietly|kTuneKeepApp); 774 877 } 878 879 return true; 880 } 775 881 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 882 void MHIContext::EndStream() 883 { 884 LOG(VB_MHEG, LOG_INFO, QString("[mhi] EndStream 0x%1") 885 .arg((quintptr)m_notify,0,16) ); 886 887 m_notify = 0; 888 (void)m_parent->GetNVP()->SetStream(QString()); 889 } 890 891 // Callback from MythPlayer when a stream starts or stops 892 bool MHIContext::StreamStarted(bool bStarted) 893 { 894 if (!m_engine || !m_notify) 781 895 return false; 896 897 LOG(VB_MHEG, LOG_INFO, QString("[mhi] Stream 0x%1 %2") 898 .arg((quintptr)m_notify,0,16).arg(bStarted ? "started" : "stopped")); 899 900 m_engine->StreamStarted(m_notify, bStarted); 901 if (!bStarted) 902 m_notify = 0; 903 return m_currentStream == -1; // Return true if it's an http stream 782 904 } 783 905 906 // Begin playing audio 907 bool MHIContext::BeginAudio(int tag) 908 { 909 LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginAudio %1").arg(tag)); 910 911 if (tag < 0) 912 return true; // Leave it at the default. 913 914 m_audioTag = tag; 915 if (m_parent->GetNVP()) 916 return m_parent->GetNVP()->SetAudioByComponentTag(tag); 917 return false; 918 } 919 784 920 // Stop playing audio 785 void MHIContext::StopAudio( void)921 void MHIContext::StopAudio() 786 922 { 787 923 // Do nothing at the moment. 788 924 } 789 925 790 926 // Begin displaying video from the specified stream 791 bool MHIContext::BeginVideo( const QString &stream,int tag)927 bool MHIContext::BeginVideo(int tag) 792 928 { 793 LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginVideo %1 %2").arg(stream).arg(tag));929 LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginVideo %1").arg(tag)); 794 930 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 931 if (tag < 0) 806 932 return true; // Leave it at the default. 807 else if (m_parent->GetNVP()) 933 934 m_videoTag = tag; 935 if (m_parent->GetNVP()) 808 936 return m_parent->GetNVP()->SetVideoByComponentTag(tag); 809 810 937 return false; 811 938 } 812 813 // Stop displaying video814 void MHIContext::StopVideo( void)939 940 // Stop displaying video 941 void MHIContext::StopVideo() 815 942 { 816 943 // Do nothing at the moment. 817 944 } 945 946 // Get current stream position, -1 if unknown 947 long MHIContext::GetStreamPos() 948 { 949 return m_parent->GetNVP() ? m_parent->GetNVP()->GetStreamPos() : -1; 950 } 951 952 // Get current stream size, -1 if unknown 953 long MHIContext::GetStreamMaxPos() 954 { 955 return m_parent->GetNVP() ? m_parent->GetNVP()->GetStreamMaxPos() : -1; 956 } 957 958 // Set current stream position 959 long MHIContext::SetStreamPos(long pos) 960 { 961 return m_parent->GetNVP() ? m_parent->GetNVP()->SetStreamPos(pos) : -1; 962 } 963 964 // Play or pause a stream 965 void MHIContext::StreamPlay(bool play) 966 { 967 if (m_parent->GetNVP()) 968 m_parent->GetNVP()->StreamPlay(play); 969 } 818 970 819 971 // Create a new object to draw dynamic line art. 820 972 MHDLADisplay *MHIContext::CreateDynamicLineArt( … … void MHIContext::DrawRect(int xPos, int yPos, int width, int height, 866 1018 // and usually that will be the same as the origin of the bounding 867 1019 // box (clipRect). 868 1020 void MHIContext::DrawImage(int x, int y, const QRect &clipRect, 869 const QImage &qImage )1021 const QImage &qImage, bool bScaled /* = false */) 870 1022 { 871 1023 if (qImage.isNull()) 872 1024 return; 873 1025 874 1026 QRect imageRect(x, y, qImage.width(), qImage.height()); 875 QRect displayRect = QRect(clipRect.x(), clipRect.y(), 876 clipRect.width(), clipRect.height()) & imageRect; 1027 QRect displayRect = clipRect & imageRect; 877 1028 878 if ( displayRect == imageRect) // No clipping required1029 if (bScaled || displayRect == imageRect) // No clipping required 879 1030 { 880 1031 QImage q_scaled = 881 1032 qImage.scaled( … … void MHIContext::DrawImage(int x, int y, const QRect &clipRect, 889 1040 else if (!displayRect.isEmpty()) 890 1041 { // We must clip the image. 891 1042 QImage clipped = qImage.convertToFormat(QImage::Format_ARGB32) 892 .copy(displayRect.x() - x, displayRect.y() - y, 893 displayRect.width(), displayRect.height()); 1043 .copy(displayRect.translated(-x, -y)); 894 1044 QImage q_scaled = 895 1045 clipped.scaled( 896 1046 SCALED_X(displayRect.width()), … … void MHIBitmap::Draw(int x, int y, QRect rect, bool tiled) 1470 1620 tiledImage.setPixel(i, j, m_image.pixel(i % m_image.width(), j % m_image.height())); 1471 1621 } 1472 1622 } 1473 m_parent->DrawImage(rect.x(), rect.y(), rect, tiledImage );1623 m_parent->DrawImage(rect.x(), rect.y(), rect, tiledImage, true); 1474 1624 } 1475 1625 else 1476 1626 { 1477 m_parent->DrawImage(x, y, rect, m_image); 1627 // NB THe BBC expects bitmaps to be scaled, not clipped 1628 m_parent->DrawImage(x, y, rect, m_image, true); 1478 1629 } 1479 1630 } 1480 1631 … … void MHIBitmap::CreateFromPNG(const unsigned char *data, int length) 1493 1644 m_opaque = ! m_image.hasAlphaChannel(); 1494 1645 } 1495 1646 1647 // Create a bitmap from JPEG. 1648 //virtual 1649 void MHIBitmap::CreateFromJPEG(const unsigned char *data, int length) 1650 { 1651 m_image = QImage(); 1652 1653 if (!m_image.loadFromData(data, length, "JPG")) 1654 { 1655 m_image = QImage(); 1656 return; 1657 } 1658 1659 // Assume that if it has an alpha buffer then it's partly transparent. 1660 m_opaque = ! m_image.hasAlphaChannel(); 1661 } 1662 1496 1663 // Convert an MPEG I-frame into a bitmap. This is used as the way of 1497 1664 // sending still pictures. We convert the image to a QImage even 1498 1665 // though that actually means converting it from YUV and eventually -
mythtv/libs/libmythtv/mhi.h
diff --git a/mythtv/libs/libmythtv/mhi.h b/mythtv/libs/libmythtv/mhi.h index 2b10c8b..17dea0e 100644
a b using namespace std; 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; … … class MHIBitmap : public MHBitmapDisplay 231 251 /// Create bitmap from single I frame MPEG 232 252 virtual void CreateFromMPEG(const unsigned char *data, int length); 233 253 254 /// Create bitmap from JPEG 255 virtual void CreateFromJPEG(const unsigned char *data, int length); 256 234 257 /** \fn MHIBitmap::Draw(int,int,QRect,bool) 235 258 * \brief Draw the completed drawing onto the display. 236 259 * -
mythtv/libs/libmythtv/mythplayer.cpp
diff --git a/mythtv/libs/libmythtv/mythplayer.cpp b/mythtv/libs/libmythtv/mythplayer.cpp index 546c1ec..e9e9274 100644
a b using namespace std; 25 25 #include <QCoreApplication> 26 26 #include <QKeyEvent> 27 27 #include <QDir> 28 #include <QScopedPointer> 28 29 29 30 // MythTV headers 30 31 #include "mthread.h" … … using namespace std; 58 59 #include "mythimage.h" 59 60 #include "mythuiimage.h" 60 61 #include "mythlogging.h" 62 #include "icringbuffer.h" 61 63 62 64 extern "C" { 63 65 #include "vbitext/vbi.h" … … int MythPlayer::OpenFile(uint retries) 899 901 MythTimer peekTimer; peekTimer.start(); 900 902 while (player_ctx->buffer->Peek(testbuf, testreadsize) != testreadsize) 901 903 { 902 if (peekTimer.elapsed() > 1000 || bigTimer.elapsed() > timeout) 904 // NB need to allow for streams encountering network congestion 905 if (peekTimer.elapsed() > 30000 || bigTimer.elapsed() > timeout) 903 906 { 904 907 LOG(VB_GENERAL, LOG_ERR, LOC + 905 908 QString("OpenFile(): Could not read first %1 bytes of '%2'") … … int MythPlayer::OpenFile(uint retries) 909 912 return -1; 910 913 } 911 914 LOG(VB_GENERAL, LOG_WARNING, LOC + "OpenFile() waiting on data"); 912 usleep( 50 * 1000);915 usleep(150 * 1000); 913 916 } 914 917 915 918 player_ctx->LockPlayingInfo(__FILE__, __LINE__); … … void MythPlayer::DisplayPauseFrame(void) 1998 2001 SetBuffering(false); 1999 2002 2000 2003 RefreshPauseFrame(); 2004 PreProcessNormalFrame(); // Allow interactiveTV to draw on pause frame 2001 2005 2002 2006 osdLock.lock(); 2003 2007 videofiltersLock.lock(); … … bool MythPlayer::PrebufferEnoughFrames(int min_buffers) 2062 2066 // to recover from serious problems if frames get leaked. 2063 2067 DiscardVideoFrames(true); 2064 2068 } 2065 if (waited_for > 20000) // 20 seconds2069 if (waited_for > 30000) // 30 seconds for internet streamed media 2066 2070 { 2067 2071 LOG(VB_GENERAL, LOG_ERR, LOC + 2068 2072 "Waited too long for decoder to fill video buffers. Exiting.."); … … void MythPlayer::SwitchToProgram(void) 2415 2419 return; 2416 2420 } 2417 2421 2422 if (player_ctx->buffer->GetType() == ICRingBuffer::kRingBufferType) 2423 { 2424 // Restore original ringbuffer 2425 ICRingBuffer *ic = dynamic_cast< ICRingBuffer* >(player_ctx->buffer); 2426 player_ctx->buffer = ic->Take(); 2427 delete ic; 2428 } 2429 2418 2430 player_ctx->buffer->OpenFile( 2419 2431 pginfo->GetPlaybackURL(), RingBuffer::kLiveTVOpenTimeout); 2420 2432 … … void MythPlayer::JumpToProgram(void) 2522 2534 2523 2535 SendMythSystemPlayEvent("PLAY_CHANGED", pginfo); 2524 2536 2537 if (player_ctx->buffer->GetType() == ICRingBuffer::kRingBufferType) 2538 { 2539 // Restore original ringbuffer 2540 ICRingBuffer *ic = dynamic_cast< ICRingBuffer* >(player_ctx->buffer); 2541 player_ctx->buffer = ic->Take(); 2542 delete ic; 2543 } 2544 2525 2545 player_ctx->buffer->OpenFile( 2526 2546 pginfo->GetPlaybackURL(), RingBuffer::kLiveTVOpenTimeout); 2527 2547 … … void MythPlayer::EventLoop(void) 2714 2734 JumpToProgram(); 2715 2735 } 2716 2736 2737 // Change interactive stream if requested 2738 { QMutexLocker locker(&itvLock); 2739 if (!m_newStream.isEmpty()) 2740 { 2741 QString stream = m_newStream; 2742 m_newStream.clear(); 2743 locker.unlock(); 2744 JumpToStream(stream); 2745 }} 2746 2717 2747 // Disable fastforward if we are too close to the end of the buffer 2718 2748 if (ffrew_skip > 1 && (CalcMaxFFTime(100, false) < 100)) 2719 2749 { … … void MythPlayer::EventLoop(void) 2750 2780 } 2751 2781 2752 2782 // Handle end of file 2753 if (GetEof() )2783 if (GetEof() && !allpaused) 2754 2784 { 2755 if ( player_ctx->tvchain)2785 if (interactiveTV && interactiveTV->StreamStarted(false)) 2756 2786 { 2757 if (!allpaused && player_ctx->tvchain->HasNext()) 2758 { 2759 LOG(VB_GENERAL, LOG_NOTICE, LOC + "LiveTV forcing JumpTo 1"); 2760 player_ctx->tvchain->JumpToNext(true, 1); 2761 return; 2762 } 2787 Pause(); 2788 return; 2763 2789 } 2764 else if (!allpaused) 2790 2791 if (player_ctx->tvchain && player_ctx->tvchain->HasNext()) 2765 2792 { 2766 SetPlaying(false); 2793 LOG(VB_GENERAL, LOG_NOTICE, LOC + "LiveTV forcing JumpTo 1"); 2794 player_ctx->tvchain->JumpToNext(true, 1); 2767 2795 return; 2768 2796 } 2797 2798 SetPlaying(false); 2799 return; 2769 2800 } 2770 2801 2771 2802 // Handle rewind … … void MythPlayer::UnpauseDecoder(void) 2896 2927 2897 2928 int tries = 0; 2898 2929 unpauseDecoder = true; 2899 while (decoder Thread && !killdecoder && (tries++ < 100) &&2930 while (decoderPaused && decoderThread && !killdecoder && (tries++ < 10) && 2900 2931 !decoderThreadUnpause.wait(&decoderPauseLock, 100)) 2901 2932 { 2902 2933 LOG(VB_GENERAL, LOG_WARNING, LOC + … … bool MythPlayer::SetVideoByComponentTag(int tag) 4784 4815 return false; 4785 4816 } 4786 4817 4818 // Called from MHIContext::Begin/End/Stream on the MHIContext::StartMHEGEngine thread 4819 bool MythPlayer::SetStream(const QString &stream) 4820 { 4821 // The stream name is empty if the stream is closing 4822 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("SetStream '%1'").arg(stream)); 4823 4824 QMutexLocker locker(&itvLock); 4825 m_newStream = stream; 4826 m_newStream.detach(); 4827 // Stream will be changed by JumpToStream called from EventLoop 4828 // If successful will call interactiveTV->StreamStarted(); 4829 return !stream.isEmpty(); 4830 } 4831 4832 // Called from EventLoop pn the main application thread 4833 void MythPlayer::JumpToStream(const QString &stream) 4834 { 4835 LOG(VB_PLAYBACK, LOG_INFO, LOC + "JumpToStream - begin"); 4836 4837 if (stream.isEmpty()) 4838 return; // Shouldn't happen 4839 4840 Pause(); 4841 ResetCaptions(); 4842 4843 if (player_ctx->buffer->GetType() != ICRingBuffer::kRingBufferType) 4844 player_ctx->buffer = new ICRingBuffer(stream, player_ctx->buffer); 4845 else 4846 player_ctx->buffer->OpenFile(stream); 4847 4848 if (!player_ctx->buffer->IsOpen()) 4849 { 4850 LOG(VB_GENERAL, LOG_ERR, LOC + "JumpToStream buffer OpenFile failed"); 4851 SetEof(true); 4852 SetErrored(QObject::tr("Error opening remote stream buffer")); 4853 return; 4854 } 4855 4856 if (OpenFile(120) < 0) // 120 retries ~= 60 seconds 4857 { 4858 LOG(VB_GENERAL, LOG_ERR, LOC + "JumpToStream OpenFile failed."); 4859 SetEof(true); 4860 SetErrored(QObject::tr("Error opening remote stream")); 4861 return; 4862 } 4863 4864 SetEof(false); 4865 4866 if (player_ctx->tvchain) CheckTVChain(); 4867 //player_ctx->buffer->IgnoreLiveEOF(false); 4868 4869 Play(); 4870 ChangeSpeed(); 4871 4872 player_ctx->SetPlayerChangingBuffers(false); 4873 if (interactiveTV) interactiveTV->StreamStarted(); 4874 4875 LOG(VB_PLAYBACK, LOG_INFO, LOC + "JumpToStream - end"); 4876 } 4877 4878 static inline int SafeFPS(DecoderBase *decoder) 4879 { 4880 if (!decoder) 4881 return 25; 4882 double fps = decoder->GetFPS(); 4883 return fps > 0 ? (int)(fps + 0.5) : 25; 4884 } 4885 4886 long MythPlayer::GetStreamPos() 4887 { 4888 return (1000L * GetFramesPlayed()) / SafeFPS(decoder); 4889 } 4890 4891 long MythPlayer::GetStreamMaxPos() 4892 { 4893 uint kbps = decoder ? decoder->GetRawBitrate() : 0; 4894 long pos = GetStreamPos(), maxpos = 0; 4895 long long len = player_ctx->buffer->GetRealFileSize(); 4896 if (len > 0) 4897 { 4898 const uint kMin = 64; 4899 maxpos = (long)((8 * len) / (kbps < kMin ? kMin : kbps)); 4900 } 4901 4902 if (maxpos < pos) 4903 maxpos = pos; 4904 LOG(VB_PLAYBACK, LOG_INFO, LOC + 4905 QString("GetStreamMaxPos => %1 mS (%2 KB @ %3 KBPS)") 4906 .arg(maxpos).arg(len > 0 ? len/1024 : len).arg((kbps+4)/8) ); 4907 return maxpos; 4908 } 4909 4910 long MythPlayer::SetStreamPos(long ms) 4911 { 4912 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("SetStreamPos %1").arg(ms)); 4913 return JumpToFrame((uint64_t)((ms * SafeFPS(decoder)) / 1000)); 4914 } 4915 4916 void MythPlayer::StreamPlay(bool play) 4917 { 4918 if (play) 4919 Play(); 4920 else 4921 Pause(); 4922 } 4923 4787 4924 /** \fn MythPlayer::SetDecoder(DecoderBase*) 4788 4925 * \brief Sets the stream decoder, deleting any existing recorder. 4789 4926 */ -
mythtv/libs/libmythtv/mythplayer.h
diff --git a/mythtv/libs/libmythtv/mythplayer.h b/mythtv/libs/libmythtv/mythplayer.h index e30c3a7..234818f 100644
a b class MTV_PUBLIC MythPlayer 288 288 // Public MHEG/MHI stream selection 289 289 bool SetAudioByComponentTag(int tag); 290 290 bool SetVideoByComponentTag(int tag); 291 bool SetStream(const QString &); 292 long GetStreamPos(); // mS 293 long GetStreamMaxPos(); // mS 294 long SetStreamPos(long); // mS 295 void StreamPlay(bool play = true); 291 296 292 297 // LiveTV public stuff 293 298 void CheckTVChain(); … … class MTV_PUBLIC MythPlayer 560 565 // Private LiveTV stuff 561 566 void SwitchToProgram(void); 562 567 void JumpToProgram(void); 568 void JumpToStream(const QString&); 563 569 564 570 protected: 565 571 PlayerFlags playerFlags; … … class MTV_PUBLIC MythPlayer 695 701 InteractiveTV *interactiveTV; 696 702 bool itvEnabled; 697 703 QMutex itvLock; 704 QString m_newStream; // Guarded by itvLock 698 705 699 706 // OSD stuff 700 707 OSD *osd; -
new file mythtv/libs/libmythtv/netstream.cpp
diff --git a/mythtv/libs/libmythtv/netstream.cpp b/mythtv/libs/libmythtv/netstream.cpp new file mode 100644 index 0000000..6bef91a
- + 1 /* Network stream 2 * Copyright 2011 Lawrence Rust <lvr at softsystem dot co dot uk> 3 */ 4 #include "netstream.h" 5 6 // C/C++ lib 7 #include <cstdlib> 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 #if QT_VERSION >= 0x040700 699 switch (m.m_nam->networkAccessible()) 700 { 701 case QNetworkAccessManager::Accessible: return true; 702 case QNetworkAccessManager::NotAccessible: return false; 703 case QNetworkAccessManager::UnknownAccessibility: return true; 704 } 705 #else 706 // can't test network accessibility so assume it is available 707 return true; 708 #endif 709 return false; 710 } 711 712 // Time when URI was last written to cache or invalid if not cached. 713 // static 714 QDateTime NAMThread::GetLastModified(const QString &url) 715 { 716 NAMThread &m = manager(); 717 718 QMutexLocker locker(&m.m_mutex); 719 720 if (!m.m_nam) 721 return QDateTime(); // Invalid 722 723 QAbstractNetworkCache *cache = m.m_nam->cache(); 724 if (!cache) 725 return QDateTime(); // Invalid 726 727 QNetworkCacheMetaData meta = cache->metaData(QUrl(url)); 728 if (!meta.isValid()) 729 { 730 LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetLastModified('%1') not in cache") 731 .arg(url)); 732 return QDateTime(); // Invalid 733 } 734 735 // Check if expired 736 QDateTime const now(QDateTime::currentDateTime()); // local time 737 QDateTime expire = meta.expirationDate(); 738 if (expire.isValid() && expire.toLocalTime() < now) 739 { 740 LOG(VB_FILE, LOG_INFO, LOC + QString("GetLastModified('%1') past expiration %2") 741 .arg(url).arg(expire.toString())); 742 return QDateTime(); // Invalid 743 } 744 745 // Get time URI was modified (Last-Modified header) NB this may be invalid 746 QDateTime lastMod = meta.lastModified(); 747 748 QNetworkCacheMetaData::RawHeaderList headers = meta.rawHeaders(); 749 Q_FOREACH(const QNetworkCacheMetaData::RawHeader &h, headers) 750 { 751 // RFC 1123 date format: Thu, 01 Dec 1994 16:00:00 GMT 752 static const char kszFormat[] = "ddd, dd MMM yyyy HH:mm:ss 'GMT'"; 753 754 QString const first(h.first.toLower()); 755 if (first == "cache-control") 756 { 757 QString const second(h.second.toLower()); 758 if (second == "no-cache" || second == "no-store") 759 { 760 LOG(VB_FILE, LOG_INFO, LOC + 761 QString("GetLastModified('%1') Cache-Control disabled").arg(url)); 762 cache->remove(QUrl(url)); 763 return QDateTime(); // Invalid 764 } 765 } 766 else if (first == "date") 767 { 768 QDateTime d = QDateTime::fromString(h.second, kszFormat); 769 if (!d.isValid()) 770 { 771 LOG(VB_GENERAL, LOG_WARNING, LOC + 772 QString("GetLastModified invalid Date header '%1'") 773 .arg(h.second.constData())); 774 continue; 775 } 776 d.setTimeSpec(Qt::UTC); 777 lastMod = d; 778 } 779 } 780 781 LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetLastModified('%1') last modified %2") 782 .arg(url).arg(lastMod.toString())); 783 return lastMod; 784 } 785 786 /* End of file */ -
new file mythtv/libs/libmythtv/netstream.h
diff --git a/mythtv/libs/libmythtv/netstream.h b/mythtv/libs/libmythtv/netstream.h new file mode 100644 index 0000000..c740013
- + 1 /* Network stream 2 * Copyright 2011 Lawrence Rust <lvr at softsystem dot co dot uk> 3 */ 4 #ifndef NETSTREAM_H 5 #define NETSTREAM_H 6 7 #include <QList> 8 #include <QString> 9 #include <QByteArray> 10 #include <QObject> 11 #include <QMutex> 12 #include <QSemaphore> 13 #include <QThread> 14 #include <QNetworkRequest> 15 #include <QNetworkReply> 16 #include <QSslError> 17 #include <QWaitCondition> 18 #include <QQueue> 19 #include <QDateTime> 20 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 */ -
mythtv/libs/libmythtv/ringbuffer.cpp
diff --git a/mythtv/libs/libmythtv/ringbuffer.cpp b/mythtv/libs/libmythtv/ringbuffer.cpp index 6940300..a5fb4c1 100644
a b bool RingBuffer::IsNearEnd(double fps, uint vvf) const 415 415 // WARNING: readahead_frames can greatly overestimate or underestimate 416 416 // the number of frames available in the read ahead buffer 417 417 // when rh_frames is less than the keyframe distance. 418 if (fps == 0.) 419 return false; 418 420 double bytes_per_frame = kbits_per_sec * (1000.0/8.0) / fps; 421 if (bytes_per_frame == 0.) 422 return false; 419 423 double readahead_frames = sz / bytes_per_frame; 420 424 421 425 bool near_end = ((vvf + readahead_frames) < 10.0) || (sz < rbs*1.5); … … int RingBuffer::ReadBufAvail(void) const 454 458 return ret; 455 459 } 456 460 461 inline int RingBuffer::ReadBufUsed() const 462 { 463 return (bufferSize - 1) - ReadBufFree(); 464 } 465 466 inline bool RingBuffer::ReadsAllowed() const 467 { 468 return ateof || setswitchtonext || commserror || 469 // Ensure some hysteresis around fill_min 470 ReadBufUsed() >= (readsallowed ? 1 : fill_min); 471 } 472 457 473 /** \fn RingBuffer::ResetReadAhead(long long) 458 474 * \brief Restart the read-ahead thread at the 'newinternal' position. 459 475 * … … void RingBuffer::run(void) 896 912 } 897 913 } 898 914 899 int used = bufferSize - ReadBufFree();900 901 915 bool reads_were_allowed = readsallowed; 902 916 903 if ((0 == read_return) || (numfailures > 5) || 904 (readsallowed != (used >= fill_min || ateof || 905 setswitchtonext || commserror))) 917 if ((0 == read_return) || (numfailures > 5) || ReadsAllowed() != readsallowed) 906 918 { 907 919 // If readpos changes while the lock is released 908 920 // we should not handle the 0 read_return now. … … void RingBuffer::run(void) 913 925 914 926 commserror |= (numfailures > 5); 915 927 916 readsallowed = used >= fill_min || ateof || 917 setswitchtonext || commserror; 928 bool bReadsAllowed = ReadsAllowed(); 929 if (readsallowed != bReadsAllowed) 930 { 931 readsallowed = bReadsAllowed; 932 LOG(VB_FILE, LOG_INFO, LOC + (bReadsAllowed ? 933 QString("Reads allowed: %1 bytes available").arg(ReadBufUsed()) : 934 QString("Rebuffering %1..%2").arg(ReadBufUsed()).arg(fill_min)) ); 935 } 918 936 919 937 if (0 == read_return && old_readpos == readpos) 920 938 { … … void RingBuffer::run(void) 937 955 938 956 rwlock.unlock(); 939 957 rwlock.lockForRead(); 940 used = bufferSize - ReadBufFree();941 958 } 942 959 943 960 LOG(VB_FILE, LOG_DEBUG, LOC + "@ end of read ahead loop"); 944 961 945 962 if (!readsallowed || commserror || ateof || setswitchtonext || 946 (wanttoread <= used&& wanttoread > 0))963 (wanttoread <= ReadBufUsed() && wanttoread > 0)) 947 964 { 948 965 // To give other threads a good chance to handle these 949 966 // conditions, even if they are only requesting a read lock … … void RingBuffer::run(void) 957 974 { 958 975 // yield if we have nothing to do... 959 976 if (!request_pause && reads_were_allowed && 960 ( used >= fill_threshold || ateof || setswitchtonext))977 (ReadBufUsed() >= fill_threshold || ateof || setswitchtonext || ignoreliveeof)) 961 978 { 962 979 generalWait.wait(&rwlock, 50); 963 980 } … … void RingBuffer::run(void) 967 984 generalWait.wakeAll(); 968 985 rwlock.unlock(); 969 986 usleep(5 * 1000); 970 rwlock.lockForRead(); 987 rwlock.lockForRead(); 971 988 } 972 989 } 973 990 } … … bool RingBuffer::WaitForReadsAllowed(void) 1023 1040 while (!readsallowed && !stopreads && 1024 1041 !request_pause && !commserror && readaheadrunning) 1025 1042 { 1026 generalWait.wait(&rwlock, 1000);1027 if ( !readsallowed && t.elapsed() > 1000)1043 // The timeout should allow for congestion of internet streamed media 1044 if (t.elapsed() >= 30000) 1028 1045 { 1029 LOG(VB_GENERAL, LOG_WARNING, LOC + 1030 "Taking too long to be allowed to read.."); 1031 1032 if (t.elapsed() > 10000) 1033 { 1034 LOG(VB_GENERAL, LOG_ERR, LOC + "Took more than 10 seconds to " 1035 "be allowed to read, aborting."); 1036 return false; 1037 } 1046 LOG(VB_GENERAL, LOG_ERR, LOC + 1047 QString("Waited %1 seconds to be allowed to read, aborting.") 1048 .arg(t.elapsed()/1000) ); 1049 return false; 1038 1050 } 1051 1052 generalWait.wait(&rwlock, 250); 1039 1053 } 1040 1054 1041 return readsallowed; 1055 if (t.elapsed() >= 500) 1056 { 1057 LOG(VB_GENERAL, LOG_WARNING, LOC + 1058 QString("Waited %1 mS to be allowed to read (avail=%2 fill_min=%3)..") 1059 .arg(t.elapsed()).arg(ReadBufAvail()).arg(fill_min) ); 1060 } 1061 return true; 1042 1062 } 1043 1063 1044 1064 bool RingBuffer::WaitForAvail(int count) … … bool RingBuffer::WaitForAvail(int count) 1062 1082 generalWait.wakeAll(); 1063 1083 } 1064 1084 1065 MythTimer t; 1066 t.start();1067 while ( (avail < count) && !stopreads&&1068 !request_pause &&!commserror && readaheadrunning)1085 MythTimer t; t.start(); 1086 wanttoread = count; 1087 while (avail < count && !stopreads && !request_pause && 1088 !commserror && readaheadrunning) 1069 1089 { 1070 wanttoread = count; 1071 generalWait.wait(&rwlock, 250); 1072 avail = ReadBufAvail(); 1073 1074 if (ateof && avail < count) 1075 count = avail; 1076 1077 if (avail < count) 1090 uint elapsed = t.elapsed(); 1091 if (elapsed >= 10000) 1078 1092 { 1079 1093 int elapsed = t.elapsed(); 1080 1094 if (elapsed > 500 && low_buffers && avail >= fill_min) … … bool RingBuffer::WaitForAvail(int count) 1101 1115 return false; 1102 1116 } 1103 1117 } 1118 else if (elapsed >= 100 && avail) 1119 { 1120 LOG(VB_GENERAL, LOG_INFO, LOC + 1121 QString("Waited %1 mS for %2 bytes (wanted %3)") 1122 .arg(elapsed).arg(avail).arg(count) ); 1123 count = avail; 1124 generalWait.wakeAll(); 1125 break; 1126 } 1127 1128 generalWait.wait(&rwlock, 100); 1129 avail = ReadBufAvail(); 1104 1130 } 1105 1131 1106 1132 wanttoread = 0; … … int RingBuffer::ReadDirect(void *buf, int count, bool peek) 1162 1188 if (new_pos != old_pos) 1163 1189 { 1164 1190 LOG(VB_GENERAL, LOG_ERR, LOC + 1165 QString(" Peek() Failed to return from new "1191 QString("Seek() Failed to return from new " 1166 1192 "position %1 to old position %2, now " 1167 1193 "at position %3") 1168 1194 .arg(old_pos - ret).arg(old_pos).arg(new_pos)); … … int RingBuffer::ReadDirect(void *buf, int count, bool peek) 1182 1208 */ 1183 1209 int RingBuffer::ReadPriv(void *buf, int count, bool peek) 1184 1210 { 1185 QString loc_desc = QString("ReadPriv(..%1, %2)")1211 const QString loc_desc = QString("ReadPriv(..%1, %2)") 1186 1212 .arg(count).arg(peek?"peek":"normal"); 1187 LOG(VB_FILE, LOG_DEBUG, LOC + loc_desc +1188 QString(" @%1 -- begin").arg(rbrpos));1189 1213 1190 1214 rwlock.lockForRead(); 1215 1216 LOG(VB_FILE, LOG_DEBUG, LOC + loc_desc + 1217 QString(" @%1 avail=%2 -- begin").arg(rbrpos).arg(ReadBufAvail())); 1218 1191 1219 if (writemode) 1192 1220 { 1193 1221 LOG(VB_GENERAL, LOG_ERR, LOC + loc_desc + … … int RingBuffer::ReadPriv(void *buf, int count, bool peek) 1218 1246 if (request_pause || stopreads || 1219 1247 !readaheadrunning || (ignorereadpos >= 0)) 1220 1248 { 1249 LOG(VB_FILE, LOG_DEBUG, LOC + loc_desc + " -- direct read"); 1221 1250 int ret = ReadDirect(buf, count, peek); 1222 1251 LOG(VB_FILE, LOG_DEBUG, LOC + loc_desc + 1223 1252 QString(": ReadDirect checksum %1") … … int RingBuffer::ReadPriv(void *buf, int count, bool peek) 1232 1261 if (!WaitForReadsAllowed()) 1233 1262 { 1234 1263 LOG(VB_FILE, LOG_NOTICE, LOC + loc_desc + ": !WaitForReadsAllowed()"); 1235 rwlock.unlock(); 1236 stopreads = true; // this needs to be outside the lock 1237 rwlock.lockForWrite(); 1238 wanttoread = 0; 1264 // NB don't set stopreads or else the next ReadPriv will call ReadDirect 1265 // which, if there's any readahead, will cause data to be returned out 1266 // of sequence 1239 1267 rwlock.unlock(); 1240 1268 return 0; 1241 1269 } -
mythtv/libs/libmythtv/ringbuffer.h
diff --git a/mythtv/libs/libmythtv/ringbuffer.h b/mythtv/libs/libmythtv/ringbuffer.h index 45bd956..8c1a710 100644
a b enum RingBufferType 38 38 kRingBuffer_DVD, 39 39 kRingBuffer_BD, 40 40 kRingBuffer_HTTP, 41 kRingBuffer_MHEG 41 42 }; 42 43 43 44 class MTV_PUBLIC RingBuffer : protected MThread 44 45 { 46 friend class ICRingBuffer; 47 45 48 public: 46 49 static RingBuffer *Create(const QString &lfilename, bool write, 47 50 bool usereadahead = true, … … class MTV_PUBLIC RingBuffer : protected MThread 84 87 virtual bool IsBookmarkAllowed(void) { return true; } 85 88 virtual int BestBufferSize(void) { return 32768; } 86 89 static QString BitrateToString(uint64_t rate, bool hz = false); 90 RingBufferType GetType() const { return type; } 87 91 88 92 // DVD and bluray methods 89 93 bool IsDisc(void) const { return IsDVD() || IsBD(); } … … class MTV_PUBLIC RingBuffer : protected MThread 167 171 168 172 int ReadBufFree(void) const; 169 173 int ReadBufAvail(void) const; 174 int ReadBufUsed() const; 175 bool ReadsAllowed() const; 170 176 171 177 void ResetReadAhead(long long newinternal); 172 178 void KillReadAheadThread(void);
