/* authenticate user */ static bool BarMainLoginUser (BarApp_t *app) { PianoReturn_t pRet; WaitressReturn_t wRet; PianoRequestDataLogin_t reqData; bool ret; reqData.user = app->settings.username; reqData.password = app->settings.password; reqData.step = 0; BarUiMsg (&app->settings, MSG_INFO, "Login... "); ret = BarUiPianoCall (app, PIANO_REQUEST_LOGIN, &reqData, &pRet, &wRet); BarUiStartEventCmd (&app->settings, "userlogin", NULL, NULL, &app->player, NULL, pRet, wRet); return ret; }
/* transform station if necessary to allow changes like rename, rate, ... * @param piano handle * @param transform this station * @return 0 = error, 1 = everything went well */ static int BarTransformIfShared (BarApp_t *app, PianoStation_t *station) { PianoReturn_t pRet; CURLcode wRet; assert (station != NULL); /* shared stations must be transformed */ if (!station->isCreator) { BarUiMsg (&app->settings, MSG_INFO, "Transforming station... "); if (!BarUiPianoCall (app, PIANO_REQUEST_TRANSFORM_STATION, station, &pRet, &wRet)) { return 0; } } return 1; }
/* authenticate user */ static bool BarMainLoginUser (BarApp_t *app) { PianoReturn_t pRet; WaitressReturn_t wRet; PianoRequestDataLogin_t reqData; bool ret; #if 0 WaitressHandle_t waithSync; char *syncTime; unsigned long int syncTimeInt; /* skip sync step by fetching time from somewhere else */ WaitressInit (&waithSync); WaitressSetUrl (&waithSync, "http://ridetheclown.com/s2/synctime.php"); if (app->settings.proxy != NULL && strlen (app->settings.proxy) > 0) { WaitressSetProxy (&waithSync, app->settings.proxy); } wRet = WaitressFetchBuf (&waithSync, &syncTime); WaitressFree (&waithSync); if (wRet != WAITRESS_RET_OK) { BarUiMsg (&app->settings, MSG_ERR, "Unable to sync: %s\n", WaitressErrorToStr (wRet)); return false; } syncTimeInt = strtoul (syncTime, NULL, 0); app->ph.timeOffset = time (NULL) - syncTimeInt; free (syncTime); #endif app->ph.timeOffset = -30239998; /* woo! magic number */ reqData.user = app->settings.username; reqData.password = app->settings.password; reqData.step = 0; BarUiMsg (&app->settings, MSG_INFO, "Login... "); ret = BarUiPianoCall (app, PIANO_REQUEST_LOGIN, &reqData, &pRet, &wRet); BarUiStartEventCmd (&app->settings, "userlogin", NULL, NULL, &app->player, NULL, pRet, wRet); return ret; }
/* fetch new playlist */ static void BarMainGetPlaylist (BarApp_t *app) { PianoReturn_t pRet; CURLcode wRet; PianoRequestDataGetPlaylist_t reqData; reqData.station = app->curStation; reqData.quality = app->settings.audioQuality; BarUiMsg (&app->settings, MSG_INFO, "Receiving new playlist... "); if (!BarUiPianoCall (app, PIANO_REQUEST_GET_PLAYLIST, &reqData, &pRet, &wRet)) { app->curStation = NULL; } else { app->playlist = reqData.retPlaylist; if (app->playlist == NULL) { BarUiMsg (&app->settings, MSG_INFO, "No tracks left.\n"); app->curStation = NULL; } } BarUiStartEventCmd (&app->settings, "stationfetchplaylist", app->curStation, app->playlist, &app->player, app->ph.stations, pRet, wRet); }
/* browse genre stations and create shared station * @param app handle */ void BarStationFromGenre (BarApp_t *app) { PianoReturn_t pRet; WaitressReturn_t wRet; PianoGenreCategory_t *curCat; PianoGenre_t *curGenre; int i; /* receive genre stations list if not yet available */ if (app->ph.genreStations == NULL) { BarUiMsg (&app->settings, MSG_INFO, "Receiving genre stations... "); if (!BarUiPianoCall (app, PIANO_REQUEST_GET_GENRE_STATIONS, NULL, &pRet, &wRet)) { return; } } /* print all available categories */ curCat = app->ph.genreStations; i = 0; while (curCat != NULL) { BarUiMsg (&app->settings, MSG_LIST, "%2i) %s\n", i, curCat->name); i++; curCat = curCat->next; } do { /* select category or exit */ BarUiMsg (&app->settings, MSG_QUESTION, "Select category: "); if (BarReadlineInt (&i, &app->input) == 0) { return; } curCat = app->ph.genreStations; while (curCat != NULL && i > 0) { curCat = curCat->next; i--; } } while (curCat == NULL); /* print all available stations */ curGenre = curCat->genres; i = 0; while (curGenre != NULL) { BarUiMsg (&app->settings, MSG_LIST, "%2i) %s\n", i, curGenre->name); i++; curGenre = curGenre->next; } do { BarUiMsg (&app->settings, MSG_QUESTION, "Select genre: "); if (BarReadlineInt (&i, &app->input) == 0) { return; } curGenre = curCat->genres; while (curGenre != NULL && i > 0) { curGenre = curGenre->next; i--; } } while (curGenre == NULL); /* create station */ PianoRequestDataCreateStation_t reqData; reqData.token = curGenre->musicId; reqData.type = PIANO_MUSICTYPE_INVALID; BarUiMsg (&app->settings, MSG_INFO, "Adding shared station \"%s\"... ", curGenre->name); BarUiPianoCall (app, PIANO_REQUEST_CREATE_STATION, &reqData, &pRet, &wRet); }
/* search music: query, search request, return music id * @param app handle * @param seed suggestion station * @param seed suggestion musicid * @param prompt string * @return musicId or NULL on abort/error */ char *BarUiSelectMusicId (BarApp_t *app, PianoStation_t *station, const char *msg) { char *musicId = NULL; char lineBuf[100], selectBuf[2]; PianoSearchResult_t searchResult; PianoArtist_t *tmpArtist; PianoSong_t *tmpSong; BarUiMsg (&app->settings, MSG_QUESTION, msg); if (BarReadlineStr (lineBuf, sizeof (lineBuf), &app->input, BAR_RL_DEFAULT) > 0) { PianoReturn_t pRet; WaitressReturn_t wRet; PianoRequestDataSearch_t reqData; reqData.searchStr = lineBuf; BarUiMsg (&app->settings, MSG_INFO, "Searching... "); if (!BarUiPianoCall (app, PIANO_REQUEST_SEARCH, &reqData, &pRet, &wRet)) { return NULL; } memcpy (&searchResult, &reqData.searchResult, sizeof (searchResult)); BarUiMsg (&app->settings, MSG_NONE, "\r"); if (searchResult.songs != NULL && searchResult.artists != NULL) { /* songs and artists found */ BarUiMsg (&app->settings, MSG_QUESTION, "Is this an [a]rtist or [t]rack name? "); BarReadline (selectBuf, sizeof (selectBuf), "at", &app->input, BAR_RL_FULLRETURN, -1); if (*selectBuf == 'a') { tmpArtist = BarUiSelectArtist (app, searchResult.artists); if (tmpArtist != NULL) { musicId = strdup (tmpArtist->musicId); } } else if (*selectBuf == 't') { tmpSong = BarUiSelectSong (&app->settings, searchResult.songs, &app->input); if (tmpSong != NULL) { musicId = strdup (tmpSong->musicId); } } } else if (searchResult.songs != NULL) { /* songs found */ tmpSong = BarUiSelectSong (&app->settings, searchResult.songs, &app->input); if (tmpSong != NULL) { musicId = strdup (tmpSong->musicId); } } else if (searchResult.artists != NULL) { /* artists found */ tmpArtist = BarUiSelectArtist (app, searchResult.artists); if (tmpArtist != NULL) { musicId = strdup (tmpArtist->musicId); } } else { BarUiMsg (&app->settings, MSG_INFO, "Nothing found...\n"); } PianoDestroySearchResult (&searchResult); } return musicId; }
/* piano wrapper: prepare/execute http request and pass result back to * libpiano (updates data structures) * @param app handle * @param request type * @param request data * @param stores piano return code * @param stores waitress return code * @return 1 on success, 0 otherwise */ int BarUiPianoCall (BarApp_t * const app, PianoRequestType_t type, void *data, PianoReturn_t *pRet, WaitressReturn_t *wRet) { PianoRequest_t req; memset (&req, 0, sizeof (req)); /* repeat as long as there are http requests to do */ do { req.data = data; *pRet = PianoRequest (&app->ph, &req, type); if (*pRet != PIANO_RET_OK) { BarUiMsg (&app->settings, MSG_NONE, "Error: %s\n", PianoErrorToStr (*pRet)); PianoDestroyRequest (&req); return 0; } *wRet = BarPianoHttpRequest (&app->waith, &req); if (*wRet != WAITRESS_RET_OK) { BarUiMsg (&app->settings, MSG_NONE, "Network error: %s\n", WaitressErrorToStr (*wRet)); if (req.responseData != NULL) { free (req.responseData); } PianoDestroyRequest (&req); return 0; } *pRet = PianoResponse (&app->ph, &req); if (*pRet != PIANO_RET_CONTINUE_REQUEST) { /* checking for request type avoids infinite loops */ if (*pRet == PIANO_RET_P_INVALID_AUTH_TOKEN && type != PIANO_REQUEST_LOGIN) { /* reauthenticate */ PianoReturn_t authpRet; WaitressReturn_t authwRet; PianoRequestDataLogin_t reqData; reqData.user = app->settings.username; reqData.password = app->settings.password; reqData.step = 0; BarUiMsg (&app->settings, MSG_NONE, "Reauthentication required... "); if (!BarUiPianoCall (app, PIANO_REQUEST_LOGIN, &reqData, &authpRet, &authwRet)) { *pRet = authpRet; *wRet = authwRet; if (req.responseData != NULL) { free (req.responseData); } PianoDestroyRequest (&req); return 0; } else { /* try again */ *pRet = PIANO_RET_CONTINUE_REQUEST; BarUiMsg (&app->settings, MSG_INFO, "Trying again... "); } } else if (*pRet != PIANO_RET_OK) { BarUiMsg (&app->settings, MSG_NONE, "Error: %s\n", PianoErrorToStr (*pRet)); if (req.responseData != NULL) { free (req.responseData); } PianoDestroyRequest (&req); return 0; } else { BarUiMsg (&app->settings, MSG_NONE, "Ok.\n"); } } /* we can destroy the request at this point, even when this call needs * more than one http request. persistent data (step counter, e.g.) is * stored in req.data */ if (req.responseData != NULL) { free (req.responseData); } PianoDestroyRequest (&req); } while (*pRet == PIANO_RET_CONTINUE_REQUEST); return 1; }
int main (int argc, char **argv) { static BarApp_t app; pthread_t playerThread; /* FIXME: max path length? */ char ctlPath[1024]; FILE *ctlFd = NULL; struct timeval selectTimeout; int maxFd, selectFds[2]; fd_set readSet, readSetCopy; char buf = '\0'; /* terminal attributes _before_ we started messing around with ~ECHO */ struct termios termOrig; memset (&app, 0, sizeof (app)); /* save terminal attributes, before disabling echoing */ BarTermSave (&termOrig); BarTermSetEcho (0); BarTermSetBuffer (0); /* init some things */ ao_initialize (); PianoInit (&app.ph); WaitressInit (&app.waith); strncpy (app.waith.host, PIANO_RPC_HOST, sizeof (app.waith.host)-1); strncpy (app.waith.port, PIANO_RPC_PORT, sizeof (app.waith.port)-1); BarSettingsInit (&app.settings); BarSettingsRead (&app.settings); BarUiMsg (MSG_NONE, "Welcome to " PACKAGE "! Press %c for a list of commands.\n", app.settings.keys[BAR_KS_HELP]); /* init fds */ FD_ZERO(&readSet); selectFds[0] = fileno (stdin); FD_SET(selectFds[0], &readSet); maxFd = selectFds[0] + 1; BarGetXdgConfigDir (PACKAGE "/ctl", ctlPath, sizeof (ctlPath)); /* FIXME: why is r_+_ required? */ ctlFd = fopen (ctlPath, "r+"); if (ctlFd != NULL) { selectFds[1] = fileno (ctlFd); FD_SET(selectFds[1], &readSet); /* assuming ctlFd is always > stdin */ maxFd = selectFds[1] + 1; BarUiMsg (MSG_INFO, "Control fifo at %s opened\n", ctlPath); } if (app.settings.username == NULL) { char nameBuf[100]; BarUiMsg (MSG_QUESTION, "Username: "******"Password: "******"Login... "); if (!BarUiPianoCall (&app.ph, PIANO_REQUEST_LOGIN, &app.waith, &reqData, &pRet, &wRet)) { BarTermRestore (&termOrig); return 0; } } { PianoReturn_t pRet; WaitressReturn_t wRet; BarUiMsg (MSG_INFO, "Get stations... "); if (!BarUiPianoCall (&app.ph, PIANO_REQUEST_GET_STATIONS, &app.waith, NULL, &pRet, &wRet)) { BarTermRestore (&termOrig); return 0; } } /* try to get autostart station */ if (app.settings.autostartStation != NULL) { app.curStation = PianoFindStationById (app.ph.stations, app.settings.autostartStation); if (app.curStation == NULL) { BarUiMsg (MSG_ERR, "Error: Autostart station not found.\n"); } } /* no autostart? ask the user */ if (app.curStation == NULL) { app.curStation = BarUiSelectStation (&app.ph, "Select station: ", app.settings.sortOrder, stdin); } if (app.curStation != NULL) { BarUiPrintStation (app.curStation); } /* little hack, needed to signal: hey! we need a playlist, but don't * free anything (there is nothing to be freed yet) */ memset (&app.player, 0, sizeof (app.player)); while (!app.doQuit) { /* song finished playing, clean up things/scrobble song */ if (app.player.mode == PLAYER_FINISHED_PLAYBACK) { BarUiStartEventCmd (&app.settings, "songfinish", app.curStation, app.playlist, &app.player, PIANO_RET_OK, WAITRESS_RET_OK); /* FIXME: pthread_join blocks everything if network connection * is hung up e.g. */ void *threadRet; pthread_join (playerThread, &threadRet); /* don't continue playback if thread reports error */ if (threadRet != (void *) PLAYER_RET_OK) { app.curStation = NULL; } memset (&app.player, 0, sizeof (app.player)); } /* check whether player finished playing and start playing new * song */ if (app.player.mode >= PLAYER_FINISHED_PLAYBACK || app.player.mode == PLAYER_FREED) { if (app.curStation != NULL) { /* what's next? */ if (app.playlist != NULL) { if (app.settings.history != 0) { /* prepend song to history list */ PianoSong_t *tmpSong = app.songHistory; app.songHistory = app.playlist; /* select next song */ app.playlist = app.playlist->next; app.songHistory->next = tmpSong; /* limit history's length */ /* start with 1, so we're stopping at n-1 and have the * chance to set ->next = NULL */ unsigned int i = 1; tmpSong = app.songHistory; while (i < app.settings.history && tmpSong != NULL) { tmpSong = tmpSong->next; ++i; } /* if too many songs in history... */ if (tmpSong != NULL) { PianoSong_t *delSong = tmpSong->next; tmpSong->next = NULL; if (delSong != NULL) { PianoDestroyPlaylist (delSong); } } } else { /* don't keep history */ app.playlist = app.playlist->next; } } if (app.playlist == NULL) { PianoReturn_t pRet; WaitressReturn_t wRet; PianoRequestDataGetPlaylist_t reqData; reqData.station = app.curStation; reqData.format = app.settings.audioFormat; BarUiMsg (MSG_INFO, "Receiving new playlist... "); if (!BarUiPianoCall (&app.ph, PIANO_REQUEST_GET_PLAYLIST, &app.waith, &reqData, &pRet, &wRet)) { app.curStation = NULL; } else { app.playlist = reqData.retPlaylist; if (app.playlist == NULL) { BarUiMsg (MSG_INFO, "No tracks left.\n"); app.curStation = NULL; } } BarUiStartEventCmd (&app.settings, "stationfetchplaylist", app.curStation, app.playlist, &app.player, pRet, wRet); } /* song ready to play */ if (app.playlist != NULL) { BarUiPrintSong (app.playlist, app.curStation->isQuickMix ? PianoFindStationById (app.ph.stations, app.playlist->stationId) : NULL); if (app.playlist->audioUrl == NULL) { BarUiMsg (MSG_ERR, "Invalid song url.\n"); } else { /* setup player */ memset (&app.player, 0, sizeof (app.player)); WaitressInit (&app.player.waith); WaitressSetUrl (&app.player.waith, app.playlist->audioUrl); /* set up global proxy, player is NULLed on songfinish */ if (app.settings.proxy != NULL) { char tmpPath[2]; WaitressSplitUrl (app.settings.proxy, app.player.waith.proxyHost, sizeof (app.player.waith.proxyHost), app.player.waith.proxyPort, sizeof (app.player.waith.proxyPort), tmpPath, sizeof (tmpPath)); } app.player.gain = app.playlist->fileGain; app.player.audioFormat = app.playlist->audioFormat; /* throw event */ BarUiStartEventCmd (&app.settings, "songstart", app.curStation, app.playlist, &app.player, PIANO_RET_OK, WAITRESS_RET_OK); /* prevent race condition, mode must _not_ be FREED if * thread has been started */ app.player.mode = PLAYER_STARTING; /* start player */ pthread_create (&playerThread, NULL, BarPlayerThread, &app.player); } /* end if audioUrl == NULL */ } /* end if playlist != NULL */ } /* end if curStation != NULL */ } /* select modifies its arguments => copy the set */ memcpy (&readSetCopy, &readSet, sizeof (readSet)); selectTimeout.tv_sec = 1; selectTimeout.tv_usec = 0; /* in the meantime: wait for user actions */ if (select (maxFd, &readSetCopy, NULL, NULL, &selectTimeout) > 0) { FILE *curFd = NULL; if (FD_ISSET(selectFds[0], &readSetCopy)) { curFd = stdin; } else if (FD_ISSET(selectFds[1], &readSetCopy)) { curFd = ctlFd; } buf = fgetc (curFd); size_t i; for (i = 0; i < BAR_KS_COUNT; i++) { if (app.settings.keys[i] == buf) { static const BarKeyShortcutFunc_t idToF[] = {BarUiActHelp, BarUiActLoveSong, BarUiActBanSong, BarUiActAddMusic, BarUiActCreateStation, BarUiActDeleteStation, BarUiActExplain, BarUiActStationFromGenre, BarUiActHistory, BarUiActSongInfo, BarUiActAddSharedStation, BarUiActMoveSong, BarUiActSkipSong, BarUiActPause, BarUiActQuit, BarUiActRenameStation, BarUiActSelectStation, BarUiActTempBanSong, BarUiActPrintUpcoming, BarUiActSelectQuickMix, BarUiActDebug, BarUiActBookmark}; idToF[i] (&app, curFd); break; } } } /* show time */ if (app.player.mode >= PLAYER_SAMPLESIZE_INITIALIZED && app.player.mode < PLAYER_FINISHED_PLAYBACK) { /* Ugly: songDuration is unsigned _long_ int! Lets hope this won't * overflow */ int songRemaining = (signed long int) (app.player.songDuration - app.player.songPlayed) / BAR_PLAYER_MS_TO_S_FACTOR; char pos = 0; if (songRemaining < 0) { /* Use plus sign if song is longer than expected */ pos = 1; songRemaining = -songRemaining; } BarUiMsg (MSG_TIME, "%c%02i:%02i/%02i:%02i\r", (pos ? '+' : '-'), songRemaining / 60, songRemaining % 60, app.player.songDuration / BAR_PLAYER_MS_TO_S_FACTOR / 60, app.player.songDuration / BAR_PLAYER_MS_TO_S_FACTOR % 60); } } /* destroy everything (including the world...) */ if (app.player.mode != PLAYER_FREED) { pthread_join (playerThread, NULL); } if (ctlFd != NULL) { fclose (ctlFd); } PianoDestroy (&app.ph); PianoDestroyPlaylist (app.songHistory); PianoDestroyPlaylist (app.playlist); ao_shutdown(); BarSettingsDestroy (&app.settings); /* restore terminal attributes, zsh doesn't need this, bash does... */ BarTermRestore (&termOrig); return 0; }
/* piano wrapper: prepare/execute http request and pass result back to * libpiano */ bool BarUiPianoCall (BarApp_t * const app, const PianoRequestType_t type, void * const data, PianoReturn_t * const pRet, CURLcode * const wRet) { PianoReturn_t pRetLocal = PIANO_RET_OK; CURLcode wRetLocal = CURLE_OK; bool ret = false; /* repeat as long as there are http requests to do */ do { PianoRequest_t req = { .data = data, .responseData = NULL }; pRetLocal = PianoRequest (&app->ph, &req, type); if (pRetLocal != PIANO_RET_OK) { BarUiMsg (&app->settings, MSG_NONE, "Error: %s\n", PianoErrorToStr (pRetLocal)); goto cleanup; } wRetLocal = BarPianoHttpRequest (app->http, &app->settings, &req); if (wRetLocal == CURLE_ABORTED_BY_CALLBACK) { BarUiMsg (&app->settings, MSG_NONE, "Interrupted.\n"); goto cleanup; } else if (wRetLocal != CURLE_OK) { BarUiMsg (&app->settings, MSG_NONE, "Network error: %s\n", curl_easy_strerror (wRetLocal)); goto cleanup; } pRetLocal = PianoResponse (&app->ph, &req); if (pRetLocal != PIANO_RET_CONTINUE_REQUEST) { /* checking for request type avoids infinite loops */ if (pRetLocal == PIANO_RET_P_INVALID_AUTH_TOKEN && type != PIANO_REQUEST_LOGIN) { /* reauthenticate */ PianoRequestDataLogin_t reqData; reqData.user = app->settings.username; reqData.password = app->settings.password; reqData.step = 0; BarUiMsg (&app->settings, MSG_NONE, "Reauthentication required... "); if (!BarUiPianoCall (app, PIANO_REQUEST_LOGIN, &reqData, &pRetLocal, &wRetLocal)) { goto cleanup; } else { /* try again */ pRetLocal = PIANO_RET_CONTINUE_REQUEST; BarUiMsg (&app->settings, MSG_INFO, "Trying again... "); } } else if (pRetLocal != PIANO_RET_OK) { BarUiMsg (&app->settings, MSG_NONE, "Error: %s\n", PianoErrorToStr (pRetLocal)); goto cleanup; } else { BarUiMsg (&app->settings, MSG_NONE, "Ok.\n"); ret = true; } } cleanup: /* persistent data is stored in req.data */ free (req.responseData); PianoDestroyRequest (&req); } while (pRetLocal == PIANO_RET_CONTINUE_REQUEST); *pRet = pRetLocal; *wRet = wRetLocal; return ret; }
/* start new player thread */ static void BarMainStartPlayback (BarApp_t *app, pthread_t *playerThread) { PianoReturn_t pRet; WaitressReturn_t wRet; /* is this an advertising track? */ if (app->playlist->adToken != NULL) { PianoRequestDataGetAdMetadata_t adReqData; adReqData.token = app->playlist->adToken; adReqData.quality = app->settings.audioQuality; BarUiMsg (&app->settings, MSG_INFO, "Fetching ads with token %s... ", adReqData.token); BarUiPianoCall (app, PIANO_REQUEST_GET_AD_METADATA, &adReqData, &pRet, &wRet); /* got token? */ if (adReqData.retTokenCount > 0) { PianoRequestDataRegisterAd_t regReqData; regReqData.token = adReqData.retToken; regReqData.tokenCount = adReqData.retTokenCount; regReqData.station = app->curStation; BarUiMsg (&app->settings, MSG_INFO, "Registering ad... "); BarUiPianoCall (app, PIANO_REQUEST_REGISTER_AD, ®ReqData, &pRet, &wRet); // change the current song to the actual audio ad url app->playlist->audioUrl = adReqData.audioUrl; // you can configure to get silence instead of ads or you can choose // to hear the ads. The default is silence if (app->settings.silenceAds) { app->playlist->fileGain = -999; app->playlist->title = strdup("Audio Ad (Silenced)"); } else { app->playlist->fileGain = adReqData.fileGain; app->playlist->title = strdup("Audio Ad"); } app->playlist->audioFormat = adReqData.audioFormat; app->playlist->artist = strdup("advertiser"); app->playlist->album = strdup("pianobar"); /* delete */ for (size_t i = 0; i < adReqData.retTokenCount; i++) { free (adReqData.retToken[i]); } free (adReqData.retToken); } } BarUiPrintSong (&app->settings, app->playlist, app->curStation->isQuickMix ? PianoFindStationById (app->ph.stations, app->playlist->stationId) : NULL); if (app->playlist->audioUrl == NULL) { BarUiMsg (&app->settings, MSG_ERR, "Invalid song url.\n"); } else { /* setup player */ memset (&app->player, 0, sizeof (app->player)); WaitressInit (&app->player.waith); WaitressSetUrl (&app->player.waith, app->playlist->audioUrl); /* set up global proxy, player is NULLed on songfinish */ if (app->settings.proxy != NULL) { WaitressSetProxy (&app->player.waith, app->settings.proxy); } app->player.gain = app->playlist->fileGain; app->player.scale = BarPlayerCalcScale (app->player.gain + app->settings.volume); app->player.audioFormat = app->playlist->audioFormat; app->player.settings = &app->settings; pthread_mutex_init (&app->player.pauseMutex, NULL); pthread_cond_init (&app->player.pauseCond, NULL); /* throw event */ BarUiStartEventCmd (&app->settings, "songstart", app->curStation, app->playlist, &app->player, app->ph.stations, PIANO_RET_OK, WAITRESS_RET_OK); /* prevent race condition, mode must _not_ be FREED if * thread has been started */ app->player.mode = PLAYER_STARTING; /* start player */ pthread_create (playerThread, NULL, BarPlayerThread, &app->player); } }
/* browse genre stations and create shared station * @param piano handle */ void BarStationFromGenre (PianoHandle_t *ph, WaitressHandle_t *waith, FILE *curFd) { PianoReturn_t pRet; WaitressReturn_t wRet; PianoGenreCategory_t *curCat; PianoGenre_t *curGenre; PianoRequestDataCreateStation_t reqData; int i; /* receive genre stations list if not yet available */ if (ph->genreStations == NULL) { PianoReturn_t pRet; WaitressReturn_t wRet; BarUiMsg (MSG_INFO, "Receiving genre stations... "); if (!BarUiPianoCall (ph, PIANO_REQUEST_GET_GENRE_STATIONS, waith, NULL, &pRet, &wRet)) { return; } } /* print all available categories */ curCat = ph->genreStations; i = 0; while (curCat != NULL) { BarUiMsg (MSG_LIST, "%2i) %s\n", i, curCat->name); i++; curCat = curCat->next; } /* select category or exit */ BarUiMsg (MSG_QUESTION, "Select category: "); if (BarReadlineInt (&i, curFd) == 0) { return; } curCat = ph->genreStations; while (curCat != NULL && i > 0) { curCat = curCat->next; i--; } /* print all available stations */ curGenre = curCat->genres; i = 0; while (curGenre != NULL) { BarUiMsg (MSG_LIST, "%2i) %s\n", i, curGenre->name); i++; curGenre = curGenre->next; } BarUiMsg (MSG_QUESTION, "Select genre: "); if (BarReadlineInt (&i, curFd) == 0) { return; } curGenre = curCat->genres; while (curGenre != NULL && i > 0) { curGenre = curGenre->next; i--; } /* create station */ BarUiMsg (MSG_INFO, "Adding shared station \"%s\"... ", curGenre->name); reqData.id = curGenre->musicId; reqData.type = "mi"; BarUiPianoCall (ph, PIANO_REQUEST_CREATE_STATION, waith, &reqData, &pRet, &wRet); }
/* search music: query, search request, return music id * @param piano handle * @param read data from fd * @param allow seed suggestions if != NULL * @return musicId or NULL on abort/error */ char *BarUiSelectMusicId (PianoHandle_t *ph, WaitressHandle_t *waith, FILE *curFd, char *similarToId) { char *musicId = NULL; char lineBuf[100], selectBuf[2]; PianoSearchResult_t searchResult; PianoArtist_t *tmpArtist; PianoSong_t *tmpSong; BarUiMsg (MSG_QUESTION, "Search for artist/title: "); if (BarReadlineStr (lineBuf, sizeof (lineBuf), 0, curFd) > 0) { if (strcmp ("?", lineBuf) == 0 && similarToId != NULL) { PianoReturn_t pRet; WaitressReturn_t wRet; PianoRequestDataGetSeedSuggestions_t reqData; reqData.musicId = similarToId; reqData.max = 20; BarUiMsg (MSG_INFO, "Receiving suggestions... "); if (!BarUiPianoCall (ph, PIANO_REQUEST_GET_SEED_SUGGESTIONS, waith, &reqData, &pRet, &wRet)) { return NULL; } memcpy (&searchResult, &reqData.searchResult, sizeof (searchResult)); } else { PianoReturn_t pRet; WaitressReturn_t wRet; PianoRequestDataSearch_t reqData; reqData.searchStr = lineBuf; BarUiMsg (MSG_INFO, "Searching... "); if (!BarUiPianoCall (ph, PIANO_REQUEST_SEARCH, waith, &reqData, &pRet, &wRet)) { return NULL; } memcpy (&searchResult, &reqData.searchResult, sizeof (searchResult)); } BarUiMsg (MSG_NONE, "\r"); if (searchResult.songs != NULL && searchResult.artists != NULL) { /* songs and artists found */ BarUiMsg (MSG_QUESTION, "Is this an [a]rtist or [t]rack name? "); BarReadline (selectBuf, sizeof (selectBuf), "at", 1, 0, curFd); if (*selectBuf == 'a') { tmpArtist = BarUiSelectArtist (searchResult.artists, curFd); if (tmpArtist != NULL) { musicId = strdup (tmpArtist->musicId); } } else if (*selectBuf == 't') { tmpSong = BarUiSelectSong (searchResult.songs, curFd); if (tmpSong != NULL) { musicId = strdup (tmpSong->musicId); } } } else if (searchResult.songs != NULL) { /* songs found */ tmpSong = BarUiSelectSong (searchResult.songs, curFd); if (tmpSong != NULL) { musicId = strdup (tmpSong->musicId); } } else if (searchResult.artists != NULL) { /* artists found */ tmpArtist = BarUiSelectArtist (searchResult.artists, curFd); if (tmpArtist != NULL) { musicId = strdup (tmpArtist->musicId); } } else { BarUiMsg (MSG_INFO, "Nothing found...\n"); } PianoDestroySearchResult (&searchResult); } return musicId; }