![]() |
Mixxx
|
00001 /*************************************************************************** 00002 engineshoutcast.cpp - class to shoutcast the mix 00003 ------------------- 00004 copyright : (C) 2007 by Wesley Stessens 00005 (C) 2007 by Albert Santoni 00006 : (C) 2010 by Tobias Rafreider 00007 ***************************************************************************/ 00008 00009 /*************************************************************************** 00010 * * 00011 * This program is free software; you can redistribute it and/or modify * 00012 * it under the terms of the GNU General Public License as published by * 00013 * the Free Software Foundation; either version 2 of the License, or * 00014 * (at your option) any later version. * 00015 * * 00016 ***************************************************************************/ 00017 00018 #include "engineshoutcast.h" 00019 //#include "controllogpotmeter.h" 00020 #include "configobject.h" 00021 #include "dlgprefshoutcast.h" 00022 00023 00024 #include "recording/encodervorbis.h" 00025 #include "recording/encodermp3.h" 00026 00027 #include "playerinfo.h" 00028 #include "trackinfoobject.h" 00029 #ifdef __WINDOWS__ 00030 #include <windows.h> 00031 //sleep on linux assumes seconds where as Sleep on Windows assumes milliseconds 00032 #define sleep(x) Sleep(x*1000) 00033 #endif 00034 00035 #define TIMEOUT 10 00036 00037 #include <QDebug> 00038 #include <QMutexLocker> 00039 #include <stdio.h> // currently used for writing to stdout 00040 00041 00042 /* 00043 * Initialize EngineShoutcast 00044 */ 00045 EngineShoutcast::EngineShoutcast(ConfigObject<ConfigValue> *_config) 00046 : m_pMetaData(), 00047 m_pShout(NULL), 00048 m_pShoutMetaData(NULL), 00049 m_pConfig(_config), 00050 m_encoder(NULL), 00051 m_pUpdateShoutcastFromPrefs(NULL), 00052 m_pMasterSamplerate(new ControlObjectThread( 00053 ControlObject::getControl(ConfigKey("[Master]", "samplerate")))), 00054 m_shoutMutex(QMutex::Recursive) { 00055 00056 m_pShout = 0; 00057 m_iShoutStatus = 0; 00058 m_pShoutcastNeedUpdateFromPrefs = new ControlObject(ConfigKey("[Shoutcast]","update_from_prefs")); 00059 m_pUpdateShoutcastFromPrefs = new ControlObjectThreadMain(m_pShoutcastNeedUpdateFromPrefs); 00060 00061 m_bQuit = false; 00062 00063 m_firstCall = false; 00064 // Initialize libshout 00065 shout_init(); 00066 00067 if (!(m_pShout = shout_new())) { 00068 errorDialog(tr("Mixxx encountered a problem"), tr("Could not allocate shout_t")); 00069 return; 00070 } 00071 00072 if (!(m_pShoutMetaData = shout_metadata_new())) { 00073 errorDialog(tr("Mixxx encountered a problem"), tr("Could not allocate shout_metadata_t")); 00074 return; 00075 } 00076 if (shout_set_nonblocking(m_pShout, 1) != SHOUTERR_SUCCESS) { 00077 errorDialog(tr("Error setting non-blocking mode:"), shout_get_error(m_pShout)); 00078 return; 00079 } 00080 } 00081 00082 /* 00083 * Cleanup EngineShoutcast 00084 */ 00085 EngineShoutcast::~EngineShoutcast() 00086 { 00087 QMutexLocker locker(&m_shoutMutex); 00088 00089 if (m_encoder){ 00090 m_encoder->flush(); 00091 delete m_encoder; 00092 } 00093 00094 delete m_pUpdateShoutcastFromPrefs; 00095 delete m_pShoutcastNeedUpdateFromPrefs; 00096 delete m_pMasterSamplerate; 00097 00098 if (m_pShoutMetaData) 00099 shout_metadata_free(m_pShoutMetaData); 00100 if (m_pShout) { 00101 shout_close(m_pShout); 00102 shout_free(m_pShout); 00103 } 00104 shout_shutdown(); 00105 } 00106 00107 bool EngineShoutcast::serverDisconnect() 00108 { 00109 QMutexLocker locker(&m_shoutMutex); 00110 if (m_encoder){ 00111 m_encoder->flush(); 00112 delete m_encoder; 00113 m_encoder = NULL; 00114 } 00115 00116 if (m_pShout) { 00117 shout_close(m_pShout); 00118 return true; 00119 } 00120 return false; //if no connection has been established, nothing can be disconnected 00121 } 00122 00123 bool EngineShoutcast::isConnected() 00124 { 00125 QMutexLocker locker(&m_shoutMutex); 00126 if (m_pShout) { 00127 m_iShoutStatus = shout_get_connected(m_pShout); 00128 if (m_iShoutStatus == SHOUTERR_CONNECTED) 00129 return true; 00130 } 00131 return false; 00132 } 00133 /* 00134 * Update EngineShoutcast values from the preferences. 00135 */ 00136 void EngineShoutcast::updateFromPreferences() 00137 { 00138 QMutexLocker locker(&m_shoutMutex); 00139 qDebug() << "EngineShoutcast: updating from preferences"; 00140 00141 m_pUpdateShoutcastFromPrefs->slotSet(0.0f); 00142 00143 //Convert a bunch of QStrings to QByteArrays so we can get regular C char* strings to pass to libshout. 00144 QByteArray baHost = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"host")).toLatin1(); 00145 QByteArray baServerType = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"servertype")).toLatin1(); 00146 QByteArray baPort = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"port")).toLatin1(); 00147 QByteArray baMountPoint = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"mountpoint")).toLatin1(); 00148 QByteArray baLogin = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"login")).toLatin1(); 00149 QByteArray baPassword = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"password")).toLatin1(); 00150 QByteArray baStreamName = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"stream_name")).toLatin1(); 00151 QByteArray baStreamWebsite = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"stream_website")).toLatin1(); 00152 QByteArray baStreamDesc = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"stream_desc")).toLatin1(); 00153 QByteArray baStreamGenre = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"stream_genre")).toLatin1(); 00154 QByteArray baStreamPublic = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"stream_public")).toLatin1(); 00155 QByteArray baBitrate = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"bitrate")).toLatin1(); 00156 00157 m_baFormat = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"format")).toLatin1(); 00158 00159 m_custom_metadata = (bool)m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"enable_metadata")).toInt(); 00160 m_baCustom_title = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"custom_title")).toLatin1(); 00161 m_baCustom_artist = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"custom_artist")).toLatin1(); 00162 00163 int format; 00164 int len; 00165 int protocol; 00166 00167 00168 if (shout_set_host(m_pShout, baHost.data()) != SHOUTERR_SUCCESS) { 00169 errorDialog("Error setting hostname!", shout_get_error(m_pShout)); 00170 return; 00171 } 00172 00173 if (shout_set_protocol(m_pShout, SHOUT_PROTOCOL_HTTP) != SHOUTERR_SUCCESS) { 00174 errorDialog("Error setting protocol!", shout_get_error(m_pShout)); 00175 return; 00176 } 00177 00178 if (shout_set_port(m_pShout, baPort.toUInt()) != SHOUTERR_SUCCESS) { 00179 errorDialog("Error setting port!", shout_get_error(m_pShout)); 00180 return; 00181 } 00182 00183 if (shout_set_password(m_pShout, baPassword.data()) != SHOUTERR_SUCCESS) { 00184 errorDialog("Error setting password!", shout_get_error(m_pShout)); 00185 return; 00186 } 00187 if (shout_set_mount(m_pShout, baMountPoint.data()) != SHOUTERR_SUCCESS) { 00188 errorDialog("Error setting mount!", shout_get_error(m_pShout)); 00189 return; 00190 } 00191 00192 if (shout_set_user(m_pShout, baLogin.data()) != SHOUTERR_SUCCESS) { 00193 errorDialog("Error setting username!", shout_get_error(m_pShout)); 00194 return; 00195 } 00196 if (shout_set_name(m_pShout, baStreamName.data()) != SHOUTERR_SUCCESS) { 00197 errorDialog("Error setting stream name!", shout_get_error(m_pShout)); 00198 return; 00199 } 00200 if (shout_set_description(m_pShout, baStreamDesc.data()) != SHOUTERR_SUCCESS) { 00201 errorDialog("Error setting stream description!", shout_get_error(m_pShout)); 00202 return; 00203 } 00204 if (shout_set_genre(m_pShout, baStreamGenre.data()) != SHOUTERR_SUCCESS) { 00205 errorDialog("Error setting stream genre!", shout_get_error(m_pShout)); 00206 return; 00207 } 00208 if (shout_set_url(m_pShout, baStreamWebsite.data()) != SHOUTERR_SUCCESS) { 00209 errorDialog("Error setting stream url!", shout_get_error(m_pShout)); 00210 return; 00211 } 00212 00213 00214 if ( !qstrcmp(m_baFormat.data(), "MP3")) { 00215 format = SHOUT_FORMAT_MP3; 00216 } 00217 else if ( !qstrcmp(m_baFormat.data(), "Ogg Vorbis")) { 00218 format = SHOUT_FORMAT_OGG; 00219 } 00220 else { 00221 qDebug() << "Error: unknown format:" << m_baFormat.data(); 00222 return; 00223 } 00224 00225 if (shout_set_format(m_pShout, format) != SHOUTERR_SUCCESS) { 00226 errorDialog("Error setting soutcast format!", shout_get_error(m_pShout)); 00227 return; 00228 } 00229 00230 if ((len = baBitrate.indexOf(' ')) != -1) { 00231 baBitrate.resize(len); 00232 } 00233 int iBitrate = baBitrate.toInt(); 00234 00235 int iMasterSamplerate = m_pMasterSamplerate->get(); 00236 if (format == SHOUT_FORMAT_OGG && iMasterSamplerate == 96000) { 00237 errorDialog("Broadcasting at 96kHz with Ogg Vorbis is not currently " 00238 "supported. Please try a different sample-rate or switch " 00239 "to a different encoding.", 00240 "See https://bugs.launchpad.net/mixxx/+bug/686212 for more " 00241 "information."); 00242 return; 00243 } 00244 00245 if (shout_set_audio_info(m_pShout, SHOUT_AI_BITRATE, baBitrate.data()) != SHOUTERR_SUCCESS) { 00246 errorDialog("Error setting bitrate", shout_get_error(m_pShout)); 00247 return; 00248 } 00249 00250 if ( ! qstricmp(baServerType.data(), "Icecast 2")) { 00251 protocol = SHOUT_PROTOCOL_HTTP; 00252 } else if ( ! qstricmp(baServerType.data(), "Shoutcast")) { 00253 protocol = SHOUT_PROTOCOL_ICY; 00254 } else if ( ! qstricmp(baServerType.data(), "Icecast 1")) { 00255 protocol = SHOUT_PROTOCOL_XAUDIOCAST; 00256 } else { 00257 errorDialog("Error: unknown server protocol!", shout_get_error(m_pShout)); 00258 return; 00259 } 00260 00261 if (( protocol == SHOUT_PROTOCOL_ICY ) && ( format != SHOUT_FORMAT_MP3)) { 00262 errorDialog("Error: libshout only supports Shoutcast with MP3 format!", shout_get_error(m_pShout)); 00263 return; 00264 } 00265 00266 if (shout_set_protocol(m_pShout, protocol) != SHOUTERR_SUCCESS) { 00267 errorDialog("Error setting protocol!", shout_get_error(m_pShout)); 00268 return; 00269 } 00270 00271 // Initialize m_encoder 00272 if(m_encoder) { 00273 delete m_encoder; //delete m_encoder if it has been initalized (with maybe) different bitrate 00274 } 00275 if ( ! qstrcmp(m_baFormat, "MP3")) { 00276 m_encoder = new EncoderMp3(this); 00277 00278 } 00279 else if ( ! qstrcmp(m_baFormat, "Ogg Vorbis")) { 00280 m_encoder = new EncoderVorbis(this); 00281 } 00282 else { 00283 qDebug() << "**** Unknown Encoder Format"; 00284 return; 00285 } 00286 if (m_encoder->initEncoder(iBitrate) < 0) { 00287 //e.g., if lame is not found 00288 //init m_encoder itself will display a message box 00289 qDebug() << "**** Encoder init failed"; 00290 delete m_encoder; 00291 m_encoder = NULL; 00292 } 00293 00294 } 00295 00296 /* 00297 * Reset the Server state and Connect to the Server. 00298 * 00299 */ 00300 bool EngineShoutcast::serverConnect() 00301 { 00302 QMutexLocker locker(&m_shoutMutex); 00303 // set to busy in case another thread calls one of the other 00304 // EngineShoutcast calls 00305 m_iShoutStatus = SHOUTERR_BUSY; 00306 // reset the number of failures to zero 00307 m_iShoutFailures = 0; 00308 // set to a high number to automatically update the metadata 00309 // on the first change 00310 m_iMetaDataLife = 31337; 00311 //If static metadata is available, we only need to send metadata one time 00312 m_firstCall = false; 00313 00314 /*Check if m_encoder is initalized 00315 * Encoder is initalized in updateFromPreferences which is called always before serverConnect() 00316 * If m_encoder is NULL, then we propably want to use MP3 streaming, however, lame could not be found 00317 * It does not make sense to connect 00318 */ 00319 if(m_encoder == NULL){ 00320 m_pConfig->set(ConfigKey("[Shoutcast]","enabled"),ConfigValue("0")); 00321 return false; 00322 } 00323 const int iMaxTries = 3; 00324 while (!m_bQuit && m_iShoutFailures < iMaxTries) { 00325 if (m_pShout) 00326 shout_close(m_pShout); 00327 00328 m_iShoutStatus = shout_open(m_pShout); 00329 if (m_iShoutStatus == SHOUTERR_SUCCESS) 00330 m_iShoutStatus = SHOUTERR_CONNECTED; 00331 00332 if ((m_iShoutStatus == SHOUTERR_BUSY) || 00333 (m_iShoutStatus == SHOUTERR_CONNECTED) || 00334 (m_iShoutStatus == SHOUTERR_SUCCESS)) 00335 break; 00336 00337 m_iShoutFailures++; 00338 qDebug() << "Shoutcast failed connect. Failures:" << m_iShoutFailures; 00339 sleep(1); 00340 } 00341 if (m_iShoutFailures == iMaxTries) { 00342 if (m_pShout) 00343 shout_close(m_pShout); 00344 m_pConfig->set(ConfigKey("[Shoutcast]","enabled"),ConfigValue("0")); 00345 } 00346 if (m_bQuit) { 00347 if (m_pShout) 00348 shout_close(m_pShout); 00349 return false; 00350 } 00351 00352 m_iShoutFailures = 0; 00353 int timeout = 0; 00354 while (m_iShoutStatus == SHOUTERR_BUSY && timeout < TIMEOUT) { 00355 qDebug() << "Connection pending. Sleeping..."; 00356 sleep(1); 00357 m_iShoutStatus = shout_get_connected(m_pShout); 00358 ++ timeout; 00359 } 00360 if (m_iShoutStatus == SHOUTERR_CONNECTED) { 00361 qDebug() << "***********Connected to Shoutcast server..."; 00362 return true; 00363 } 00364 //otherwise disable shoutcast in preferences 00365 m_pConfig->set(ConfigKey("[Shoutcast]","enabled"),ConfigValue("0")); 00366 if(m_pShout){ 00367 shout_close(m_pShout); 00368 //errorDialog(tr("Mixxx could not connect to the server"), tr("Please check your connection to the Internet and verify that your username and password are correct.")); 00369 } 00370 00371 return false; 00372 } 00373 00374 /* 00375 * Called by the encoder in method 'encodebuffer()' to flush the stream to the server. 00376 */ 00377 void EngineShoutcast::write(unsigned char *header, unsigned char *body, 00378 int headerLen, int bodyLen) 00379 { 00380 QMutexLocker locker(&m_shoutMutex); 00381 int ret; 00382 00383 if (!m_pShout) 00384 return; 00385 00386 if (m_iShoutStatus == SHOUTERR_CONNECTED) { 00387 // Send header if there is one 00388 if ( headerLen > 0 ) { 00389 ret = shout_send(m_pShout, header, headerLen); 00390 if (ret != SHOUTERR_SUCCESS) { 00391 qDebug() << "DEBUG: Send error: " << shout_get_error(m_pShout); 00392 if ( m_iShoutFailures > 3 ){ 00393 if(!serverConnect()) 00394 errorDialog(tr("Lost connection to streaming server"), tr("Please check your connection to the Internet and verify that your username and password are correct.")); 00395 } 00396 else{ 00397 m_iShoutFailures++; 00398 } 00399 00400 return; 00401 } else { 00402 //qDebug() << "yea I kinda sent header"; 00403 } 00404 } 00405 00406 ret = shout_send(m_pShout, body, bodyLen); 00407 if (ret != SHOUTERR_SUCCESS) { 00408 qDebug() << "DEBUG: Send error: " << shout_get_error(m_pShout); 00409 if ( m_iShoutFailures > 3 ){ 00410 if(!serverConnect()) 00411 errorDialog(tr("Lost connection to streaming server"), tr("Please check your connection to the Internet and verify that your username and password are correct.")); 00412 } 00413 else{ 00414 m_iShoutFailures++; 00415 } 00416 00417 return; 00418 } else { 00419 //qDebug() << "yea I kinda sent footer"; 00420 } 00421 if (shout_queuelen(m_pShout) > 0) 00422 printf("DEBUG: queue length: %d\n", (int)shout_queuelen(m_pShout)); 00423 } else { 00424 qDebug() << "Error connecting to Shoutcast server:" << shout_get_error(m_pShout); 00425 // errorDialog(tr("Shoutcast aborted connect after 3 tries"), tr("Please check your connection to the Internet and verify that your username and password are correct.")); 00426 } 00427 } 00428 00429 /* 00430 * This is called by the Engine implementation for each sample. 00431 * Encode and send the stream, as well as check for metadata changes. 00432 */ 00433 void EngineShoutcast::process(const CSAMPLE *, const CSAMPLE *pOut, const int iBufferSize) 00434 { 00435 QMutexLocker locker(&m_shoutMutex); 00436 //Check to see if Shoutcast is enabled, and pass the samples off to be broadcast if necessary. 00437 bool prefEnabled = (m_pConfig->getValueString(ConfigKey("[Shoutcast]","enabled")).toInt() == 1); 00438 00439 if (prefEnabled) { 00440 if(!isConnected()){ 00441 //Initialize the m_pShout structure with the info from Mixxx's m_shoutcast preferences. 00442 updateFromPreferences(); 00443 00444 if(serverConnect()){ 00445 ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties(); 00446 props->setType(DLG_INFO); 00447 props->setTitle(tr("Live broadcasting")); 00448 props->setText(tr("Mixxx has successfully connected to the shoutcast server")); 00449 ErrorDialogHandler::instance()->requestErrorDialog(props); 00450 } 00451 else{ 00452 errorDialog(tr("Mixxx could not connect to streaming server"), tr("Please check your connection to the Internet and verify that your username and password are correct.")); 00453 00454 } 00455 } 00456 //send to shoutcast, if connection has been established 00457 if (m_iShoutStatus != SHOUTERR_CONNECTED) 00458 return; 00459 00460 if (iBufferSize > 0 && m_encoder){ 00461 m_encoder->encodeBuffer(pOut, iBufferSize); //encode and send to shoutcast 00462 } 00463 //Check if track has changed and submit its new metadata to shoutcast 00464 if (metaDataHasChanged()) 00465 updateMetaData(); 00466 00467 if (m_pUpdateShoutcastFromPrefs->get() > 0.0f){ 00468 /* 00469 * You cannot change bitrate, hostname, etc while connected to a stream 00470 */ 00471 serverDisconnect(); 00472 updateFromPreferences(); 00473 serverConnect(); 00474 } 00475 } 00476 //if shoutcast is disabled 00477 else{ 00478 if(isConnected()){ 00479 serverDisconnect(); 00480 ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties(); 00481 props->setType(DLG_INFO); 00482 props->setTitle(tr("Live broadcasting")); 00483 props->setText(tr("Mixxx has successfully disconnected to the shoutcast server")); 00484 00485 ErrorDialogHandler::instance()->requestErrorDialog(props); 00486 } 00487 } 00488 } 00489 00490 /* 00491 * Check if the metadata has changed since the previous check. 00492 * We also check when was the last check performed to avoid using 00493 * too much CPU and as well to avoid changing the metadata during 00494 * scratches. 00495 */ 00496 bool EngineShoutcast::metaDataHasChanged() 00497 { 00498 QMutexLocker locker(&m_shoutMutex); 00499 TrackPointer pTrack; 00500 00501 00502 if ( m_iMetaDataLife < 16 ) { 00503 m_iMetaDataLife++; 00504 return false; 00505 } 00506 00507 m_iMetaDataLife = 0; 00508 00509 00510 pTrack = PlayerInfo::Instance().getCurrentPlayingTrack(); 00511 if ( !pTrack ) 00512 return false; 00513 00514 if ( m_pMetaData ) { 00515 if ((pTrack->getId() == -1) || (m_pMetaData->getId() == -1)) { 00516 if ((pTrack->getArtist() == m_pMetaData->getArtist()) && 00517 (pTrack->getTitle() == m_pMetaData->getArtist())) { 00518 return false; 00519 } 00520 } 00521 else if (pTrack->getId() == m_pMetaData->getId()) { 00522 return false; 00523 } 00524 } 00525 00526 m_pMetaData = pTrack; 00527 return true; 00528 } 00529 00530 /* 00531 * Update shoutcast metadata. 00532 * This does not work for OGG/Vorbis and Icecast, since the actual 00533 * OGG/Vorbis stream contains the metadata. 00534 */ 00535 void EngineShoutcast::updateMetaData() 00536 { 00537 QMutexLocker locker(&m_shoutMutex); 00538 if (!m_pShout || !m_pShoutMetaData) 00539 return; 00540 00541 QByteArray baSong = ""; 00556 //If we use MP3 streaming and want dynamic metadata changes 00557 if(!m_custom_metadata && !qstrcmp(m_baFormat, "MP3")){ 00558 if (m_pMetaData != NULL) { 00559 // convert QStrings to char*s 00560 QByteArray baArtist = m_pMetaData->getArtist().toLatin1(); 00561 QByteArray baTitle = m_pMetaData->getTitle().toLatin1(); 00562 00563 if (baArtist.isEmpty()) 00564 baSong = baTitle; 00565 else 00566 baSong = baArtist + " - " + baTitle; 00567 00569 shout_metadata_add(m_pShoutMetaData, "song", baSong.data()); 00570 shout_set_metadata(m_pShout, m_pShoutMetaData); 00571 } 00572 } 00573 //Otherwise we might use static metadata 00574 else{ 00576 if(m_custom_metadata && !m_firstCall){ 00577 baSong = m_baCustom_artist + " - " + m_baCustom_title; 00579 shout_metadata_add(m_pShoutMetaData, "song", baSong.data()); 00580 shout_set_metadata(m_pShout, m_pShoutMetaData); 00581 m_firstCall = true; 00582 } 00583 } 00584 } 00585 /* -------- ------------------------------------------------------ 00586 Purpose: Common error dialog creation code for run-time exceptions 00587 Notify user when connected or disconnected and so on 00588 Input: Detailed error string 00589 Output: - 00590 -------- ------------------------------------------------------ */ 00591 void EngineShoutcast::errorDialog(QString text, QString detailedError) { 00592 qWarning() << "Shoutcast error: " << detailedError; 00593 ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties(); 00594 props->setType(DLG_WARNING); 00595 props->setTitle(tr("Live broadcasting")); 00596 props->setText(text); 00597 props->setDetails(detailedError); 00598 props->setKey(detailedError); // To prevent multiple windows for the same error 00599 props->setDefaultButton(QMessageBox::Close); 00600 00601 props->setModal(false); 00602 00603 ErrorDialogHandler::instance()->requestErrorDialog(props); 00604 } 00605 00606