Ticket #10019: iplayer-master.diff
| File iplayer-master.diff, 124.4 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/avformatdecoder.cpp
diff --git a/mythtv/libs/libmythtv/avformatdecoder.cpp b/mythtv/libs/libmythtv/avformatdecoder.cpp index 89cc07b..c70a472 100644
a b int AvFormatDecoder::ScanStreams(bool novideo) 2151 2151 } 2152 2152 } 2153 2153 2154 if ((uint)ic->bit_rate > bitrate) 2155 bitrate = (uint)ic->bit_rate; 2156 2154 2157 if (bitrate > 0) 2155 2158 { 2156 2159 bitrate = (bitrate + 999) / 1000; -
new file mythtv/libs/libmythtv/icringbuffer.cpp
diff --git a/mythtv/libs/libmythtv/icringbuffer.cpp b/mythtv/libs/libmythtv/icringbuffer.cpp new file mode 100644 index 0000000..d23bc3c
- + 1 #include "icringbuffer.h" 2 3 #include <stdio.h> // SEEK_SET 4 5 #include <QScopedPointer> 6 #include <QWriteLocker> 7 8 #include "netstream.h" 9 #include "mythlogging.h" 10 11 12 #define LOC QString("ICRingBuf ") 13 14 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, NetStream::kNeverCache)); 42 if (!stream || !stream->IsOpen()) 43 { 44 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to open %1").arg(url) ); 45 return false; 46 } 47 48 if (!stream->WaitTillReady(30000)) 49 { 50 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Stream not ready%1").arg(url) ); 51 return false; 52 } 53 54 if (m_parent) 55 m_parent->Pause(); 56 57 QWriteLocker locker(&rwlock); 58 59 safefilename = url; 60 filename = url; 61 62 delete m_stream; 63 m_stream = stream.take(); 64 65 // The initial bitrate needs to be set with consideration for low bit rate 66 // streams (e.g. radio @ 64Kbps) such that fill_min bytes are received 67 // in a reasonable time period to enable decoders to peek the first few KB 68 // to determine type & settings. 69 rawbitrate = 128; // remotefile 70 CalcReadAheadThresh(); 71 72 locker.unlock(); 73 Reset(true, false, true); 74 75 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Opened %1").arg(url)); 76 return true; 77 } 78 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 if (!m_stream) 87 return -1; 88 89 // lockForWrite takes priority over lockForRead, so this will 90 // take priority over the lockForRead in the read ahead thread. 91 if (!has_lock) 92 rwlock.lockForWrite(); 93 94 poslock.lockForWrite(); 95 96 long long ret; 97 98 // Optimize no-op seeks 99 if (readaheadrunning && 100 ((whence == SEEK_SET && pos == readpos) || 101 (whence == SEEK_CUR && pos == 0))) 102 { 103 ret = readpos; 104 105 poslock.unlock(); 106 if (!has_lock) 107 rwlock.unlock(); 108 109 return ret; 110 } 111 112 switch (whence) 113 { 114 case SEEK_SET: 115 break; 116 case SEEK_CUR: 117 pos += m_stream->GetReadPosition(); 118 break; 119 case SEEK_END: 120 pos += m_stream->GetSize(); 121 break; 122 default: 123 errno = EINVAL; 124 ret = -1; 125 goto err; 126 } 127 128 ret = m_stream->Seek(pos); 129 if (ret >= 0) 130 { 131 readpos = ret; 132 133 ignorereadpos = -1; 134 135 if (readaheadrunning) 136 ResetReadAhead(readpos); 137 138 readAdjust = 0; 139 } 140 141 err: 142 poslock.unlock(); 143 144 generalWait.wakeAll(); 145 146 if (!has_lock) 147 rwlock.unlock(); 148 149 return ret; 150 } 151 152 int ICRingBuffer::safe_read(void *data, uint sz) 153 { 154 return m_stream ? m_stream->safe_read(data, sz, 1000) : (ateof = true, 0); 155 } 156 157 long long ICRingBuffer::GetRealFileSize(void) const 158 { 159 return m_stream ? m_stream->GetSize() : -1; 160 } 161 162 // Take ownership of parent RingBuffer 163 RingBuffer *ICRingBuffer::Take() 164 { 165 RingBuffer *parent = m_parent; 166 if (parent && IsOpen()) 167 parent->Unpause(); 168 m_parent = 0; 169 return parent; 170 } 171 172 // End of file -
new file mythtv/libs/libmythtv/icringbuffer.h
diff --git a/mythtv/libs/libmythtv/icringbuffer.h b/mythtv/libs/libmythtv/icringbuffer.h new file mode 100644 index 0000000..dc7585d
- + 1 #ifndef ICRINGBUFFER_H 2 #define ICRINGBUFFER_H 3 4 #include "ringbuffer.h" 5 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 false; } 24 virtual bool IsSeekingAllowed(void) { return true; } 25 virtual bool IsBookmarkAllowed(void) { return false; } 26 27 protected: 28 virtual int safe_read(void *data, uint sz); 29 30 // Operations 31 public: 32 // Take ownership of parent RingBuffer 33 RingBuffer *Take(); 34 35 private: 36 NetStream *m_stream; 37 RingBuffer *m_parent; // parent RingBuffer 38 }; 39 40 #endif // ICRINGBUFFER_H -
mythtv/libs/libmythtv/interactivetv.cpp
diff --git a/mythtv/libs/libmythtv/interactivetv.cpp b/mythtv/libs/libmythtv/interactivetv.cpp index 7ed2e4b..24cd1a6 100644
a b InteractiveTV::InteractiveTV(MythPlayer *nvp) 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 b26cbd0..27974ad 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 405fff9..902b778 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 { 430 432 SOURCES += dsmcc.cpp dsmcccache.cpp 431 433 SOURCES += dsmccbiop.cpp dsmccobjcarousel.cpp 432 434 435 # MHEG interaction channel 436 HEADERS += mhegic.h netstream.h 437 SOURCES += mhegic.cpp netstream.cpp 438 433 439 # MHEG/MHI stuff 434 440 HEADERS += interactivetv.h mhi.h 435 441 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 8370a72..1115be4 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) 359 361 // Called by the engine to check for the presence of an object in the carousel. 360 362 bool MHIContext::CheckCarouselObject(QString objectPath) 361 363 { 364 if (objectPath.startsWith("http:") || objectPath.startsWith("https:")) 365 { 366 // TODO verify access to server in carousel file auth.servers 367 // TODO use TLS cert from carousel auth.tls.<x> 368 return m_ic.CheckFile(objectPath); 369 } 370 362 371 QStringList path = objectPath.split(QChar('/'), QString::SkipEmptyParts); 363 372 QByteArray result; // Unused 364 373 int res = m_dsmcc->GetDSMCCObject(path, result); … … bool MHIContext::CheckCarouselObject(QString objectPath) 368 377 // Called by the engine to request data from the carousel. 369 378 bool MHIContext::GetCarouselData(QString objectPath, QByteArray &result) 370 379 { 380 bool const isIC = objectPath.startsWith("http:") || objectPath.startsWith("https:"); 381 371 382 // Get the path components. The string will normally begin with "//" 372 383 // since this is an absolute path but that will be removed by split. 373 384 QStringList path = objectPath.split(QChar('/'), QString::SkipEmptyParts); … … bool MHIContext::GetCarouselData(QString objectPath, QByteArray &result) 376 387 // the result. 377 388 378 389 QMutexLocker locker(&m_runLock); 390 bool bReported = false; 379 391 QTime t; t.start(); 380 392 while (!m_stop) 381 393 { 382 394 locker.unlock(); 383 395 384 int res = m_dsmcc->GetDSMCCObject(path, result); 385 if (res == 0) 386 return true; // Found it 387 else if (res < 0) 388 return false; // Not there. 389 else if (t.elapsed() > 60000) // TODO get this from carousel info 390 return false; // Not there. 396 if (isIC) 397 { 398 // TODO verify access to server in carousel file auth.servers 399 // TODO use TLS cert from carousel file auth.tls.<x> 400 switch (m_ic.GetFile(objectPath, result)) 401 { 402 case MHInteractionChannel::kSuccess: 403 if (bReported) 404 LOG(VB_MHEG, LOG_INFO, QString("[mhi] Received %1").arg(objectPath)); 405 return true; 406 case MHInteractionChannel::kError: 407 if (bReported) 408 LOG(VB_MHEG, LOG_INFO, QString("[mhi] Not found %1").arg(objectPath)); 409 return false; 410 case MHInteractionChannel::kPending: 411 break; 412 } 413 } 414 else 415 { 416 int res = m_dsmcc->GetDSMCCObject(path, result); 417 if (res == 0) 418 { 419 if (bReported) 420 LOG(VB_MHEG, LOG_INFO, QString("[mhi] Received %1").arg(objectPath)); 421 return true; // Found it 422 } 423 // NB don't exit if -1 (not present) is returned as the object may 424 // arrive later. Exiting can cause the inital app to not be found 425 } 426 427 if (t.elapsed() > 60000) // TODO get this from carousel info 428 return false; // Not there. 391 429 // Otherwise we block. 392 // Process DSMCC packets then block for a second or until we receive 430 if (!bReported) 431 { 432 bReported = true; 433 LOG(VB_MHEG, LOG_INFO, QString("[mhi] Waiting for %1").arg(objectPath)); 434 } 435 // Process DSMCC packets then block for a while or until we receive 393 436 // some more packets. We should eventually find out if this item is 394 437 // present. 395 438 ProcessDSMCCQueue(); 396 439 397 440 locker.relock(); 398 if (!m_stop) 399 m_engine_wait.wait(locker.mutex(), 1000); 441 m_engine_wait.wait(locker.mutex(), 300); 400 442 } 401 443 return false; // Stop has been set. Say the object isn't present. 402 444 } 403 445 404 // Called from tv_play when a key is pressed. 405 // If it is one in the current profile we queue it for the engine 406 // and return true otherwise we return false. 407 bool MHIContext::OfferKey(QString key) 446 // Mapping from key name & UserInput register to UserInput EventData 447 class MHKeyLookup 408 448 { 409 int action = 0; 410 QMutexLocker locker(&m_keyLock); 449 typedef QPair< QString, int /*UserInput register*/ > key_t; 450 451 public: 452 MHKeyLookup(); 453 454 int Find(const QString &name, int reg) const 455 { return m_map.value(key_t(name,reg), 0); } 411 456 457 private: 458 void key(const QString &name, int code, int r1, 459 int r2=0, int r3=0, int r4=0, int r5=0, int r6=0, int r7=0, int r8=0, int r9=0); 460 461 QHash<key_t,int /*EventData*/ > m_map; 462 }; 463 464 void MHKeyLookup::key(const QString &name, int code, int r1, 465 int r2, int r3, int r4, int r5, int r6, int r7, int r8, int r9) 466 { 467 m_map.insert(key_t(name,r1), code); 468 if (r2 > 0) 469 m_map.insert(key_t(name,r2), code); 470 if (r3 > 0) 471 m_map.insert(key_t(name,r3), code); 472 if (r4 > 0) 473 m_map.insert(key_t(name,r4), code); 474 if (r5 > 0) 475 m_map.insert(key_t(name,r5), code); 476 if (r6 > 0) 477 m_map.insert(key_t(name,r6), code); 478 if (r7 > 0) 479 m_map.insert(key_t(name,r7), code); 480 if (r8 > 0) 481 m_map.insert(key_t(name,r8), code); 482 if (r9 > 0) 483 m_map.insert(key_t(name,r9), code); 484 } 485 486 MHKeyLookup::MHKeyLookup() 487 { 412 488 // This supports the UK and NZ key profile registers. 413 489 // The UK uses 3, 4 and 5 and NZ 13, 14 and 15. These are 414 490 // similar but the NZ profile also provides an EPG key. 491 // ETSI ES 202 184 V2.2.1 (2011-03) adds group 6 for ICE. 492 // The BBC use group 7 for ICE 493 key(ACTION_UP, 1, 4,5,6,7,14,15); 494 key(ACTION_DOWN, 2, 4,5,6,7,14,15); 495 key(ACTION_LEFT, 3, 4,5,6,7,14,15); 496 key(ACTION_RIGHT, 4, 4,5,6,7,14,15); 497 key(ACTION_0, 5, 4,6,7,14); 498 key(ACTION_1, 6, 4,6,7,14); 499 key(ACTION_2, 7, 4,6,7,14); 500 key(ACTION_3, 8, 4,6,7,14); 501 key(ACTION_4, 9, 4,6,7,14); 502 key(ACTION_5, 10, 4,6,7,14); 503 key(ACTION_6, 11, 4,6,7,14); 504 key(ACTION_7, 12, 4,6,7,14); 505 key(ACTION_8, 13, 4,6,7,14); 506 key(ACTION_9, 14, 4,6,7,14); 507 key(ACTION_SELECT, 15, 4,5,6,7,14,15); 508 key(ACTION_TEXTEXIT, 16, 3,4,5,6,7,13,14,15); // 16= Cancel 509 // 17= help 510 // 18..99 reserved by DAVIC 511 key(ACTION_MENURED, 100, 3,4,5,6,7,13,14,15); 512 key(ACTION_MENUGREEN, 101, 3,4,5,6,7,13,14,15); 513 key(ACTION_MENUYELLOW, 102, 3,4,5,6,7,13,14,15); 514 key(ACTION_MENUBLUE, 103, 3,4,5,6,7,13,14,15); 515 key(ACTION_MENUTEXT, 104, 3,4,5,6,7); 516 key(ACTION_MENUTEXT, 105, 13,14,15); // NB from original Myth code 517 // 105..119 reserved for future spec 518 key(ACTION_STOP, 120, 6,7); 519 key(ACTION_PLAY, 121, 6,7); 520 key(ACTION_PAUSE, 122, 6,7); 521 key(ACTION_JUMPFFWD, 123, 6,7); // 123= Skip Forward 522 key(ACTION_JUMPRWND, 124, 6,7); // 124= Skip Back 523 #if 0 // These conflict with left & right 524 key(ACTION_SEEKFFWD, 125, 6,7); // 125= Fast Forward 525 key(ACTION_SEEKRWND, 126, 6,7); // 126= Rewind 526 #endif 527 key(ACTION_PLAYBACK, 127, 6,7); 528 // 128..256 reserved for future spec 529 // 257..299 vendor specific 530 key(ACTION_MENUEPG, 300, 13,14,15); 531 // 301.. Vendor specific 532 } 415 533 416 if (key == ACTION_UP) 417 { 418 if (m_keyProfile == 4 || m_keyProfile == 5 || 419 m_keyProfile == 14 || m_keyProfile == 15) 420 action = 1; 421 } 422 else if (key == ACTION_DOWN) 423 { 424 if (m_keyProfile == 4 || m_keyProfile == 5 || 425 m_keyProfile == 14 || m_keyProfile == 15) 426 action = 2; 427 } 428 else if (key == ACTION_LEFT) 429 { 430 if (m_keyProfile == 4 || m_keyProfile == 5 || 431 m_keyProfile == 14 || m_keyProfile == 15) 432 action = 3; 433 } 434 else if (key == ACTION_RIGHT) 435 { 436 if (m_keyProfile == 4 || m_keyProfile == 5 || 437 m_keyProfile == 14 || m_keyProfile == 15) 438 action = 4; 439 } 440 else if (key == ACTION_0 || key == ACTION_1 || key == ACTION_2 || 441 key == ACTION_3 || key == ACTION_4 || key == ACTION_5 || 442 key == ACTION_6 || key == ACTION_7 || key == ACTION_8 || 443 key == ACTION_9) 444 { 445 if (m_keyProfile == 4 || m_keyProfile == 14) 446 action = key.toInt() + 5; 447 } 448 else if (key == ACTION_SELECT) 449 { 450 if (m_keyProfile == 4 || m_keyProfile == 5 || 451 m_keyProfile == 14 || m_keyProfile == 15) 452 action = 15; 453 } 454 else if (key == ACTION_TEXTEXIT) 455 action = 16; 456 else if (key == ACTION_MENURED) 457 action = 100; 458 else if (key == ACTION_MENUGREEN) 459 action = 101; 460 else if (key == ACTION_MENUYELLOW) 461 action = 102; 462 else if (key == ACTION_MENUBLUE) 463 action = 103; 464 else if (key == ACTION_MENUTEXT) 465 action = m_keyProfile > 12 ? 105 : 104; 466 else if (key == ACTION_MENUEPG) 467 action = m_keyProfile > 12 ? 300 : 0; 468 469 if (action != 0) 470 { 471 m_keyQueue.enqueue(action); 472 LOG(VB_GENERAL, LOG_INFO, QString("Adding MHEG key %1:%2:%3").arg(key) 473 .arg(action).arg(m_keyQueue.size())); 474 locker.unlock(); 475 QMutexLocker locker2(&m_runLock); 476 m_engine_wait.wakeAll(); 477 return true; 478 } 479 480 return false; 534 // Called from tv_play when a key is pressed. 535 // If it is one in the current profile we queue it for the engine 536 // and return true otherwise we return false. 537 bool MHIContext::OfferKey(QString key) 538 { 539 static const MHKeyLookup s_keymap; 540 int action = s_keymap.Find(key, m_keyProfile); 541 if (action == 0) 542 return false; 543 544 LOG(VB_GENERAL, LOG_INFO, QString("[mhi] Adding MHEG key %1:%2:%3") 545 .arg(key).arg(action).arg(m_keyQueue.size()) ); 546 { QMutexLocker locker(&m_keyLock); 547 m_keyQueue.enqueue(action);} 548 QMutexLocker locker2(&m_runLock); 549 m_engine_wait.wakeAll(); 550 return true; 481 551 } 482 552 483 553 void MHIContext::Reinit(const QRect &display) … … void MHIContext::Reinit(const QRect &display) 492 562 493 563 void MHIContext::SetInputRegister(int num) 494 564 { 565 LOG(VB_MHEG, LOG_INFO, QString("[mhi] SetInputRegister %1").arg(num)); 495 566 QMutexLocker locker(&m_keyLock); 496 567 m_keyQueue.clear(); 497 568 m_keyProfile = num; 498 569 } 499 570 571 int MHIContext::GetICStatus() 572 { 573 // 0= Active, 1= Inactive, 2= Disabled 574 return m_ic.status(); 575 } 500 576 501 577 // Called by the video player to redraw the image. 502 578 void MHIContext::UpdateOSD(InteractiveScreen *osdWindow, … … int MHIContext::GetChannelIndex(const QString &str) 701 777 nResult = query.value(0).toInt(); 702 778 } 703 779 else if (str == "rec://svc/cur") 704 nResult = m_currentStream ;780 nResult = m_currentStream > 0 ? m_currentStream : m_currentChannel; 705 781 else if (str == "rec://svc/def") 706 782 nResult = m_currentChannel; 707 783 else if (str.startsWith("rec://")) … … int MHIContext::GetChannelIndex(const QString &str) 717 793 bool MHIContext::GetServiceInfo(int channelId, int &netId, int &origNetId, 718 794 int &transportId, int &serviceId) 719 795 { 720 LOG(VB_MHEG, LOG_INFO, QString("[mhi] GetServiceInfo %1").arg(channelId));721 796 MSqlQuery query(MSqlQuery::InitCon()); 722 797 query.prepare("SELECT networkid, transportid, serviceid " 723 798 "FROM channel, dtv_multiplex " … … bool MHIContext::GetServiceInfo(int channelId, int &netId, int &origNetId, 730 805 origNetId = netId; // We don't have this in the database. 731 806 transportId = query.value(1).toInt(); 732 807 serviceId = query.value(2).toInt(); 808 LOG(VB_MHEG, LOG_INFO, QString("[mhi] GetServiceInfo %1 => NID=%2 TID=%3 SID=%4") 809 .arg(channelId).arg(netId).arg(transportId).arg(serviceId)); 733 810 return true; 734 811 } 735 else return false; 812 813 LOG(VB_MHEG, LOG_WARNING, QString("[mhi] GetServiceInfo %1 failed").arg(channelId)); 814 return false; 736 815 } 737 816 738 817 bool MHIContext::TuneTo(int channel, int tuneinfo) 739 818 { 740 LOG(VB_MHEG, LOG_INFO, QString("[mhi] TuneTo %1 0x%2")741 .arg(channel).arg(tuneinfo,0,16));742 743 819 if (!m_isLive) 820 { 821 LOG(VB_MHEG, LOG_WARNING, QString("[mhi] Can't TuneTo %1 0x%2 while not live") 822 .arg(channel).arg(tuneinfo,0,16)); 744 823 return false; // Can't tune if this is a recording. 824 } 745 825 826 LOG(VB_GENERAL, LOG_INFO, QString("[mhi] TuneTo %1 0x%2") 827 .arg(channel).arg(tuneinfo,0,16)); 746 828 m_tuneinfo.append(tuneinfo); 747 829 748 830 // Post an event requesting a channel change. … … bool MHIContext::TuneTo(int channel, int tuneinfo) 755 837 return true; 756 838 } 757 839 758 // Begin playing audio from the specified stream 759 bool MHIContext::BeginAudio(const QString &stream, int tag) 840 841 // Begin playing the specified stream 842 bool MHIContext::BeginStream(const QString &stream, MHStream *notify) 760 843 { 761 LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginAudio %1 %2").arg(stream).arg(tag)); 844 LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginStream %1 0x%2") 845 .arg(stream).arg((quintptr)notify,0,16)); 846 847 m_audioTag = -1; 848 m_videoTag = -1; 849 m_notify = notify; 850 851 if (stream.startsWith("http://") || stream.startsWith("https://")) 852 { 853 m_currentStream = -1; 854 855 // The url is sometimes only http:// during stream startup 856 if (QUrl(stream).authority().isEmpty()) 857 return false; 858 859 return m_parent->GetNVP()->SetStream(stream); 860 } 762 861 763 862 int chan = GetChannelIndex(stream); 764 863 if (chan < 0) 765 864 return false; 766 865 if (VERBOSE_LEVEL_CHECK(VB_MHEG, LOG_ANY)) 866 { 867 int netId, origNetId, transportId, serviceId; 868 GetServiceInfo(chan, netId, origNetId, transportId, serviceId); 869 } 870 767 871 if (chan != m_currentStream) 768 872 { 769 // We have to tune to the channel where the audiois to be found.873 // We have to tune to the channel where the stream is to be found. 770 874 // Because the audio and video are both components of an MHEG stream 771 875 // they will both be on the same channel. 772 876 m_currentStream = chan; 773 m_audioTag = tag;774 877 return TuneTo(chan, kTuneKeepChnl|kTuneQuietly|kTuneKeepApp); 775 878 } 879 880 return true; 881 } 776 882 777 if (tag < 0) 778 return true; // Leave it at the default. 779 else if (m_parent->GetNVP()) 780 return m_parent->GetNVP()->SetAudioByComponentTag(tag); 781 else 883 void MHIContext::EndStream() 884 { 885 LOG(VB_MHEG, LOG_INFO, QString("[mhi] EndStream 0x%1") 886 .arg((quintptr)m_notify,0,16) ); 887 888 m_notify = 0; 889 (void)m_parent->GetNVP()->SetStream(QString()); 890 } 891 892 // Callback from MythPlayer when a stream starts or stops 893 bool MHIContext::StreamStarted(bool bStarted) 894 { 895 if (!m_engine || !m_notify) 782 896 return false; 897 898 LOG(VB_MHEG, LOG_INFO, QString("[mhi] Stream 0x%1 %2") 899 .arg((quintptr)m_notify,0,16).arg(bStarted ? "started" : "stopped")); 900 901 m_engine->StreamStarted(m_notify, bStarted); 902 if (!bStarted) 903 m_notify = 0; 904 return m_currentStream == -1; // Return true if it's an http stream 783 905 } 784 906 907 // Begin playing audio 908 bool MHIContext::BeginAudio(int tag) 909 { 910 LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginAudio %1").arg(tag)); 911 912 if (tag < 0) 913 return true; // Leave it at the default. 914 915 m_audioTag = tag; 916 if (m_parent->GetNVP()) 917 return m_parent->GetNVP()->SetAudioByComponentTag(tag); 918 return false; 919 } 920 785 921 // Stop playing audio 786 void MHIContext::StopAudio( void)922 void MHIContext::StopAudio() 787 923 { 788 924 // Do nothing at the moment. 789 925 } 790 926 791 927 // Begin displaying video from the specified stream 792 bool MHIContext::BeginVideo( const QString &stream,int tag)928 bool MHIContext::BeginVideo(int tag) 793 929 { 794 LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginVideo %1 %2").arg(stream).arg(tag));930 LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginVideo %1").arg(tag)); 795 931 796 int chan = GetChannelIndex(stream);797 if (chan < 0)798 return false;799 if (chan != m_currentStream)800 {801 // We have to tune to the channel where the video is to be found.802 m_currentStream = chan;803 m_videoTag = tag;804 return TuneTo(chan, kTuneKeepChnl|kTuneQuietly|kTuneKeepApp);805 }806 932 if (tag < 0) 807 933 return true; // Leave it at the default. 808 else if (m_parent->GetNVP()) 934 935 m_videoTag = tag; 936 if (m_parent->GetNVP()) 809 937 return m_parent->GetNVP()->SetVideoByComponentTag(tag); 810 811 938 return false; 812 939 } 813 814 // Stop displaying video815 void MHIContext::StopVideo( void)940 941 // Stop displaying video 942 void MHIContext::StopVideo() 816 943 { 817 944 // Do nothing at the moment. 818 945 } 946 947 // Get current stream position, -1 if unknown 948 long MHIContext::GetStreamPos() 949 { 950 return m_parent->GetNVP() ? m_parent->GetNVP()->GetStreamPos() : -1; 951 } 952 953 // Get current stream size, -1 if unknown 954 long MHIContext::GetStreamMaxPos() 955 { 956 return m_parent->GetNVP() ? m_parent->GetNVP()->GetStreamMaxPos() : -1; 957 } 958 959 // Set current stream position 960 long MHIContext::SetStreamPos(long pos) 961 { 962 return m_parent->GetNVP() ? m_parent->GetNVP()->SetStreamPos(pos) : -1; 963 } 964 965 // Play or pause a stream 966 void MHIContext::StreamPlay(bool play) 967 { 968 if (m_parent->GetNVP()) 969 m_parent->GetNVP()->StreamPlay(play); 970 } 819 971 820 972 // Create a new object to draw dynamic line art. 821 973 MHDLADisplay *MHIContext::CreateDynamicLineArt( … … void MHIContext::DrawRect(int xPos, int yPos, int width, int height, 867 1019 // and usually that will be the same as the origin of the bounding 868 1020 // box (clipRect). 869 1021 void MHIContext::DrawImage(int x, int y, const QRect &clipRect, 870 const QImage &qImage )1022 const QImage &qImage, bool bScaled /* = false */) 871 1023 { 872 1024 if (qImage.isNull()) 873 1025 return; 874 1026 875 1027 QRect imageRect(x, y, qImage.width(), qImage.height()); 876 QRect displayRect = QRect(clipRect.x(), clipRect.y(), 877 clipRect.width(), clipRect.height()) & imageRect; 1028 QRect displayRect = clipRect & imageRect; 878 1029 879 if ( displayRect == imageRect) // No clipping required1030 if (bScaled || displayRect == imageRect) // No clipping required 880 1031 { 881 1032 QImage q_scaled = 882 1033 qImage.scaled( … … void MHIContext::DrawImage(int x, int y, const QRect &clipRect, 890 1041 else if (!displayRect.isEmpty()) 891 1042 { // We must clip the image. 892 1043 QImage clipped = qImage.convertToFormat(QImage::Format_ARGB32) 893 .copy(displayRect.x() - x, displayRect.y() - y, 894 displayRect.width(), displayRect.height()); 1044 .copy(displayRect.translated(-x, -y)); 895 1045 QImage q_scaled = 896 1046 clipped.scaled( 897 1047 SCALED_X(displayRect.width()), … … void MHIBitmap::Draw(int x, int y, QRect rect, bool tiled) 1471 1621 tiledImage.setPixel(i, j, m_image.pixel(i % m_image.width(), j % m_image.height())); 1472 1622 } 1473 1623 } 1474 m_parent->DrawImage(rect.x(), rect.y(), rect, tiledImage );1624 m_parent->DrawImage(rect.x(), rect.y(), rect, tiledImage, true); 1475 1625 } 1476 1626 else 1477 1627 { 1478 m_parent->DrawImage(x, y, rect, m_image); 1628 // NB THe BBC expects bitmaps to be scaled, not clipped 1629 m_parent->DrawImage(x, y, rect, m_image, true); 1479 1630 } 1480 1631 } 1481 1632 … … void MHIBitmap::CreateFromPNG(const unsigned char *data, int length) 1494 1645 m_opaque = ! m_image.hasAlphaChannel(); 1495 1646 } 1496 1647 1648 // Create a bitmap from JPEG. 1649 //virtual 1650 void MHIBitmap::CreateFromJPEG(const unsigned char *data, int length) 1651 { 1652 m_image = QImage(); 1653 1654 if (!m_image.loadFromData(data, length, "JPG")) 1655 { 1656 m_image = QImage(); 1657 return; 1658 } 1659 1660 // Assume that if it has an alpha buffer then it's partly transparent. 1661 m_opaque = ! m_image.hasAlphaChannel(); 1662 } 1663 1497 1664 // Convert an MPEG I-frame into a bitmap. This is used as the way of 1498 1665 // sending still pictures. We convert the image to a QImage even 1499 1666 // though that actually means converting it from YUV and eventually -
mythtv/libs/libmythtv/mhi.h
diff --git a/mythtv/libs/libmythtv/mhi.h b/mythtv/libs/libmythtv/mhi.h index 36341f0..e54023b 100644
a b using namespace std; 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 5e93d4f..b5e32d2 100644
a b using namespace std; 58 58 #include "mythimage.h" 59 59 #include "mythuiimage.h" 60 60 #include "mythlogging.h" 61 #include "icringbuffer.h" 61 62 62 63 extern "C" { 63 64 #include "vbitext/vbi.h" … … int MythPlayer::OpenFile(uint retries) 876 877 if (!player_ctx || !player_ctx->buffer) 877 878 return -1; 878 879 879 livetv = player_ctx->tvchain ;880 livetv = player_ctx->tvchain && player_ctx->buffer->LiveMode(); 880 881 881 882 if (player_ctx->tvchain && 882 883 player_ctx->tvchain->GetCardType(player_ctx->tvchain->GetCurPos()) == … … void MythPlayer::DisplayPauseFrame(void) 2001 2002 SetBuffering(false); 2002 2003 2003 2004 RefreshPauseFrame(); 2005 PreProcessNormalFrame(); // Allow interactiveTV to draw on pause frame 2004 2006 2005 2007 osdLock.lock(); 2006 2008 videofiltersLock.lock(); … … void MythPlayer::SwitchToProgram(void) 2418 2420 return; 2419 2421 } 2420 2422 2423 if (player_ctx->buffer->GetType() == ICRingBuffer::kRingBufferType) 2424 { 2425 // Restore original ringbuffer 2426 ICRingBuffer *ic = dynamic_cast< ICRingBuffer* >(player_ctx->buffer); 2427 player_ctx->buffer = ic->Take(); 2428 delete ic; 2429 } 2430 2421 2431 player_ctx->buffer->OpenFile( 2422 2432 pginfo->GetPlaybackURL(), RingBuffer::kLiveTVOpenTimeout); 2423 2433 … … void MythPlayer::JumpToProgram(void) 2525 2535 2526 2536 SendMythSystemPlayEvent("PLAY_CHANGED", pginfo); 2527 2537 2538 if (player_ctx->buffer->GetType() == ICRingBuffer::kRingBufferType) 2539 { 2540 // Restore original ringbuffer 2541 ICRingBuffer *ic = dynamic_cast< ICRingBuffer* >(player_ctx->buffer); 2542 player_ctx->buffer = ic->Take(); 2543 delete ic; 2544 } 2545 2528 2546 player_ctx->buffer->OpenFile( 2529 2547 pginfo->GetPlaybackURL(), RingBuffer::kLiveTVOpenTimeout); 2530 2548 … … void MythPlayer::EventLoop(void) 2717 2735 JumpToProgram(); 2718 2736 } 2719 2737 2738 // Change interactive stream if requested 2739 { QMutexLocker locker(&itvLock); 2740 if (!m_newStream.isEmpty()) 2741 { 2742 QString stream = m_newStream; 2743 m_newStream.clear(); 2744 locker.unlock(); 2745 JumpToStream(stream); 2746 }} 2747 2720 2748 // Disable fastforward if we are too close to the end of the buffer 2721 2749 if (ffrew_skip > 1 && (CalcMaxFFTime(100, false) < 100)) 2722 2750 { … … void MythPlayer::EventLoop(void) 2753 2781 } 2754 2782 2755 2783 // Handle end of file 2756 if (GetEof() )2784 if (GetEof() && !allpaused) 2757 2785 { 2758 if (player_ctx->tvchain) 2786 #ifdef USING_MHEG 2787 if (interactiveTV && interactiveTV->StreamStarted(false)) 2759 2788 { 2760 if (!allpaused && player_ctx->tvchain->HasNext()) 2761 { 2762 LOG(VB_GENERAL, LOG_NOTICE, LOC + "LiveTV forcing JumpTo 1"); 2763 player_ctx->tvchain->JumpToNext(true, 1); 2764 return; 2765 } 2789 Pause(); 2790 return; 2766 2791 } 2767 else if (!allpaused) 2792 #endif 2793 if (player_ctx->tvchain && player_ctx->tvchain->HasNext()) 2768 2794 { 2769 SetPlaying(false); 2795 LOG(VB_GENERAL, LOG_NOTICE, LOC + "LiveTV forcing JumpTo 1"); 2796 player_ctx->tvchain->JumpToNext(true, 1); 2770 2797 return; 2771 2798 } 2799 2800 SetPlaying(false); 2801 return; 2772 2802 } 2773 2803 2774 2804 // Handle rewind … … bool MythPlayer::SetVideoByComponentTag(int tag) 4792 4822 return false; 4793 4823 } 4794 4824 4825 static inline double SafeFPS(DecoderBase *decoder) 4826 { 4827 if (!decoder) 4828 return 25; 4829 double fps = decoder->GetFPS(); 4830 return fps > 0 ? fps : 25.0; 4831 } 4832 4833 // Called from MHIContext::Begin/End/Stream on the MHIContext::StartMHEGEngine thread 4834 bool MythPlayer::SetStream(const QString &stream) 4835 { 4836 // The stream name is empty if the stream is closing 4837 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("SetStream '%1'").arg(stream)); 4838 4839 QMutexLocker locker(&itvLock); 4840 m_newStream = stream; 4841 m_newStream.detach(); 4842 // Stream will be changed by JumpToStream called from EventLoop 4843 // If successful will call interactiveTV->StreamStarted(); 4844 4845 if (stream.isEmpty() && player_ctx->tvchain && 4846 player_ctx->buffer->GetType() == ICRingBuffer::kRingBufferType) 4847 { 4848 // Restore livetv 4849 SetEof(true); 4850 player_ctx->tvchain->JumpToNext(false, 1); 4851 player_ctx->tvchain->JumpToNext(true, 1); 4852 } 4853 4854 return !stream.isEmpty(); 4855 } 4856 4857 // Called from EventLoop pn the main application thread 4858 void MythPlayer::JumpToStream(const QString &stream) 4859 { 4860 LOG(VB_PLAYBACK, LOG_INFO, LOC + "JumpToStream - begin"); 4861 4862 if (stream.isEmpty()) 4863 return; // Shouldn't happen 4864 4865 Pause(); 4866 ResetCaptions(); 4867 4868 ProgramInfo pginfo(stream); 4869 SetPlayingInfo(pginfo); 4870 4871 if (player_ctx->buffer->GetType() != ICRingBuffer::kRingBufferType) 4872 player_ctx->buffer = new ICRingBuffer(stream, player_ctx->buffer); 4873 else 4874 player_ctx->buffer->OpenFile(stream); 4875 4876 if (!player_ctx->buffer->IsOpen()) 4877 { 4878 LOG(VB_GENERAL, LOG_ERR, LOC + "JumpToStream buffer OpenFile failed"); 4879 SetEof(true); 4880 SetErrored(QObject::tr("Error opening remote stream buffer")); 4881 return; 4882 } 4883 4884 watchingrecording = false; 4885 totalLength = 0; 4886 totalFrames = 0; 4887 totalDuration = 0; 4888 4889 if (OpenFile(120) < 0) // 120 retries ~= 60 seconds 4890 { 4891 LOG(VB_GENERAL, LOG_ERR, LOC + "JumpToStream OpenFile failed."); 4892 SetEof(true); 4893 SetErrored(QObject::tr("Error opening remote stream")); 4894 return; 4895 } 4896 4897 if (totalLength == 0) 4898 { 4899 long long len = player_ctx->buffer->GetRealFileSize(); 4900 totalLength = (int)(len / ((decoder->GetRawBitrate() * 1000) / 8)); 4901 totalFrames = (int)(totalLength * SafeFPS(decoder)); 4902 } 4903 LOG(VB_PLAYBACK, LOG_INFO, LOC + 4904 QString("JumpToStream length %1 bytes @ %2 Kbps = %3 Secs, %4 frames @ %5 fps") 4905 .arg(player_ctx->buffer->GetRealFileSize()).arg(decoder->GetRawBitrate()) 4906 .arg(totalLength).arg(totalFrames).arg(decoder->GetFPS()) ); 4907 4908 SetEof(false); 4909 4910 // the bitrate is reset by player_ctx->buffer->OpenFile()... 4911 player_ctx->buffer->UpdateRawBitrate(decoder->GetRawBitrate()); 4912 decoder->SetProgramInfo(pginfo); 4913 4914 Play(); 4915 ChangeSpeed(); 4916 4917 player_ctx->SetPlayerChangingBuffers(false); 4918 #ifdef USING_MHEG 4919 if (interactiveTV) interactiveTV->StreamStarted(); 4920 #endif 4921 4922 LOG(VB_PLAYBACK, LOG_INFO, LOC + "JumpToStream - end"); 4923 } 4924 4925 long MythPlayer::GetStreamPos() 4926 { 4927 return (long)((1000 * GetFramesPlayed()) / SafeFPS(decoder)); 4928 } 4929 4930 long MythPlayer::GetStreamMaxPos() 4931 { 4932 long maxpos = (long)(1000 * (totalDuration > 0 ? totalDuration : totalLength)); 4933 long pos = GetStreamPos(); 4934 return maxpos > pos ? maxpos : pos; 4935 } 4936 4937 long MythPlayer::SetStreamPos(long ms) 4938 { 4939 uint64_t frameNum = (uint64_t)((ms * SafeFPS(decoder)) / 1000); 4940 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("SetStreamPos %1 mS = frame %2, now=%3") 4941 .arg(ms).arg(frameNum).arg(GetFramesPlayed()) ); 4942 JumpToFrame(frameNum); 4943 return ms; 4944 } 4945 4946 void MythPlayer::StreamPlay(bool play) 4947 { 4948 if (play) 4949 Play(); 4950 else 4951 Pause(); 4952 } 4953 4795 4954 /** \fn MythPlayer::SetDecoder(DecoderBase*) 4796 4955 * \brief Sets the stream decoder, deleting any existing recorder. 4797 4956 */ -
mythtv/libs/libmythtv/mythplayer.h
diff --git a/mythtv/libs/libmythtv/mythplayer.h b/mythtv/libs/libmythtv/mythplayer.h index 28290d9..ee2c68f 100644
a b class MTV_PUBLIC MythPlayer 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 566 571 // Private LiveTV stuff 567 572 void SwitchToProgram(void); 568 573 void JumpToProgram(void); 574 void JumpToStream(const QString&); 569 575 570 576 protected: 571 577 PlayerFlags playerFlags; … … class MTV_PUBLIC MythPlayer 701 707 InteractiveTV *interactiveTV; 702 708 bool itvEnabled; 703 709 QMutex itvLock; 710 QString m_newStream; // Guarded by itvLock 704 711 705 712 // OSD stuff 706 713 OSD *osd; -
new file mythtv/libs/libmythtv/netstream.cpp
diff --git a/mythtv/libs/libmythtv/netstream.cpp b/mythtv/libs/libmythtv/netstream.cpp new file mode 100644 index 0000000..4322944
- + 1 /* Network stream 2 * Copyright 2011 Lawrence Rust <lvr at softsystem dot co dot uk> 3 */ 4 #include "netstream.h" 5 6 // C/C++ lib 7 #include <cstdlib> 8 using std::getenv; 9 #include <cstddef> 10 #include <cstdio> 11 12 // Qt 13 #include <QNetworkAccessManager> 14 #include <QNetworkRequest> 15 #include <QNetworkReply> 16 #include <QNetworkProxy> 17 #include <QNetworkDiskCache> 18 #include <QSslConfiguration> 19 #include <QSslError> 20 #include <QSslSocket> 21 #include <QUrl> 22 #include <QThread> 23 #include <QMutexLocker> 24 #include <QEvent> 25 #include <QCoreApplication> 26 #include <QAtomicInt> 27 #include <QMetaType> // qRegisterMetaType 28 #include <QDesktopServices> 29 #include <QScopedPointer> 30 31 // Myth 32 #include "mythlogging.h" 33 #include "mythcorecontext.h" 34 #include "mythdirs.h" 35 36 37 /* 38 * Constants 39 */ 40 #define LOC "[netstream] " 41 42 43 /* 44 * Private data 45 */ 46 static QAtomicInt s_nRequest(1); // Unique NetStream request ID 47 static QMutex s_mtx; // Guard local static data e.g. NAMThread singleton 48 49 50 /* 51 * Private types 52 */ 53 // Custom event posted to NAMThread 54 class NetStreamRequest : public QEvent 55 { 56 public: 57 static const QEvent::Type kType = QEvent::User; 58 59 NetStreamRequest(int id, const QNetworkRequest &req) : 60 QEvent(kType), 61 m_id(id), 62 m_req(req), 63 m_bCancelled(false) 64 { } 65 66 const int m_id; 67 const QNetworkRequest m_req; 68 volatile bool m_bCancelled; 69 }; 70 71 class NetStreamAbort : public QEvent 72 { 73 public: 74 static const QEvent::Type kType = static_cast< QEvent::Type >(QEvent::User + 1); 75 76 NetStreamAbort(int id, QNetworkReply *reply) : 77 QEvent(kType), 78 m_id(id), 79 m_reply(reply) 80 { } 81 82 const int m_id; 83 QNetworkReply * const m_reply; 84 }; 85 86 87 /** 88 * Network streaming request 89 */ 90 NetStream::NetStream(const QUrl &url, EMode mode /*= kPreferCache*/) : 91 m_id(s_nRequest.fetchAndAddRelaxed(1)), 92 m_state(kClosed), 93 m_pending(0), 94 m_reply(0), 95 m_nRedirections(0), 96 m_size(-1), 97 m_pos(0) 98 { 99 setObjectName("NetStream " + url.toString()); 100 101 m_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, 102 mode == kAlwaysCache ? QNetworkRequest::AlwaysCache : 103 mode == kPreferCache ? QNetworkRequest::PreferCache : 104 mode == kNeverCache ? QNetworkRequest::AlwaysNetwork : 105 QNetworkRequest::PreferNetwork ); 106 107 // Receive requestStarted signals from NAMThread when it processes a NetStreamRequest 108 connect(&NAMThread::manager(), SIGNAL(requestStarted(int, QNetworkReply*)), 109 this, SLOT(slotRequestStarted(int, QNetworkReply*)), Qt::DirectConnection ); 110 111 QMutexLocker locker(&m_mutex); 112 113 if (Request(url)) 114 m_state = kPending; 115 } 116 117 // virtual 118 NetStream::~NetStream() 119 { 120 Abort(); 121 122 QMutexLocker locker(&m_mutex); 123 124 if (m_reply) 125 { 126 m_reply->disconnect(this); 127 m_reply->deleteLater(); 128 } 129 } 130 131 static inline QString Source(const QNetworkRequest &request) 132 { 133 switch (request.attribute(QNetworkRequest::CacheLoadControlAttribute).toInt()) 134 { 135 case QNetworkRequest::AlwaysCache: return "cache"; 136 case QNetworkRequest::PreferCache: return "cache-preferred"; 137 case QNetworkRequest::PreferNetwork: return "net-preferred"; 138 case QNetworkRequest::AlwaysNetwork: return "net"; 139 } 140 return "unknown"; 141 } 142 143 static inline QString Source(const QNetworkReply* reply) 144 { 145 return reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool() ? 146 "cache" : "host"; 147 } 148 149 // Send request to the network manager 150 // Caller must hold m_mutex 151 bool NetStream::Request(const QUrl& url) 152 { 153 if (!IsSupported(url)) 154 { 155 LOG(VB_GENERAL, LOG_WARNING, LOC + 156 QString("(%1) Request unsupported URL: %2") 157 .arg(m_id).arg(url.toString()) ); 158 return false; 159 } 160 161 if (m_pending) 162 { 163 // Cancel the pending request 164 m_pending->m_bCancelled = true; 165 m_pending = 0; 166 } 167 168 if (m_reply) 169 { 170 // Abort the current request 171 // NB the abort method appears to only work if called from NAMThread 172 m_reply->disconnect(this); 173 NAMThread::PostEvent(new NetStreamAbort(m_id, m_reply)); 174 // NAMthread will delete the reply 175 m_reply = 0; 176 } 177 178 m_request.setUrl(url); 179 180 const QByteArray ua("User-Agent"); 181 if (!m_request.hasRawHeader(ua)) 182 m_request.setRawHeader(ua, "UK-MHEG/2 MYT001/001 MHGGNU/001"); 183 184 if (m_pos > 0 || m_size >= 0) 185 m_request.setRawHeader("Range", QString("bytes=%1-").arg(m_pos).toAscii()); 186 187 #ifndef QT_NO_OPENSSL 188 #if 1 // The BBC use a self certified cert so don't verify it 189 if (m_request.url().scheme() == "https") 190 { 191 // TODO use cert from carousel auth.tls.<x> 192 QSslConfiguration ssl(QSslConfiguration::defaultConfiguration()); 193 ssl.setPeerVerifyMode(QSslSocket::VerifyNone); 194 m_request.setSslConfiguration(ssl); 195 } 196 #endif 197 #endif 198 199 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Requesting %2 from %3") 200 .arg(m_id).arg(m_request.url().toString()).arg(Source(m_request)) ); 201 m_pending = new NetStreamRequest(m_id, m_request); 202 NAMThread::PostEvent(m_pending); 203 return true; 204 } 205 206 // signal from NAMThread manager that a request has been started 207 void NetStream::slotRequestStarted(int id, QNetworkReply *reply) 208 { 209 QMutexLocker locker(&m_mutex); 210 211 if (m_id != id) 212 return; 213 214 m_pending = 0; // Event is no longer valid 215 216 if (!m_reply) 217 { 218 LOG(VB_FILE, LOG_DEBUG, LOC + QString("(%1) Started %2-").arg(m_id).arg(m_pos) ); 219 220 m_reply = reply; 221 m_state = kStarted; 222 223 reply->setReadBufferSize(4*1024*1024L); // 0= unlimited, 1MB => 4secs @ 1.5Mbps 224 225 // NB The following signals must be Qt::DirectConnection 'cos this slot 226 // was connected Qt::DirectConnection so the current thread is NAMThread 227 228 // QNetworkReply signals 229 connect(reply, SIGNAL(finished()), this, SLOT(slotFinished()), Qt::DirectConnection ); 230 #ifndef QT_NO_OPENSSL 231 connect(reply, SIGNAL(sslErrors(const QList<QSslError> &)), this, 232 SLOT(slotSslErrors(const QList<QSslError> &)), Qt::DirectConnection ); 233 #endif 234 // QIODevice signals 235 connect(reply, SIGNAL(readyRead()), this, SLOT(slotReadyRead()), Qt::DirectConnection ); 236 } 237 else 238 LOG(VB_GENERAL, LOG_ERR, LOC + 239 QString("(%1) Started but m_reply not NULL").arg(m_id)); 240 } 241 242 static qlonglong inline ContentLength(const QNetworkReply *reply) 243 { 244 bool ok; 245 qlonglong len = reply->header(QNetworkRequest::ContentLengthHeader) 246 .toLongLong(&ok); 247 return ok ? len : -1; 248 } 249 250 static qlonglong inline ContentRange(const QNetworkReply *reply, 251 qlonglong &first, qlonglong &last) 252 { 253 first = last = -1; 254 255 QByteArray range = reply->rawHeader("Content-Range"); 256 if (range.isEmpty()) 257 return -1; 258 259 // See RFC 2616 14.16: 'bytes begin-end/size' 260 qlonglong len; 261 if (3 != std::sscanf(range.constData(), " bytes %lld - %lld / %lld", &first, &last, &len)) 262 { 263 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Invalid Content-Range:'%1'") 264 .arg(range.constData()) ); 265 return -1; 266 } 267 268 return len; 269 } 270 271 static bool inline RequestRange(const QNetworkRequest &request, 272 qlonglong &first, qlonglong &last) 273 { 274 first = last = -1; 275 276 QByteArray range = request.rawHeader("Range"); 277 if (range.isEmpty()) 278 return false; 279 280 if (1 > std::sscanf(range.constData(), " bytes %lld - %lld", &first, &last)) 281 { 282 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Invalid Range:'%1'") 283 .arg(range.constData()) ); 284 return false; 285 } 286 287 return true; 288 } 289 290 // signal from QNetworkReply 291 void NetStream::slotReadyRead() 292 { 293 QMutexLocker locker(&m_mutex); 294 295 if (m_reply) 296 { 297 LOG(VB_FILE, LOG_DEBUG, LOC + QString("(%1) Ready %2 bytes") 298 .arg(m_id).arg(m_reply->bytesAvailable()) ); 299 300 if (m_size < 0) 301 { 302 qlonglong first, last, len = ContentRange(m_reply, first, last); 303 if (len >= 0) 304 { 305 m_size = len; 306 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) range %2-%3/%4") 307 .arg(m_id).arg(first).arg(last).arg(len) ); 308 } 309 else 310 { 311 m_size = ContentLength(m_reply); 312 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) content length %2") 313 .arg(m_id).arg(m_size) ); 314 } 315 } 316 317 if (m_state < kReady) 318 m_state = kReady; 319 320 locker.unlock(); 321 emit ReadyRead(this); 322 locker.relock(); 323 324 m_ready.wakeAll(); 325 } 326 else 327 LOG(VB_GENERAL, LOG_ERR, LOC + 328 QString("(%1) ReadyRead but m_reply = NULL").arg(m_id)); 329 } 330 331 // signal from QNetworkReply 332 void NetStream::slotFinished() 333 { 334 QMutexLocker locker(&m_mutex); 335 336 if (m_reply) 337 { 338 QNetworkReply::NetworkError error = m_reply->error(); 339 if (QNetworkReply::NoError == error) 340 { 341 // Check for a re-direct 342 QUrl url = m_reply->attribute( 343 QNetworkRequest::RedirectionTargetAttribute).toUrl(); 344 if (!url.isValid()) 345 { 346 m_state = kFinished; 347 } 348 else if (m_nRedirections++ > 0) 349 { 350 LOG(VB_FILE, LOG_WARNING, LOC + QString("(%1) Too many redirections") 351 .arg(m_id)); 352 m_state = kFinished; 353 } 354 else if ((url = m_request.url().resolved(url)) == m_request.url()) 355 { 356 LOG(VB_FILE, LOG_WARNING, LOC + QString("(%1) Redirection loop to %2") 357 .arg(m_id).arg(url.toString()) ); 358 m_state = kFinished; 359 } 360 else 361 { 362 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Redirecting").arg(m_id)); 363 m_state = Request(url) ? kPending : kFinished; 364 } 365 } 366 else 367 { 368 LOG(VB_FILE, LOG_WARNING, LOC + QString("(%1): %2") 369 .arg(m_id).arg(m_reply->errorString()) ); 370 m_state = kFinished; 371 } 372 373 if (m_state == kFinished) 374 { 375 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Finished %2/%3 bytes from %4") 376 .arg(m_id).arg(m_pos).arg(m_size).arg(Source(m_reply)) ); 377 378 locker.unlock(); 379 emit Finished(this); 380 locker.relock(); 381 382 m_finished.wakeAll(); 383 } 384 } 385 else 386 LOG(VB_GENERAL, LOG_ERR, LOC + QString("(%1) Finished but m_reply = NULL") 387 .arg(m_id)); 388 } 389 390 #ifndef QT_NO_OPENSSL 391 // signal from QNetworkReply 392 void NetStream::slotSslErrors(const QList<QSslError> &errors) 393 { 394 QMutexLocker locker(&m_mutex); 395 396 if (m_reply) 397 { 398 bool bIgnore = true; 399 Q_FOREACH(const QSslError &e, errors) 400 { 401 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) SSL error %2: ") 402 .arg(m_id).arg(e.error()) + e.errorString() ); 403 switch (e.error()) 404 { 405 #if 1 // The BBC use a self certified cert 406 case QSslError::SelfSignedCertificateInChain: 407 break; 408 #endif 409 default: 410 bIgnore = false; 411 break; 412 } 413 } 414 415 if (bIgnore) 416 { 417 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) SSL errors ignored").arg(m_id)); 418 m_reply->ignoreSslErrors(errors); 419 } 420 } 421 else 422 LOG(VB_GENERAL, LOG_ERR, LOC + 423 QString("(%1) SSL error but m_reply = NULL").arg(m_id) ); 424 } 425 #endif 426 427 428 /** 429 * RingBuffer interface 430 */ 431 // static 432 bool NetStream::IsSupported(const QUrl &url) 433 { 434 return url.isValid() && 435 (url.scheme() == "http" || url.scheme() == "https") && 436 !url.authority().isEmpty() && 437 !url.path().isEmpty(); 438 } 439 440 bool NetStream::IsOpen() const 441 { 442 QMutexLocker locker(&m_mutex); 443 return m_state > kClosed; 444 } 445 446 void NetStream::Abort() 447 { 448 QMutexLocker locker(&m_mutex); 449 450 if (m_pending) 451 { 452 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Cancelled").arg(m_id) ); 453 m_pending->m_bCancelled = true; 454 m_pending = 0; 455 } 456 457 if (m_reply && m_reply->isRunning()) 458 { 459 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Abort").arg(m_id) ); 460 NAMThread::PostEvent(new NetStreamAbort(m_id, m_reply)); 461 // NAMthread will delete the reply 462 m_reply = 0; 463 } 464 465 m_state = kFinished; 466 } 467 468 int NetStream::safe_read(void *data, unsigned sz, unsigned millisecs /* = 0 */) 469 { 470 QTime t; t.start(); 471 QMutexLocker locker(&m_mutex); 472 473 if (m_size >= 0 && m_pos >= m_size) 474 return 0; // EOF 475 476 while (m_state < kFinished && (!m_reply || m_reply->bytesAvailable() < sz)) 477 { 478 unsigned elapsed = t.elapsed(); 479 if (elapsed >= millisecs) 480 break; 481 m_ready.wait(&m_mutex, millisecs - elapsed); 482 } 483 484 if (!m_reply) 485 return -1; 486 487 qint64 avail = m_reply->read(reinterpret_cast< char* >(data), sz); 488 if (avail <= 0) 489 return m_state >= kFinished ? 0 : -1; // 0= EOF 490 491 LOG(VB_FILE, LOG_DEBUG, LOC + QString("(%1) safe_read @ %4 => %2/%3, %5 mS") 492 .arg(m_id).arg(avail).arg(sz).arg(m_pos).arg(t.elapsed()) ); 493 m_pos += avail; 494 return (int)avail; 495 } 496 497 qlonglong NetStream::Seek(qlonglong pos) 498 { 499 QMutexLocker locker(&m_mutex); 500 501 if (pos == m_pos) 502 return pos; 503 504 if (pos < 0 || (m_size >= 0 && pos > m_size)) 505 { 506 LOG(VB_GENERAL, LOG_ERR, LOC + 507 QString("(%1) Seek(%2) out of range [0..%3]") 508 .arg(m_id).arg(pos).arg(m_size) ); 509 return -1; 510 } 511 512 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Seek(%2) curr %3 end %4") 513 .arg(m_id).arg(pos).arg(m_pos).arg(m_size) ); 514 m_pos = pos; 515 return Request(m_request.url()) ? m_pos : -1; 516 } 517 518 qlonglong NetStream::GetReadPosition() const 519 { 520 QMutexLocker locker(&m_mutex); 521 522 return m_pos; 523 } 524 525 qlonglong NetStream::GetSize() const 526 { 527 QMutexLocker locker(&m_mutex); 528 529 return m_size; 530 } 531 532 533 /** 534 * Synchronous interface 535 */ 536 bool NetStream::WaitTillReady(unsigned long time) 537 { 538 QMutexLocker locker(&m_mutex); 539 540 QTime t; t.start(); 541 while (m_state < kReady) 542 { 543 unsigned elapsed = t.elapsed(); 544 if (elapsed > time) 545 return false; 546 547 m_ready.wait(&m_mutex, time - elapsed); 548 } 549 550 return true; 551 } 552 553 bool NetStream::WaitTillFinished(unsigned long time) 554 { 555 QMutexLocker locker(&m_mutex); 556 557 QTime t; t.start(); 558 while (m_state < kFinished) 559 { 560 unsigned elapsed = t.elapsed(); 561 if (elapsed > time) 562 return false; 563 564 m_finished.wait(&m_mutex, time - elapsed); 565 } 566 567 return true; 568 } 569 570 QNetworkReply::NetworkError NetStream::GetError() const 571 { 572 QMutexLocker locker(&m_mutex); 573 return !m_reply ? QNetworkReply::OperationCanceledError : m_reply->error(); 574 } 575 576 QString NetStream::GetErrorString() const 577 { 578 QMutexLocker locker(&m_mutex); 579 return !m_reply ? "Operation cancelled" : m_reply->errorString(); 580 } 581 582 qlonglong NetStream::BytesAvailable() const 583 { 584 QMutexLocker locker(&m_mutex); 585 return m_reply ? m_reply->bytesAvailable() : 0; 586 } 587 588 QByteArray NetStream::ReadAll() 589 { 590 QMutexLocker locker(&m_mutex); 591 592 if (!m_reply) 593 return 0; 594 595 QByteArray data = m_reply->readAll(); 596 m_pos += data.size(); 597 return data; 598 } 599 600 /** 601 * Asynchronous interface 602 */ 603 bool NetStream::isStarted() const 604 { 605 QMutexLocker locker(&m_mutex); 606 return m_state >= kStarted; 607 } 608 609 bool NetStream::isReady() const 610 { 611 QMutexLocker locker(&m_mutex); 612 return m_state >= kReady; 613 } 614 615 bool NetStream::isFinished() const 616 { 617 QMutexLocker locker(&m_mutex); 618 return m_state >= kFinished; 619 } 620 621 /** 622 * Public helpers 623 */ 624 // static 625 bool NetStream::isAvailable() 626 { 627 return NAMThread::isAvailable(); 628 } 629 630 // Time when URI was last written to cache or invalid if not cached. 631 // static 632 QDateTime NetStream::GetLastModified(const QString &url) 633 { 634 return NAMThread::GetLastModified(url); 635 } 636 637 638 /** 639 * NetworkAccessManager event loop thread 640 */ 641 //static 642 NAMThread & NAMThread::manager() 643 { 644 QMutexLocker locker(&s_mtx); 645 646 // Singleton 647 static NAMThread thread; 648 thread.start(); 649 return thread; 650 } 651 652 NAMThread::NAMThread() : m_bQuit(false), m_nam(0) 653 { 654 setObjectName("NAMThread"); 655 656 #ifndef QT_NO_OPENSSL 657 // This ought to be done by the Qt lib but isn't in 4.7 658 //Q_DECLARE_METATYPE(QList<QSslError>) 659 qRegisterMetaType< QList<QSslError> >(); 660 #endif 661 } 662 663 // virtual 664 NAMThread::~NAMThread() 665 { 666 QMutexLocker locker(&m_mutex); 667 delete m_nam; 668 } 669 670 // virtual 671 void NAMThread::run() 672 { 673 LOG(VB_MHEG, LOG_INFO, LOC "NAMThread starting"); 674 675 m_nam = new QNetworkAccessManager(); 676 m_nam->setObjectName("NetStream NAM"); 677 678 // Setup cache 679 QScopedPointer<QNetworkDiskCache> cache(new QNetworkDiskCache()); 680 cache->setCacheDirectory( 681 QDesktopServices::storageLocation(QDesktopServices::CacheLocation) ); 682 m_nam->setCache(cache.take()); 683 684 // Setup a network proxy e.g. for TOR: socks://localhost:9050 685 // TODO get this from mythdb 686 QString proxy(getenv("MYTHMHEG_PROXY")); 687 if (!proxy.isEmpty()) 688 { 689 QUrl url(proxy, QUrl::TolerantMode); 690 QNetworkProxy::ProxyType type = 691 url.scheme().isEmpty() ? QNetworkProxy::HttpProxy : 692 url.scheme() == "socks" ? QNetworkProxy::Socks5Proxy : 693 url.scheme() == "http" ? QNetworkProxy::HttpProxy : 694 url.scheme() == "https" ? QNetworkProxy::HttpProxy : 695 url.scheme() == "cache" ? QNetworkProxy::HttpCachingProxy : 696 url.scheme() == "ftp" ? QNetworkProxy::FtpCachingProxy : 697 QNetworkProxy::NoProxy; 698 if (QNetworkProxy::NoProxy != type) 699 { 700 LOG(VB_MHEG, LOG_INFO, LOC "Using proxy: " + proxy); 701 m_nam->setProxy(QNetworkProxy( 702 type, url.host(), url.port(), url.userName(), url.password() )); 703 } 704 else 705 { 706 LOG(VB_MHEG, LOG_ERR, LOC + QString("Unknown proxy type %1") 707 .arg(url.scheme()) ); 708 } 709 } 710 711 // Quit when main app quits 712 connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(quit()) ); 713 714 m_running.release(); 715 716 while(!m_bQuit) 717 { 718 // Process NAM events 719 QCoreApplication::processEvents(); 720 721 QMutexLocker locker(&m_mutex); 722 m_work.wait(&m_mutex, 100); 723 while (!m_workQ.isEmpty()) 724 { 725 QScopedPointer< QEvent > ev(m_workQ.dequeue()); 726 locker.unlock(); 727 NewRequest(ev.data()); 728 } 729 } 730 731 m_running.acquire(); 732 733 delete m_nam; 734 m_nam = 0; 735 736 LOG(VB_MHEG, LOG_INFO, LOC "NAMThread stopped"); 737 } 738 739 // slot 740 void NAMThread::quit() 741 { 742 m_bQuit = true; 743 QThread::quit(); 744 } 745 746 // static 747 void NAMThread::PostEvent(QEvent *event) 748 { 749 NAMThread &m = manager(); 750 QMutexLocker locker(&m.m_mutex); 751 m.m_workQ.enqueue(event); 752 } 753 754 bool NAMThread::NewRequest(QEvent *event) 755 { 756 switch (event->type()) 757 { 758 case NetStreamRequest::kType: 759 return StartRequest(dynamic_cast< NetStreamRequest* >(event)); 760 case NetStreamAbort::kType: 761 return AbortRequest(dynamic_cast< NetStreamAbort* >(event)); 762 default: 763 break; 764 } 765 return false; 766 } 767 768 bool NAMThread::StartRequest(NetStreamRequest *p) 769 { 770 if (!p) 771 { 772 LOG(VB_GENERAL, LOG_ERR, LOC "Invalid NetStreamRequest"); 773 return false; 774 } 775 776 if (!p->m_bCancelled) 777 { 778 LOG(VB_FILE, LOG_DEBUG, LOC + QString("(%1) StartRequest").arg(p->m_id) ); 779 QNetworkReply *reply = m_nam->get(p->m_req); 780 emit requestStarted(p->m_id, reply); 781 } 782 else 783 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) NetStreamRequest cancelled").arg(p->m_id) ); 784 return true; 785 } 786 787 bool NAMThread::AbortRequest(NetStreamAbort *p) 788 { 789 if (!p) 790 { 791 LOG(VB_GENERAL, LOG_ERR, LOC "Invalid NetStreamAbort"); 792 return false; 793 } 794 795 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) AbortRequest").arg(p->m_id) ); 796 p->m_reply->abort(); 797 p->m_reply->disconnect(); 798 delete p->m_reply; 799 return true; 800 } 801 802 // static 803 bool NAMThread::isAvailable() 804 { 805 NAMThread &m = manager(); 806 807 if (!m.m_running.tryAcquire(1, 3000)) 808 return false; 809 810 m.m_running.release(); 811 812 QMutexLocker locker(&m.m_mutex); 813 814 if (!m.m_nam) 815 return false; 816 817 switch (m.m_nam->networkAccessible()) 818 { 819 case QNetworkAccessManager::Accessible: return true; 820 case QNetworkAccessManager::NotAccessible: return false; 821 case QNetworkAccessManager::UnknownAccessibility: return true; 822 } 823 return false; 824 } 825 826 // Time when URI was last written to cache or invalid if not cached. 827 // static 828 QDateTime NAMThread::GetLastModified(const QString &url) 829 { 830 NAMThread &m = manager(); 831 832 QMutexLocker locker(&m.m_mutex); 833 834 if (!m.m_nam) 835 return QDateTime(); // Invalid 836 837 QAbstractNetworkCache *cache = m.m_nam->cache(); 838 if (!cache) 839 return QDateTime(); // Invalid 840 841 QNetworkCacheMetaData meta = cache->metaData(QUrl(url)); 842 if (!meta.isValid()) 843 { 844 LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetLastModified('%1') not in cache") 845 .arg(url)); 846 return QDateTime(); // Invalid 847 } 848 849 // Check if expired 850 QDateTime const now(QDateTime::currentDateTime()); // local time 851 QDateTime expire = meta.expirationDate(); 852 if (expire.isValid() && expire.toLocalTime() < now) 853 { 854 LOG(VB_FILE, LOG_INFO, LOC + QString("GetLastModified('%1') past expiration %2") 855 .arg(url).arg(expire.toString())); 856 return QDateTime(); // Invalid 857 } 858 859 // Get time URI was modified (Last-Modified header) NB this may be invalid 860 QDateTime lastMod = meta.lastModified(); 861 862 QNetworkCacheMetaData::RawHeaderList headers = meta.rawHeaders(); 863 Q_FOREACH(const QNetworkCacheMetaData::RawHeader &h, headers) 864 { 865 // RFC 1123 date format: Thu, 01 Dec 1994 16:00:00 GMT 866 static const char kszFormat[] = "ddd, dd MMM yyyy HH:mm:ss 'GMT'"; 867 868 QString const first(h.first.toLower()); 869 if (first == "cache-control") 870 { 871 QString const second(h.second.toLower()); 872 if (second == "no-cache" || second == "no-store") 873 { 874 LOG(VB_FILE, LOG_INFO, LOC + 875 QString("GetLastModified('%1') Cache-Control disabled").arg(url)); 876 cache->remove(QUrl(url)); 877 return QDateTime(); // Invalid 878 } 879 } 880 else if (first == "date") 881 { 882 QDateTime d = QDateTime::fromString(h.second, kszFormat); 883 if (!d.isValid()) 884 { 885 LOG(VB_GENERAL, LOG_WARNING, LOC + 886 QString("GetLastModified invalid Date header '%1'") 887 .arg(h.second.constData())); 888 continue; 889 } 890 d.setTimeSpec(Qt::UTC); 891 lastMod = d; 892 } 893 } 894 895 LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetLastModified('%1') last modified %2") 896 .arg(url).arg(lastMod.toString())); 897 return lastMod; 898 } 899 900 /* End of file */ -
new file mythtv/libs/libmythtv/netstream.h
diff --git a/mythtv/libs/libmythtv/netstream.h b/mythtv/libs/libmythtv/netstream.h new file mode 100644 index 0000000..b505f22
- + 1 /* Network stream 2 * Copyright 2011 Lawrence Rust <lvr at softsystem dot co dot uk> 3 */ 4 #ifndef NETSTREAM_H 5 #define NETSTREAM_H 6 7 #include <QList> 8 #include <QString> 9 #include <QByteArray> 10 #include <QObject> 11 #include <QMutex> 12 #include <QSemaphore> 13 #include <QThread> 14 #include <QNetworkRequest> 15 #include <QNetworkReply> 16 #include <QSslError> 17 #include <QWaitCondition> 18 #include <QQueue> 19 #include <QDateTime> 20 21 class QUrl; 22 class QNetworkAccessManager; 23 class NetStreamRequest; 24 class NetStreamAbort; 25 26 27 /** 28 * Stream content from a URI 29 */ 30 class NetStream : public QObject 31 { 32 Q_OBJECT 33 Q_DISABLE_COPY(NetStream) 34 35 public: 36 enum EMode { kNeverCache, kPreferCache, kAlwaysCache }; 37 NetStream(const QUrl &, EMode mode = kPreferCache); 38 virtual ~NetStream(); 39 40 public: 41 // RingBuffer interface 42 static bool IsSupported(const QUrl &); 43 bool IsOpen() const; 44 void Abort(); 45 int safe_read(void *data, unsigned size, unsigned millisecs = 0); 46 qlonglong Seek(qlonglong); 47 qlonglong GetReadPosition() const; 48 qlonglong GetSize() const; 49 50 // Properties 51 QUrl Url() const { return m_request.url(); } 52 53 // Synchronous interface 54 bool WaitTillReady(unsigned long millisecs); 55 bool WaitTillFinished(unsigned long millisecs); 56 QNetworkReply::NetworkError GetError() const; 57 QString GetErrorString() const; 58 qlonglong BytesAvailable() const; 59 QByteArray ReadAll(); 60 61 // Async interface 62 bool isStarted() const; 63 bool isReady() const; 64 bool isFinished() const; 65 66 signals: 67 void ReadyRead(QObject*); 68 void Finished(QObject*); 69 70 public: 71 // Time when a URI was last written to cache or invalid if not cached. 72 static QDateTime GetLastModified(const QString &url); 73 // Is the network accessible 74 static bool isAvailable(); 75 76 // Implementation 77 private slots: 78 // NAMThread signals 79 void slotRequestStarted(int, QNetworkReply *); 80 // QNetworkReply signals 81 void slotFinished(); 82 #ifndef QT_NO_OPENSSL 83 void slotSslErrors(const QList<QSslError> & errors); 84 #endif 85 // QIODevice signals 86 void slotReadyRead(); 87 88 private: 89 bool Request(const QUrl &); 90 91 const int m_id; // Unique request ID 92 93 mutable QMutex m_mutex; // Protects r/w access to the following data 94 QNetworkRequest m_request; 95 enum { kClosed, kPending, kStarted, kReady, kFinished } m_state; 96 NetStreamRequest* m_pending; 97 QNetworkReply* m_reply; 98 int m_nRedirections; 99 qlonglong m_size; 100 qlonglong m_pos; 101 QWaitCondition m_ready; 102 QWaitCondition m_finished; 103 }; 104 105 106 /** 107 * Thread to process NetStream requests 108 */ 109 class NAMThread : public QThread 110 { 111 Q_OBJECT 112 Q_DISABLE_COPY(NAMThread) 113 114 // Use manager() to create 115 NAMThread(); 116 117 public: 118 static NAMThread & manager(); // Singleton 119 virtual ~NAMThread(); 120 121 static void PostEvent(QEvent *); 122 123 static bool isAvailable(); // is network usable 124 static QDateTime GetLastModified(const QString &url); 125 126 signals: 127 void requestStarted(int, QNetworkReply *); 128 129 // Implementation 130 protected: 131 virtual void run(); // QThread override 132 bool NewRequest(QEvent *); 133 bool StartRequest(NetStreamRequest *); 134 bool AbortRequest(NetStreamAbort *); 135 136 private slots: 137 void quit(); 138 139 private: 140 volatile bool m_bQuit; 141 QSemaphore m_running; 142 mutable QMutex m_mutex; // Protects r/w access to the following data 143 QNetworkAccessManager *m_nam; 144 QQueue< QEvent * > m_workQ; 145 QWaitCondition m_work; 146 }; 147 148 #endif /* ndef NETSTREAM_H */ -
mythtv/libs/libmythtv/ringbuffer.h
diff --git a/mythtv/libs/libmythtv/ringbuffer.h b/mythtv/libs/libmythtv/ringbuffer.h index 45bd956..cbe9384 100644
a b enum RingBufferType 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(); }
