Ticket #7868: radio5.patch

File radio5.patch, 36.5 KB (added by micahgalizia@…, 14 years ago)

Adds streaming audio to myth music.

  • mythmusic/mythmusic.pro

     
    3939HEADERS += editmetadata.h smartplaylist.h search.h genres.h
    4040HEADERS += treebuilders.h importmusic.h
    4141HEADERS += filescanner.h libvisualplugin.h musicplayer.h miniplayer.h
    42 HEADERS += playlistcontainer.h
     42HEADERS += playlistcontainer.h audiostreammanager.h
    4343HEADERS += mythlistview-qt3.h mythlistbox-qt3.h
    4444
    4545SOURCES += cddecoder.cpp cdrip.cpp decoder.cpp
     
    5858SOURCES += avfdecoder.cpp editmetadata.cpp smartplaylist.cpp search.cpp
    5959SOURCES += treebuilders.cpp importmusic.cpp
    6060SOURCES += filescanner.cpp libvisualplugin.cpp musicplayer.cpp miniplayer.cpp
    61 SOURCES += playlistcontainer.cpp
     61SOURCES += playlistcontainer.cpp audiostreammanager.cpp
    6262SOURCES += mythlistview-qt3.cpp mythlistbox-qt3.cpp
    6363
    6464macx {
  • mythmusic/treecheckitem.cpp

     
    148148{
    149149}
    150150
     151StreamCheckItem::StreamCheckItem(UIListGenericTree *parent, const QString &text,
     152                                 const QString &level, int track)
     153               : TreeCheckItem(parent, text, level, track)
     154{
     155}
     156
    151157PlaylistItem::PlaylistItem(UIListGenericTree *parent, const QString &title)
    152158            : UIListGenericTree(parent, title, "PLAYLISTITEM", -1)
    153159{
  • mythmusic/metadata.cpp

     
    10671067    for (; it != m_all_music.end(); ++it)
    10681068        music_map[(*it)->ID()] = *it;
    10691069
     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
    10701093    //  Build a tree to reflect current state of
    10711094    //  the metadata. Once built, sort it.
    10721095
     
    11391162        new_item->setCheck(false); //  Avoiding -Wall
    11401163    }
    11411164}
     1165bool AllMusic::putStreamsOnTheListView(TreeCheckItem *where)
     1166{
     1167    MusicMap::iterator it = stream_map.begin();
    11421168
     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
    11431178QString AllMusic::getLabel(int an_id, bool *error_flag)
    11441179{
    11451180    QString a_label;
     
    11971232        {
    11981233            return music_map[an_id];
    11991234        }
     1235        else if (stream_map.contains(an_id))
     1236        {
     1237            return stream_map[an_id];
     1238        }
    12001239    }
    12011240    else if(an_id < 0)
    12021241    {
  • mythmusic/dbcheck.cpp

     
    1111#include "mythtv/mythdb.h"
    1212#include "mythtv/schemawizard.h"
    1313
    14 const QString currentDatabaseVersion = "1017";
     14const QString currentDatabaseVersion = "1018";
    1515
    1616static bool doUpgradeMusicDatabaseSchema(QString &dbver);
    1717
     
    777777            return false;
    778778    }
    779779
     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
    780796    return true;
    781797}
  • mythmusic/treecheckitem.h

     
    4040                const QString &level, int track);
    4141};
    4242
     43class StreamCheckItem : public TreeCheckItem
     44{
     45  public:
     46    StreamCheckItem(UIListGenericTree *parent, const QString &text,
     47                    const QString &level, int track);
     48};
     49
     50
    4351class PlaylistItem : public UIListGenericTree
    4452{
    4553  public:
  • mythmusic/databasebox.h

     
    9393    PlaylistTrack       *track_held;
    9494    TreeCheckItem       *allmusic;
    9595    TreeCheckItem       *alllists;
     96    TreeCheckItem       *allstreams;
    9697    PlaylistTitle       *allcurrent;
    9798    Playlist            *active_playlist;
    9899
  • mythmusic/metadata.h

     
    346346    void        setSorting(QString a_paths);
    347347    bool        putYourselfOnTheListView(TreeCheckItem *where);
    348348    void        putCDOnTheListView(CDCheckItem *where);
     349    bool        putStreamsOnTheListView(TreeCheckItem *where);
    349350    bool        doneLoading(){return m_done_loading;}
    350351    bool        cleanOutThreads();
    351352    int         getCDTrackCount(){return m_cd_data.count();}
     
    367368    //  you NEED to clear and rebuild the map
    368369    typedef QMap<int, Metadata*> MusicMap;
    369370    MusicMap music_map;
     371    MusicMap stream_map;
    370372
    371373    typedef QList<Metadata>       ValueMetadata;
    372374    ValueMetadata                 m_cd_data; //  More than one cd player?
  • mythmusic/avfdecoder.cpp

     
    1313
    1414    Revision History
    1515        - 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.
    1819*/
    1920
    2021// C++ headers
     
    2526#include <QObject>
    2627#include <QIODevice>
    2728#include <QFile>
     29#include <QXmlStreamReader>
    2830
    2931// Myth headers
    3032#include <mythcontext.h>
    3133#include <audiooutput.h>
    3234#include <mythverbose.h>
     35#include <libmythdb/httpcomms.h>
    3336
    3437using namespace std;
    3538
     
    4447#include "metaiomp4.h"
    4548#include "metaiowavpack.h"
    4649
     50#define PLAYLIST_EXTENSIONS ".m3u|.asx|.pls"
     51enum 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
    4764avfDecoder::avfDecoder(const QString &file, DecoderFactory *d, QIODevice *i,
    4865                       AudioOutput *o) :
    4966    Decoder(d, i, o),
     
    128145
    129146bool avfDecoder::initialize()
    130147{
     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
     204bool avfDecoder::openStream()
     205{
    131206    bks = blockSize();
    132207
    133208    inited = user_stop = done = finish = FALSE;
     
    136211    seekTime = -1.0;
    137212    totalTime = 0.0;
    138213
    139     filename = ((QFile *)input())->fileName();
    140 
    141214    if (!m_samples)
    142215    {
    143216        m_samples = (int16_t *)av_mallocz(AVCODEC_MAX_AUDIO_FRAME_SIZE / 2 *
     
    159232    // register av codecs
    160233    av_register_all();
    161234
    162     // open the media file
    163     // this should populate the input context
    164235    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);
    167243
     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
    168255    if (error < 0)
    169256    {
    170257        VERBOSE(VB_IMPORTANT, QString("Could not open file (%1)").arg(filename));
     
    256343    if (output())
    257344    {
    258345        const AudioSettings settings(
    259             16 /*bits*/, m_audioDec->channels, m_audioDec->codec_id, 
     346            16 /*bits*/, m_audioDec->channels, m_audioDec->codec_id,
    260347            m_audioDec->sample_rate, false /* AC3/DTS pass through */);
    261348        output()->Reconfigure(settings);
    262349        output()->SetSourceBitrate(m_audioDec->bit_rate);
    263350    }
    264351
    265     inited = TRUE;
    266     return TRUE;
     352    return true;
    267353}
    268354
    269355void avfDecoder::seek(double pos)
     
    276362    inited = user_stop = done = finish = FALSE;
    277363    len = freq = bitrate = 0;
    278364    stat = m_channels = 0;
    279     setInput(0);
    280     setOutput(0);
    281365
     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
    282373    // Cleanup here
    283374    if (m_inputContext)
    284375    {
     
    347438            VERBOSE(VB_IMPORTANT, "Read frame failed");
    348439            VERBOSE(VB_FILE, ("... for file '" + filename) + "'");
    349440            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
    350471            finish = TRUE;
    351472            break;
    352473        }
     
    456577        return new MetaIOAVFComment();
    457578}
    458579
     580QString 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
     617void 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
     642void 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
     679void 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
    459706bool avfDecoderFactory::supports(const QString &source) const
    460707{
    461708    QStringList list = extension().split("|", QString::SkipEmptyParts);
     
    465712            return true;
    466713    }
    467714
    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;
    469727}
    470728
    471729const QString &avfDecoderFactory::extension() const
    472730{
    473731    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);
    475734    return ext;
    476735}
    477736
     
    500759
    501760    return decoder;
    502761}
    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
     28AudioStreamManager::AudioStreamManager(MythScreenStack *parent)
     29    : MythScreenType (parent, "audiostreammanager")
     30{
     31}
     32
     33
     34AudioStreamManager::~AudioStreamManager()
     35{
     36}
     37
     38
     39bool 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
     101bool 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
     144void 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
     172void AudioStreamManager::StreamSelected(MythUIButtonListItem *item)
     173{
     174    m_uri_text->SetText(m_uri_map[item->GetData().toInt()]);
     175}
     176
     177void 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
     195Metadata* 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
     245void 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
     274bool 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
     299void 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
     318void 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
     349void 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
     369void 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
     400void 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

     
    9090            m_lines.push_back(line);
    9191    }
    9292
    93     if (m_lines.size() < 3)
     93    if (m_lines.size() < 6)
    9494    {
    9595        DialogBox *dlg = new DialogBox(
    9696            gContext->GetMainWindow(),
    97             tr("The theme you are using does not contain any info "
     97            tr("The theme you are using does not contain enough info "
    9898               "lines in the music element. Please contact the theme "
    9999               "creator and ask if they could please update it."));
    100100
    101101        dlg->AddButton(tr("OK"));
    102102        dlg->exec();
    103103        dlg->deleteLater();
     104
     105        return;
    104106    }
    105107
    106108    connect(tree, SIGNAL(itemEntered(UIListTreeType *, UIListGenericTree *)),
     
    116118        cditem = new CDCheckItem(rootNode, tr("Blechy Blech Blah"), "cd", 0);
    117119    alllists = new TreeCheckItem(rootNode, tr("All My Playlists"), "genre", 0);
    118120    allcurrent = new PlaylistTitle(rootNode, tr("Active Play Queue"));
     121    allstreams = new StreamCheckItem(rootNode, tr("All Internet Streams"), "genre", 0);
    119122
    120123    tree->SetTree(rootNode);
    121124
     
    211214        //  Good, now lets grab some QListItems
    212215        if (gMusicData->all_music->putYourselfOnTheListView(allmusic))
    213216        {
     217            gMusicData->all_music->putStreamsOnTheListView(allstreams);
    214218            allmusic->setText(tr("All My Music"));
    215219            fill_list_timer->stop();
    216220            gMusicData->all_playlists->setActiveWidget(allcurrent);
     
    688692                timeStr.sprintf("%02d:%02d", maxm, maxs);
    689693
    690694            tmpstr = tr("Length:\t") + timeStr;
    691 
    692695            m_lines.at(line++)->SetText(tmpstr);
    693696        }
    694697
     
    782785        }
    783786
    784787    }
     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    }
    785802    else if (TreeCheckItem *item_ptr = dynamic_cast<TreeCheckItem*>(item))
    786803    {
    787804        if (active_playlist)
  • mythmusic/main.cpp

     
    3434#include "filescanner.h"
    3535#include "musicplayer.h"
    3636#include "config.h"
     37#include "audiostreammanager.h"
    3738#ifndef USING_MINGW
    3839#include "cdrip.h"
    3940#include "importmusic.h"
     
    211212//   connect(import, SIGNAL(Changed()), SLOT(RebuildMusicTree()));
    212213}
    213214
     215void 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
    214228void RebuildMusicTree(void)
    215229{
    216230    if (!gMusicData->all_music || !gMusicData->all_playlists)
     
    291305                postMusic();
    292306        }
    293307    }
     308    else if (sel == "music_manage_streams")
     309    {
     310        manageStreams();
     311    }
    294312}
    295313
    296314int runMenu(QString which_menu)
  • mythmusic/avfdecoder.h

     
    3030
    3131    void flush(bool = FALSE);
    3232    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);
    3338
    3439    bool inited, user_stop;
    3540    int stat;
     
    5762    AVCodecContext *m_audioEnc, *m_audioDec;
    5863    AVPacket m_pkt1;
    5964    AVPacket *m_pkt;
     65    QStringList m_pl_elements;
     66    QStringList::iterator m_pl_it;
    6067
    6168    int errcode;
    6269
  • 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
     13class MythUIText;
     14class MythUIButton;
     15class MythUIButtonList;
     16class Metadata;
     17
     18
     19/** \class AudioStreamManager
     20 *  \brief Screen that manages audio streams.
     21 */
     22class AudioStreamManager : public MythScreenType
     23{
     24    Q_OBJECT
     25public:
     26    AudioStreamManager(MythScreenStack *parent);
     27
     28    virtual ~AudioStreamManager();
     29
     30    virtual bool Create(void);
     31
     32    virtual bool keyPressEvent(QKeyEvent *);
     33
     34protected:
     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
     46private 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 &);
     53private:
     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

     
    15251525            <position>170,150</position>
    15261526        </progressbar>
    15271527    </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>
    15281536
     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
    15291564</mythuitheme>
  • theme/menus/musicmenu.xml

     
    135135     <description lang="NB">Konfigurer avspilling og CD-ripping</description>
    136136     <action>CONFIGPLUGIN mythmusic</action>
    137137   </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>
    138145
    139146</mythmenu>