Ticket #7868: radio5.patch
File radio5.patch, 36.5 KB (added by , 14 years ago) |
---|
-
mythmusic/mythmusic.pro
39 39 HEADERS += editmetadata.h smartplaylist.h search.h genres.h 40 40 HEADERS += treebuilders.h importmusic.h 41 41 HEADERS += filescanner.h libvisualplugin.h musicplayer.h miniplayer.h 42 HEADERS += playlistcontainer.h 42 HEADERS += playlistcontainer.h audiostreammanager.h 43 43 HEADERS += mythlistview-qt3.h mythlistbox-qt3.h 44 44 45 45 SOURCES += cddecoder.cpp cdrip.cpp decoder.cpp … … 58 58 SOURCES += avfdecoder.cpp editmetadata.cpp smartplaylist.cpp search.cpp 59 59 SOURCES += treebuilders.cpp importmusic.cpp 60 60 SOURCES += filescanner.cpp libvisualplugin.cpp musicplayer.cpp miniplayer.cpp 61 SOURCES += playlistcontainer.cpp 61 SOURCES += playlistcontainer.cpp audiostreammanager.cpp 62 62 SOURCES += mythlistview-qt3.cpp mythlistbox-qt3.cpp 63 63 64 64 macx { -
mythmusic/treecheckitem.cpp
148 148 { 149 149 } 150 150 151 StreamCheckItem::StreamCheckItem(UIListGenericTree *parent, const QString &text, 152 const QString &level, int track) 153 : TreeCheckItem(parent, text, level, track) 154 { 155 } 156 151 157 PlaylistItem::PlaylistItem(UIListGenericTree *parent, const QString &title) 152 158 : UIListGenericTree(parent, title, "PLAYLISTITEM", -1) 153 159 { -
mythmusic/metadata.cpp
1067 1067 for (; it != m_all_music.end(); ++it) 1068 1068 music_map[(*it)->ID()] = *it; 1069 1069 1070 1071 // query all of the streams. 1072 MSqlQuery stream_query(MSqlQuery::InitCon()); 1073 if (!stream_query.exec("SELECT id,uri,name FROM music_streams;")) 1074 MythDB::DBError("AllMusic::resync (music_streams)", stream_query); 1075 1076 if (stream_query.isActive() && stream_query.size() > 0) 1077 { 1078 // have stream id's start where songs leave off to avoid getting song 1079 // metadata for a stream in AllMusic::getMetadata. 1080 int idx = music_map.keys().last() + 1; 1081 while (stream_query.next()) 1082 { 1083 // this little bit it to prevent adding a 1084 // create stream metadata and add it to the list 1085 Metadata *m = new Metadata(); 1086 m->setFilename(stream_query.value(1).toString()); 1087 m->setTitle(stream_query.value(2).toString()); 1088 stream_map[idx] = m; 1089 idx++; 1090 } 1091 } 1092 1070 1093 // Build a tree to reflect current state of 1071 1094 // the metadata. Once built, sort it. 1072 1095 … … 1139 1162 new_item->setCheck(false); // Avoiding -Wall 1140 1163 } 1141 1164 } 1165 bool AllMusic::putStreamsOnTheListView(TreeCheckItem *where) 1166 { 1167 MusicMap::iterator it = stream_map.begin(); 1142 1168 1169 for (; it != stream_map.end(); ++it) 1170 { 1171 StreamCheckItem *item = new StreamCheckItem(where, it.value()->Title(), QObject::tr("title"), it.key()); 1172 item->setCheck(false); 1173 } 1174 1175 return true; 1176 } 1177 1143 1178 QString AllMusic::getLabel(int an_id, bool *error_flag) 1144 1179 { 1145 1180 QString a_label; … … 1197 1232 { 1198 1233 return music_map[an_id]; 1199 1234 } 1235 else if (stream_map.contains(an_id)) 1236 { 1237 return stream_map[an_id]; 1238 } 1200 1239 } 1201 1240 else if(an_id < 0) 1202 1241 { -
mythmusic/dbcheck.cpp
11 11 #include "mythtv/mythdb.h" 12 12 #include "mythtv/schemawizard.h" 13 13 14 const QString currentDatabaseVersion = "101 7";14 const QString currentDatabaseVersion = "1018"; 15 15 16 16 static bool doUpgradeMusicDatabaseSchema(QString &dbver); 17 17 … … 777 777 return false; 778 778 } 779 779 780 if (dbver == "1017") 781 { 782 // the URI length of 256 is arbitrary. 783 const QString updates[] = { 784 "CREATE TABLE IF NOT EXISTS music_streams (" 785 " id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY," 786 " uri VARCHAR(512) NOT NULL," 787 " name VARCHAR(128) NOT NULL" 788 ");", 789 "" 790 }; 791 792 if (!performActualUpdate(updates, "1018", dbver)) 793 return false; 794 } 795 780 796 return true; 781 797 } -
mythmusic/treecheckitem.h
40 40 const QString &level, int track); 41 41 }; 42 42 43 class StreamCheckItem : public TreeCheckItem 44 { 45 public: 46 StreamCheckItem(UIListGenericTree *parent, const QString &text, 47 const QString &level, int track); 48 }; 49 50 43 51 class PlaylistItem : public UIListGenericTree 44 52 { 45 53 public: -
mythmusic/databasebox.h
93 93 PlaylistTrack *track_held; 94 94 TreeCheckItem *allmusic; 95 95 TreeCheckItem *alllists; 96 TreeCheckItem *allstreams; 96 97 PlaylistTitle *allcurrent; 97 98 Playlist *active_playlist; 98 99 -
mythmusic/metadata.h
346 346 void setSorting(QString a_paths); 347 347 bool putYourselfOnTheListView(TreeCheckItem *where); 348 348 void putCDOnTheListView(CDCheckItem *where); 349 bool putStreamsOnTheListView(TreeCheckItem *where); 349 350 bool doneLoading(){return m_done_loading;} 350 351 bool cleanOutThreads(); 351 352 int getCDTrackCount(){return m_cd_data.count();} … … 367 368 // you NEED to clear and rebuild the map 368 369 typedef QMap<int, Metadata*> MusicMap; 369 370 MusicMap music_map; 371 MusicMap stream_map; 370 372 371 373 typedef QList<Metadata> ValueMetadata; 372 374 ValueMetadata m_cd_data; // More than one cd player? -
mythmusic/avfdecoder.cpp
13 13 14 14 Revision History 15 15 - Initial release 16 - 1/9/2004 - Improved seek support 17 - ?/?/2009 - Extended to support many more filetypes and bug fixes 16 - 01/09/2004 - Improved seek support 17 - ??/??/2009 - Extended to support many more filetypes and bug fixes 18 - 28/12/2010 - Extended to support playlists for streaming audio. 18 19 */ 19 20 20 21 // C++ headers … … 25 26 #include <QObject> 26 27 #include <QIODevice> 27 28 #include <QFile> 29 #include <QXmlStreamReader> 28 30 29 31 // Myth headers 30 32 #include <mythcontext.h> 31 33 #include <audiooutput.h> 32 34 #include <mythverbose.h> 35 #include <libmythdb/httpcomms.h> 33 36 34 37 using namespace std; 35 38 … … 44 47 #include "metaiomp4.h" 45 48 #include "metaiowavpack.h" 46 49 50 #define PLAYLIST_EXTENSIONS ".m3u|.asx|.pls" 51 enum PlaylistType 52 { 53 PL_M3U, 54 PL_ASX, 55 PL_PLS, 56 PL_NONE 57 }; 58 #define M3U_HEADER "#EXTM3U" 59 #define M3U_INFO "#EXTINF" 60 #define ASX_TAG "ref" 61 #define ASX_STREAM "href" 62 #define PLS_FILE "File" 63 47 64 avfDecoder::avfDecoder(const QString &file, DecoderFactory *d, QIODevice *i, 48 65 AudioOutput *o) : 49 66 Decoder(d, i, o), … … 128 145 129 146 bool avfDecoder::initialize() 130 147 { 148 filename = ((QFile *)input())->fileName(); 149 m_pl_elements.clear(); 150 151 // check for a playlist 152 int pl_type = PL_NONE; 153 QString extn(PLAYLIST_EXTENSIONS); 154 QStringList list = extn.split("|", QString::SkipEmptyParts); 155 for (int i = 0; i < list.size(); i++) 156 { 157 QString extn = list[i]; 158 if (extn == filename.right(extn.size())) 159 { 160 pl_type = i; 161 } 162 } 163 164 switch (pl_type) 165 { 166 case PL_M3U: 167 parseM3U(filename); 168 break; 169 case PL_ASX: 170 parseASX(filename); 171 break; 172 case PL_PLS: 173 parsePLS(filename); 174 break; 175 default: 176 break; 177 } 178 179 // initialize to the end 180 m_pl_it = m_pl_elements.end(); 181 182 if (!m_pl_elements.isEmpty()) 183 { 184 m_pl_it = m_pl_elements.begin(); 185 filename = *m_pl_it; 186 } 187 else if (pl_type != PL_NONE) 188 { 189 // no streams were pulled from the playlist 190 VERBOSE(VB_GENERAL, "No streams were parsed from the playlist"); 191 return false; 192 } 193 194 // open up the stream 195 if (!openStream()) 196 { 197 return false; 198 } 199 200 inited = TRUE; 201 return TRUE; 202 } 203 204 bool avfDecoder::openStream() 205 { 131 206 bks = blockSize(); 132 207 133 208 inited = user_stop = done = finish = FALSE; … … 136 211 seekTime = -1.0; 137 212 totalTime = 0.0; 138 213 139 filename = ((QFile *)input())->fileName();140 141 214 if (!m_samples) 142 215 { 143 216 m_samples = (int16_t *)av_mallocz(AVCODEC_MAX_AUDIO_FRAME_SIZE / 2 * … … 159 232 // register av codecs 160 233 av_register_all(); 161 234 162 // open the media file163 // this should populate the input context164 235 int error; 165 error = av_open_input_file(&m_inputContext, filename.toLocal8Bit().constData(), 166 m_inputFormat, 0, &m_params); 236 do 237 { 238 // open the media file 239 // this should populate the input context 240 error = av_open_input_file(&m_inputContext, 241 filename.toLocal8Bit().constData(), 242 m_inputFormat, 0, &m_params); 167 243 244 if ((error < 0) && (m_pl_it != m_pl_elements.end())) 245 { 246 VERBOSE(VB_GENERAL, "Unable to open stream '" + filename + "'"); 247 248 if (++m_pl_it == m_pl_elements.end()) 249 break; 250 251 filename = *m_pl_it; 252 } 253 } while ((error < 0) && (m_pl_it != m_pl_elements.end())); 254 168 255 if (error < 0) 169 256 { 170 257 VERBOSE(VB_IMPORTANT, QString("Could not open file (%1)").arg(filename)); … … 256 343 if (output()) 257 344 { 258 345 const AudioSettings settings( 259 16 /*bits*/, m_audioDec->channels, m_audioDec->codec_id, 346 16 /*bits*/, m_audioDec->channels, m_audioDec->codec_id, 260 347 m_audioDec->sample_rate, false /* AC3/DTS pass through */); 261 348 output()->Reconfigure(settings); 262 349 output()->SetSourceBitrate(m_audioDec->bit_rate); 263 350 } 264 351 265 inited = TRUE; 266 return TRUE; 352 return true; 267 353 } 268 354 269 355 void avfDecoder::seek(double pos) … … 276 362 inited = user_stop = done = finish = FALSE; 277 363 len = freq = bitrate = 0; 278 364 stat = m_channels = 0; 279 setInput(0);280 setOutput(0);281 365 366 // we need these if we are playing another file in a playlist 367 if (m_pl_it == m_pl_elements.end()) 368 { 369 setInput(0); 370 setOutput(0); 371 } 372 282 373 // Cleanup here 283 374 if (m_inputContext) 284 375 { … … 347 438 VERBOSE(VB_IMPORTANT, "Read frame failed"); 348 439 VERBOSE(VB_FILE, ("... for file '" + filename) + "'"); 349 440 unlock(); 441 442 if (m_pl_it != m_pl_elements.end()) 443 { 444 // advance to the next playlist element 445 if (++m_pl_it == m_pl_elements.end()) 446 { 447 finish = TRUE; 448 break; 449 } 450 451 // stop the decoder 452 flush(TRUE); 453 output()->Drain(); 454 deinit(); 455 456 // set the filename to the next playlist element 457 filename = *m_pl_it; 458 459 // open the next palylist element 460 if (!openStream()) 461 { 462 finish = TRUE; 463 break; 464 } 465 466 av_read_play(m_inputContext); 467 468 continue; 469 } 470 350 471 finish = TRUE; 351 472 break; 352 473 } … … 456 577 return new MetaIOAVFComment(); 457 578 } 458 579 580 QString avfDecoder::downloadPlaylist(const QString &uri) 581 { 582 QString temp_uri = uri; 583 584 // copied from iptvchannelfetcher.cpp 585 if (temp_uri.left(4).toLower() == "file") 586 { 587 QString ret = ""; 588 QUrl qurl(temp_uri); 589 QFile file(qurl.toLocalFile()); 590 if (!file.open(QIODevice::ReadOnly)) 591 { 592 VERBOSE(VB_IMPORTANT, QString("Error Opening '%1'") 593 .arg(qurl.toLocalFile()) + ENO); 594 return ret; 595 } 596 597 QTextStream stream(&file); 598 while (!stream.atEnd()) 599 ret += stream.readLine() + "\n"; 600 601 file.close(); 602 return ret; 603 } 604 605 QString tmp = HttpComms::getHttp( 606 temp_uri, 607 10000 /* ms */, 3 /* retries */, 608 3 /* redirects */, true /* allow gzip */, 609 NULL /* login */, true); /* in QT thread */ 610 611 QString file = QString::fromUtf8(tmp.toAscii().constData()); 612 file.replace("\r\n", "\n"); 613 614 return file; 615 } 616 617 void avfDecoder::parseM3U(const QString &uri) 618 { 619 QString file = downloadPlaylist(uri); 620 QStringList lines = file.split("\n"); 621 622 QStringList::iterator it; 623 for (it = lines.begin(); it != lines.end(); ++it) 624 { 625 // ignore empty lines 626 if (it->isEmpty()) 627 continue; 628 629 // ignore the M3U header 630 if (it->startsWith(M3U_HEADER)) 631 continue; 632 633 // for now ignore M3U info lines 634 if (it->startsWith(M3U_INFO)) 635 continue; 636 637 // add to the playlist elements 638 m_pl_elements.push_back(*it); 639 } 640 } 641 642 void avfDecoder::parseASX(const QString &uri) 643 { 644 QString file = downloadPlaylist(uri); 645 QXmlStreamReader reader(file.toAscii()); 646 while (!reader.atEnd()) 647 { 648 QXmlStreamReader::TokenType tok_type; 649 tok_type = reader.readNext(); 650 651 switch (tok_type) 652 { 653 case QXmlStreamReader::StartElement: 654 if (reader.name().toString().toLower() == ASX_TAG) 655 { 656 QXmlStreamAttributes attribs = reader.attributes(); 657 QXmlStreamAttributes::iterator it = attribs.begin(); 658 for (; it != attribs.end(); ++it) 659 { 660 if (it->name().toString().toLower() == ASX_STREAM) 661 { 662 m_pl_elements.push_back(it->value().toString()); 663 } 664 } 665 } 666 break; 667 default: 668 break; 669 } 670 } 671 672 if (reader.hasError()) 673 { 674 VERBOSE(VB_GENERAL, "Unable to parse ASX stream: " + 675 reader.errorString()); 676 } 677 } 678 679 void avfDecoder::parsePLS(const QString &uri) 680 { 681 QString file = downloadPlaylist(uri); 682 QStringList elements, lines = file.split("\n");; 683 684 QStringList::iterator it; 685 for (it = lines.begin(); it != lines.end(); ++it) 686 { 687 // ignore empty lines 688 if (it->isEmpty()) 689 continue; 690 691 if (it->startsWith(PLS_FILE)) 692 { 693 // get the index 694 int idx = it->indexOf('='); 695 696 // ensure the index is valid 697 if (idx < 0) 698 continue; 699 700 // parse the stream and add it to the playlist elements 701 m_pl_elements.push_back(it->right(it->size() - idx - 1)); 702 } 703 } 704 } 705 459 706 bool avfDecoderFactory::supports(const QString &source) const 460 707 { 461 708 QStringList list = extension().split("|", QString::SkipEmptyParts); … … 465 712 return true; 466 713 } 467 714 468 return false; 715 // if the extension is unknown, see if the decoder can initialise 716 // since some streams are just a URI (with no file extension) 717 QFile *file = new QFile(source); 718 avfDecoder *decoder = new avfDecoder(source, NULL, file, NULL); 719 bool supported = decoder->initialize(); 720 721 // clean up 722 delete decoder; 723 delete file; 724 725 // return the result of initialization 726 return supported; 469 727 } 470 728 471 729 const QString &avfDecoderFactory::extension() const 472 730 { 473 731 static QString ext(".mp3|.mp2|.ogg|.oga|.flac|.wma|.wav|.ac3|.oma|.omg|" 474 ".atp|.ra|.dts|.aac|.m4a|.aa3|.tta|.mka|.aiff|.swa|.wv"); 732 ".atp|.ra|.dts|.aac|.m4a|.aa3|.tta|.mka|.aiff|.swa|.wv|" 733 PLAYLIST_EXTENSIONS); 475 734 return ext; 476 735 } 477 736 … … 500 759 501 760 return decoder; 502 761 } 503 -
mythmusic/audiostreammanager.cpp
1 // -*- Mode: c++ -*- 2 // Qt headers 3 #include <QString> 4 5 // MythTV headers 6 #include <mythdb.h> 7 #include <mythcontext.h> 8 9 // MythUI headers 10 #include <libmythui/xmlparsebase.h> 11 #include <libmythui/mythmainwindow.h> 12 #include <libmythui/mythuitext.h> 13 #include <libmythui/mythuibutton.h> 14 #include <libmythui/mythuibuttonlist.h> 15 #include <libmythui/mythuiprogressbar.h> 16 #include <libmythui/myththemedmenu.h> 17 #include <libmythui/mythdialogbox.h> 18 19 // MythMusic headers 20 #include "metadata.h" 21 #include "decoder.h" 22 #include "avfdecoder.h" 23 #include "musicplayer.h" 24 #include "audiostreammanager.h" 25 26 #define STREAM_TIMEOUT 10000 27 28 AudioStreamManager::AudioStreamManager(MythScreenStack *parent) 29 : MythScreenType (parent, "audiostreammanager") 30 { 31 } 32 33 34 AudioStreamManager::~AudioStreamManager() 35 { 36 } 37 38 39 bool AudioStreamManager::Create(void) 40 { 41 // Load the theme for this screen 42 if (!XMLParseBase::LoadWindowFromXML(QString("music-ui.xml"), QString("stream_manager"), this)) 43 { 44 VERBOSE(VB_IMPORTANT, "Unable audio stream manager to load window from xml."); 45 return false; 46 } 47 48 // UI components 49 m_title_text = dynamic_cast<MythUIText *>(GetChild("title_text")); 50 if (m_title_text == NULL) 51 { 52 VERBOSE(VB_IMPORTANT, "Unable to get title_text."); 53 return false; 54 } 55 56 m_uri_text = dynamic_cast<MythUIText *>(GetChild("uri_text")); 57 if (m_uri_text == NULL) 58 { 59 VERBOSE(VB_IMPORTANT, "Unable to get URI text."); 60 return false; 61 } 62 63 m_addButton = dynamic_cast<MythUIButton *>(GetChild("add_stream")); 64 if (m_addButton == NULL) 65 { 66 VERBOSE(VB_IMPORTANT, "Unable to get add_stream."); 67 return false; 68 } 69 m_addButton->SetText("Add"); 70 71 m_streamList = dynamic_cast<MythUIButtonList *>(GetChild("stream_list")); 72 if (m_streamList == NULL) 73 { 74 VERBOSE(VB_IMPORTANT, "Unable to get stream_list."); 75 return false; 76 } 77 78 // set the title 79 m_title_text->SetText(tr("Audio Streams")); 80 81 connect(m_streamList, SIGNAL(itemClicked(MythUIButtonListItem*)), 82 SLOT(StreamClicked(MythUIButtonListItem*))); 83 84 connect(m_streamList, SIGNAL(itemSelected(MythUIButtonListItem*)), 85 SLOT(StreamSelected(MythUIButtonListItem*))); 86 87 connect(m_addButton, SIGNAL(Clicked()), SLOT(AddStream())); 88 89 SetFocusWidget(m_addButton); 90 91 if (!PopulateStreamList()) 92 { 93 MythPopupBox::showOkPopup(gContext->GetMainWindow(), tr("ERROR"), 94 tr("Unable to populate stream list.")); 95 return false; 96 } 97 98 return true; 99 } 100 101 bool AudioStreamManager::keyPressEvent(QKeyEvent *event) 102 { 103 if (GetFocusWidget()->keyPressEvent(event)) 104 return true; 105 106 QStringList actions; 107 bool handled = GetMythMainWindow()->TranslateKeyPress("Global", event, actions); 108 109 for (int i = 0; i < actions.size() && !handled; i++) 110 { 111 QString action = actions[i]; 112 MythUIType *widget = GetFocusWidget(); 113 114 if ((action == "UP") && (widget == m_streamList)) 115 { 116 if (m_streamList->GetCurrentPos() == 0) 117 { 118 SetFocusWidget(m_addButton); 119 } 120 } 121 else if (action == "DOWN") 122 { 123 if (widget == m_addButton) 124 { 125 SetFocusWidget(m_streamList); 126 } 127 } 128 else if (action == "DELETE") 129 { 130 if (GetFocusWidget() == m_streamList) 131 { 132 int id = m_streamList->GetItemCurrent()->GetData().toInt(); 133 DeleteStream(id); 134 } 135 } 136 } 137 138 if (!handled && MythScreenType::keyPressEvent(event)) 139 handled = true; 140 141 return handled; 142 } 143 144 void AudioStreamManager::StreamClicked(MythUIButtonListItem *item) 145 { 146 QStringList buttons; 147 buttons << tr("Edit Title") << tr("Edit URI") << tr("Delete Stream"); 148 149 MythMainWindow *main_window = gContext->GetMainWindow(); 150 DialogCode c = MythPopupBox::ShowButtonPopup(main_window, tr("Edit Stream"), 151 tr("What would you like to do?"), 152 buttons, kDialogCodeButton0); 153 154 int id = item->GetData().toInt(); 155 156 switch (c) 157 { 158 case kDialogCodeButton0: 159 UpdateTitle(item->GetText()); 160 break; 161 case kDialogCodeButton1: 162 UpdateURI(m_uri_map[id]); 163 break; 164 case kDialogCodeButton2: 165 DeleteStream(id); 166 break; 167 default: 168 break; 169 } 170 } 171 172 void AudioStreamManager::StreamSelected(MythUIButtonListItem *item) 173 { 174 m_uri_text->SetText(m_uri_map[item->GetData().toInt()]); 175 } 176 177 void AudioStreamManager::AddStream() 178 { 179 MythTextInputDialog *uriBox; 180 MythScreenStack *stack = GetMythMainWindow()->GetStack("popup stack"); 181 182 uriBox = new MythTextInputDialog(stack, tr("Stream URI")); 183 184 connect(uriBox, SIGNAL(haveResult(QString)), SLOT(StreamAdded(QString))); 185 186 if (!uriBox->Create()) 187 { 188 VERBOSE(VB_IMPORTANT, "ERROR: Unable to create text input dialog."); 189 return; 190 } 191 192 stack->AddScreen(uriBox, false); 193 } 194 195 Metadata* AudioStreamManager::TestStream(const QString & uri) 196 { 197 // TODO timeout if the stream takes too long to test 198 199 // show a progress dialog 200 MythProgressDialog *prog = new MythProgressDialog(tr("Testing Stream"), 4); 201 if (prog == NULL) 202 { 203 VERBOSE(VB_IMPORTANT, "Unable to create progress dialog."); 204 return NULL; 205 } 206 207 // TODO Despite allowing feedback on the state of the stream, it would be 208 // easier to just see if the stream is supported by the decoder 209 // instead of initialising the stream (especially since most streams 210 // are wrapped in playlists). Also, it would probably be better to use 211 // a MythUI dialog. This would also allow us to not have to check each 212 // decoder manually 213 prog->setLabel(tr("Connecting")); 214 prog->setProgress(0); 215 QFile *file = new QFile(uri); 216 217 // create the decoder 218 prog->setLabel(tr("Creating decoder")); 219 prog->setProgress(1); 220 avfDecoder *decoder = new avfDecoder(uri, NULL, file, NULL); 221 222 prog->setLabel(tr("Initialising decoder")); 223 prog->setProgress(2); 224 if (!decoder->initialize()) 225 { 226 prog->Close(); 227 prog->deleteLater(); 228 delete decoder; 229 delete file; 230 return NULL; 231 } 232 233 prog->setLabel(tr("Reading metadata")); 234 prog->setProgress(3); 235 Metadata *metadata = decoder->readMetadata(); 236 237 prog->Close(); 238 prog->deleteLater(); 239 delete decoder; 240 delete file; 241 242 return metadata; 243 } 244 245 void AudioStreamManager::StreamAdded(const QString &uri) 246 { 247 Metadata *metadata = TestStream(uri); 248 249 if ( !metadata ) 250 { 251 VERBOSE(VB_IMPORTANT, "ERROR: Unable to get metadata for " + uri); 252 MythPopupBox::showOkPopup(gContext->GetMainWindow(), tr("ERROR"), 253 tr("Unable to get metadata for ") + uri); 254 return; 255 } 256 257 QString name = metadata->Title(); 258 259 MSqlQuery query(MSqlQuery::InitCon()); 260 query.prepare("INSERT INTO music_streams (uri,name) VALUES (:URI,:NAME)"); 261 query.bindValue(":URI", uri); 262 query.bindValue(":NAME", name); 263 264 if (!query.exec() || !query.isActive()) 265 { 266 MythDB::DBError("KeyBindings::CommitAction", query); 267 return; 268 } 269 270 PopulateStreamList(); 271 } 272 273 274 bool AudioStreamManager::PopulateStreamList() 275 { 276 MSqlQuery query(MSqlQuery::InitCon()); 277 query.prepare("SELECT id, uri, name FROM music_streams;"); 278 279 if (!query.exec() || !query.isActive()) 280 { 281 VERBOSE(VB_IMPORTANT, "ERROR: Unable to query music_streams;"); 282 return false; 283 } 284 285 m_streamList->Reset(); 286 287 while (query.next()) 288 { 289 int id = query.value(0).toInt(); 290 m_uri_map[id] = query.value(1).toString(); 291 QString name = query.value(2).toString(); 292 QVariant variant(id); 293 new MythUIButtonListItem(m_streamList, name, variant); 294 } 295 296 return true; 297 } 298 299 void AudioStreamManager::UpdateURI(const QString &uri) 300 { 301 MythTextInputDialog *uriBox; 302 MythScreenStack *stack = GetMythMainWindow()->GetStack("popup stack"); 303 304 uriBox = new MythTextInputDialog(stack, tr("Update Stream URI"), 305 FilterNone, false, uri); 306 307 connect(uriBox, SIGNAL(haveResult(QString)), SLOT(URIUpdated(QString))); 308 309 if (!uriBox->Create()) 310 { 311 VERBOSE(VB_IMPORTANT, "ERROR: Unable to create text input dialog."); 312 return; 313 } 314 315 stack->AddScreen(uriBox, false); 316 } 317 318 void AudioStreamManager::URIUpdated(const QString &uri) 319 { 320 // retest the stream 321 if (TestStream(uri) == NULL) 322 { 323 MythPopupBox::showOkPopup(gContext->GetMainWindow(), tr("ERROR"), 324 tr("Stream test failed.")); 325 return; 326 } 327 328 // get the database id 329 int id = m_streamList->GetItemCurrent()->GetData().toInt(); 330 331 // try to update the stream in the database 332 MSqlQuery query(MSqlQuery::InitCon()); 333 query.prepare("UPDATE music_streams SET uri = :URI WHERE id = :ID"); 334 query.bindValue(":URI", uri); 335 query.bindValue(":ID", id); 336 337 // if updating failed show an error and return 338 if (!query.exec() || !query.isActive()) 339 { 340 MythPopupBox::showOkPopup(gContext->GetMainWindow(), tr("ERROR"), 341 tr("Unable to update URI.")); 342 return; 343 } 344 345 // update the URI locally 346 m_uri_map[id] = uri; 347 } 348 349 void AudioStreamManager::UpdateTitle(const QString &title) 350 { 351 MythTextInputDialog *titleBox; 352 MythScreenStack *stack = GetMythMainWindow()->GetStack("popup stack"); 353 354 titleBox = new MythTextInputDialog(stack, tr("Update Stream Title"), 355 FilterNone, false, title); 356 357 connect(titleBox, SIGNAL(haveResult(QString)), 358 SLOT(TitleUpdated(QString))); 359 360 if (!titleBox->Create()) 361 { 362 VERBOSE(VB_IMPORTANT, "ERROR: Unable to create text input dialog."); 363 return; 364 } 365 366 stack->AddScreen(titleBox, false); 367 } 368 369 void AudioStreamManager::TitleUpdated(const QString &title) 370 { 371 // prevent empty titles 372 if (title.isEmpty()) 373 { 374 MythPopupBox::showOkPopup(gContext->GetMainWindow(), tr("ERROR"), 375 tr("Empty titles are not valid.")); 376 return; 377 } 378 379 // get the button and the database id 380 MythUIButtonListItem *item = m_streamList->GetItemCurrent(); 381 int id = item->GetData().toInt(); 382 383 // try to update the stream in the database 384 MSqlQuery query(MSqlQuery::InitCon()); 385 query.prepare("UPDATE music_streams SET name = :NAME WHERE id = :ID"); 386 query.bindValue(":NAME", title); 387 query.bindValue(":ID", id); 388 389 // if updating failed show an error and return 390 if (!query.exec() || !query.isActive()) 391 { 392 MythPopupBox::showOkPopup(gContext->GetMainWindow(), tr("ERROR"), 393 tr("Unable to update Title.")); 394 return; 395 } 396 397 item->SetText(title); 398 } 399 400 void AudioStreamManager::DeleteStream(int id) 401 { 402 // confirm before deleting 403 DialogCode d = MythPopupBox::Show2ButtonPopup(gContext->GetMainWindow(), 404 tr("Really Delete"), 405 tr("Are you sure you want " 406 "to delete this stream?"), 407 tr("Yes"), tr("No"), 408 kDialogCodeButton1); 409 410 // do nothing if No was selected 411 if (d == kDialogCodeButton1) 412 { 413 return; 414 } 415 416 417 MSqlQuery query(MSqlQuery::InitCon()); 418 query.prepare("DELETE FROM music_streams WHERE id = :ID"); 419 query.bindValue(":ID", id); 420 421 // if updating failed show an error and return 422 if (!query.exec() || !query.isActive()) 423 { 424 MythPopupBox::showOkPopup(gContext->GetMainWindow(), tr("ERROR"), 425 tr("Unable to delete stream.")); 426 return; 427 } 428 429 // refresh the streams 430 PopulateStreamList(); 431 } 432 433 -
mythmusic/databasebox.cpp
90 90 m_lines.push_back(line); 91 91 } 92 92 93 if (m_lines.size() < 3)93 if (m_lines.size() < 6) 94 94 { 95 95 DialogBox *dlg = new DialogBox( 96 96 gContext->GetMainWindow(), 97 tr("The theme you are using does not contain anyinfo "97 tr("The theme you are using does not contain enough info " 98 98 "lines in the music element. Please contact the theme " 99 99 "creator and ask if they could please update it.")); 100 100 101 101 dlg->AddButton(tr("OK")); 102 102 dlg->exec(); 103 103 dlg->deleteLater(); 104 105 return; 104 106 } 105 107 106 108 connect(tree, SIGNAL(itemEntered(UIListTreeType *, UIListGenericTree *)), … … 116 118 cditem = new CDCheckItem(rootNode, tr("Blechy Blech Blah"), "cd", 0); 117 119 alllists = new TreeCheckItem(rootNode, tr("All My Playlists"), "genre", 0); 118 120 allcurrent = new PlaylistTitle(rootNode, tr("Active Play Queue")); 121 allstreams = new StreamCheckItem(rootNode, tr("All Internet Streams"), "genre", 0); 119 122 120 123 tree->SetTree(rootNode); 121 124 … … 211 214 // Good, now lets grab some QListItems 212 215 if (gMusicData->all_music->putYourselfOnTheListView(allmusic)) 213 216 { 217 gMusicData->all_music->putStreamsOnTheListView(allstreams); 214 218 allmusic->setText(tr("All My Music")); 215 219 fill_list_timer->stop(); 216 220 gMusicData->all_playlists->setActiveWidget(allcurrent); … … 688 692 timeStr.sprintf("%02d:%02d", maxm, maxs); 689 693 690 694 tmpstr = tr("Length:\t") + timeStr; 691 692 695 m_lines.at(line++)->SetText(tmpstr); 693 696 } 694 697 … … 782 785 } 783 786 784 787 } 788 else if (StreamCheckItem *item_ptr = dynamic_cast<StreamCheckItem*>(item)) 789 { 790 if (active_playlist) 791 { 792 if (item_ptr->getCheck() > 0) 793 item_ptr->setCheck(0); 794 else 795 item_ptr->setCheck(2); 796 doSelected(item_ptr, false); 797 if (item_ptr = dynamic_cast<StreamCheckItem*>(parent)) 798 checkParent(item_ptr); 799 tree->Redraw(); 800 } 801 } 785 802 else if (TreeCheckItem *item_ptr = dynamic_cast<TreeCheckItem*>(item)) 786 803 { 787 804 if (active_playlist) -
mythmusic/main.cpp
34 34 #include "filescanner.h" 35 35 #include "musicplayer.h" 36 36 #include "config.h" 37 #include "audiostreammanager.h" 37 38 #ifndef USING_MINGW 38 39 #include "cdrip.h" 39 40 #include "importmusic.h" … … 211 212 // connect(import, SIGNAL(Changed()), SLOT(RebuildMusicTree())); 212 213 } 213 214 215 void manageStreams(void) 216 { 217 MythScreenStack *mainStack = GetMythMainWindow()->GetMainStack(); 218 AudioStreamManager *manager = new AudioStreamManager(mainStack); 219 if (!manager->Create()) 220 { 221 delete manager; 222 return; 223 } 224 225 mainStack->AddScreen(manager); 226 } 227 214 228 void RebuildMusicTree(void) 215 229 { 216 230 if (!gMusicData->all_music || !gMusicData->all_playlists) … … 291 305 postMusic(); 292 306 } 293 307 } 308 else if (sel == "music_manage_streams") 309 { 310 manageStreams(); 311 } 294 312 } 295 313 296 314 int runMenu(QString which_menu) -
mythmusic/avfdecoder.h
30 30 31 31 void flush(bool = FALSE); 32 32 void deinit(); 33 bool openStream(); 34 QString downloadPlaylist(const QString &uri); 35 void parseM3U(const QString &uri); 36 void parseASX(const QString &uri); 37 void parsePLS(const QString &uri); 33 38 34 39 bool inited, user_stop; 35 40 int stat; … … 57 62 AVCodecContext *m_audioEnc, *m_audioDec; 58 63 AVPacket m_pkt1; 59 64 AVPacket *m_pkt; 65 QStringList m_pl_elements; 66 QStringList::iterator m_pl_it; 60 67 61 68 int errcode; 62 69 -
mythmusic/audiostreammanager.h
1 /** 2 * \file AudioStreamManager.h 3 * \author Micah Galizia 4 * \brief Provides an interface to add online streams in mythmusic. 5 */ 6 7 #ifndef AUDIO_STREAM_MANAGER_H_ 8 #define AUDIO_STREAM_MANAGER_H_ 9 10 // MythUI 11 #include <mythscreentype.h> 12 13 class MythUIText; 14 class MythUIButton; 15 class MythUIButtonList; 16 class Metadata; 17 18 19 /** \class AudioStreamManager 20 * \brief Screen that manages audio streams. 21 */ 22 class AudioStreamManager : public MythScreenType 23 { 24 Q_OBJECT 25 public: 26 AudioStreamManager(MythScreenStack *parent); 27 28 virtual ~AudioStreamManager(); 29 30 virtual bool Create(void); 31 32 virtual bool keyPressEvent(QKeyEvent *); 33 34 protected: 35 36 virtual bool PopulateStreamList(); 37 38 virtual void UpdateURI(const QString &uri); 39 40 virtual void UpdateTitle(const QString &title); 41 42 virtual void DeleteStream(int id); 43 44 virtual Metadata* TestStream(const QString & uri); 45 46 private slots: 47 void StreamClicked(MythUIButtonListItem*); 48 void StreamSelected(MythUIButtonListItem*); 49 void AddStream(); 50 void StreamAdded(const QString &); 51 void URIUpdated(const QString &); 52 void TitleUpdated(const QString &); 53 private: 54 55 MythUIButton *m_addButton; 56 MythUIButtonList *m_streamList; 57 MythUIText *m_title_text; 58 MythUIText *m_uri_text; 59 QMap<int, QString> m_uri_map; 60 }; 61 62 #endif /* AUDIO_STREAM_MANAGER_H_ */ -
theme/default/music-ui.xml
1525 1525 <position>170,150</position> 1526 1526 </progressbar> 1527 1527 </window> 1528 1529 <window name="stream_manager"> 1530 1531 <font name="active" face="Arial"> 1532 <color>#ffffff</color> 1533 <size>18</size> 1534 <bold>yes</bold> 1535 </font> 1528 1536 1537 <font name="inactive" face="Arial"> 1538 <color>#9999cc</color> 1539 <size>18</size> 1540 <bold>yes</bold> 1541 </font> 1542 1543 <textarea name="title_text" from="basetextarea"> 1544 <area>20,20,760,40</area> 1545 <value>Contexts</value> 1546 <align>center</align> 1547 </textarea> 1548 1549 <button name="add_stream" from="basebutton"> 1550 <position>20,80</position> 1551 </button> 1552 1553 <buttonlist name="stream_list" from="basebuttonlist"> 1554 <area>20,140,370,410</area> 1555 <showarrow>no</showarrow> 1556 </buttonlist> 1557 1558 <textarea name="uri_text" from="basetextarea"> 1559 <area>20,570,760,30</area> 1560 <font>basemedium</font> 1561 </textarea> 1562 </window> 1563 1529 1564 </mythuitheme> -
theme/menus/musicmenu.xml
135 135 <description lang="NB">Konfigurer avspilling og CD-ripping</description> 136 136 <action>CONFIGPLUGIN mythmusic</action> 137 137 </button> 138 139 <button> 140 <type>MUSIC_MANAGE_STREAMS</type> 141 <text>Manage Online Stream</text> 142 <description>Manage internet audio stream.</description> 143 <action>MUSIC_MANAGE_STREAMS</action> 144 </button> 138 145 139 146 </mythmenu>