Ejemplo n.º 1
0
/*	get initial station from autostart setting or user input
 */
static void BarMainGetInitialStation (BarApp_t *app) {
	/* try to get autostart station */
	if (app->settings.autostartStation != NULL) {
		app->curStation = PianoFindStationById (app->ph.stations,
				app->settings.autostartStation);
		if (app->curStation == NULL) {
			BarUiMsg (&app->settings, MSG_ERR,
					"Error: Autostart station not found.\n");
		}
	}
	/* no autostart? ask the user */
	if (app->curStation == NULL) {
		app->curStation = BarUiSelectStation (app, "Select station: ", NULL);
	}
	if (app->curStation != NULL) {
		BarUiPrintStation (&app->settings, app->curStation);
	}
}
Ejemplo n.º 2
0
/*	Print song infos (artist, title, album, loved)
 *	@param pianobar settings
 *	@param the song
 *	@param alternative station info (show real station for quickmix, e.g.)
 */
void BarUiPrintSong (const BarSettings_t *settings,
		const PianoSong_t *song, const PianoStation_t *station) {
	char outstr[512];
	const char *vals[] = {song->title, song->artist, song->album,
			(song->rating == PIANO_RATE_LOVE) ? settings->loveIcon : "",
			station != NULL ? settings->atIcon : "",
			station != NULL ? station->name : "",
			song->detailUrl};

	BarUiCustomFormat (outstr, sizeof (outstr), settings->npSongFormat,
			"talr@su", vals);
	BarUiAppendNewline (outstr, sizeof (outstr));
	BarUiMsg (settings, MSG_PLAYING, "%s", outstr);

	BarUiCustomFormat(outstr, sizeof(outstr), settings->titleFormat,
		"talr@su", vals);
	BarConsoleSetTitle(outstr);
}
Ejemplo n.º 3
0
/*	start new player thread
 */
static void BarMainStartPlayback (BarApp_t *app, pthread_t *playerThread) {
	assert (app != NULL);
	assert (playerThread != NULL);

	const PianoSong_t * const curSong = app->playlist;
	assert (curSong != NULL);

	BarUiPrintSong (&app->settings, curSong, app->curStation->isQuickMix ?
			PianoFindStationById (app->ph.stations,
			curSong->stationId) : NULL);

	static const char httpPrefix[] = "http://";
	/* avoid playing local files */
	if (curSong->audioUrl == NULL ||
			strncmp (curSong->audioUrl, httpPrefix, strlen (httpPrefix)) != 0) {
		BarUiMsg (&app->settings, MSG_ERR, "Invalid song url.\n");
	} else {
		/* setup player */
		memset (&app->player, 0, sizeof (app->player));

		app->player.url = curSong->audioUrl;
		app->player.gain = curSong->fileGain;
		app->player.settings = &app->settings;
		app->player.songDuration = curSong->length;
		pthread_mutex_init (&app->player.pauseMutex, NULL);
		pthread_cond_init (&app->player.pauseCond, NULL);

		assert (interrupted == &app->doQuit);
		interrupted = &app->player.interrupted;

		/* throw event */
		BarUiStartEventCmd (&app->settings, "songstart",
				app->curStation, curSong, &app->player, app->ph.stations,
				PIANO_RET_OK, CURLE_OK);

		/* prevent race condition, mode must _not_ be DEAD if
		 * thread has been started */
		app->player.mode = PLAYER_WAITING;
		/* start player */
		pthread_create (playerThread, NULL, BarPlayerThread,
				&app->player);
	}
}
Ejemplo n.º 4
0
/*	start new player thread
 */
static void BarMainStartPlayback (BarApp_t *app, pthread_t *playerThread) {
	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;
		if ( !app->mute ) {
			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);

        BarDownloadStart(app);

		/* 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);
	}
}
Ejemplo n.º 5
0
/*	setup libao
 */
static bool openDevice (player_t * const player) {
	AVCodecContext * const cctx = player->st->codec;

	ao_sample_format aoFmt;
	memset (&aoFmt, 0, sizeof (aoFmt));
	aoFmt.bits = av_get_bytes_per_sample (avformat) * 8;
	assert (aoFmt.bits > 0);
	aoFmt.channels = cctx->channels;
	aoFmt.rate = cctx->sample_rate;
	aoFmt.byte_format = AO_FMT_NATIVE;

	int driver = ao_default_driver_id ();
	if ((player->aoDev = ao_open_live (driver, &aoFmt, NULL)) == NULL) {
		BarUiMsg (player->settings, MSG_ERR, "Cannot open audio device.\n");
		return false;
	}

	return true;
}
Ejemplo n.º 6
0
static int _BarFlyTagFetchCover(uint8_t** cover_art, size_t* cover_size,
		char const* url, BarSettings_t const* settings)
{
	int exit_status = 0;
	int status;
	uint8_t* tmp_cover_art = NULL;
	size_t tmp_cover_size;

	assert(cover_art != NULL);
	assert(cover_size != NULL);
	assert(url != NULL);
	assert(settings != NULL);

	/*
	 * Fetch the cover art.
	 */
	status = _BarFlyFetchURL(url, &tmp_cover_art, &tmp_cover_size, settings);
	if (status != 0) {
		BarUiMsg(settings, MSG_ERR, "Could not get the cover art.\n");
		goto error;
	}

	*cover_art = tmp_cover_art;
	tmp_cover_art = NULL;
	*cover_size = tmp_cover_size;

	goto end;

error:
	exit_status = -1;

end:
	if (tmp_cover_art != NULL) {
		free(tmp_cover_art);
	}

	return exit_status;
}
Ejemplo n.º 7
0
/*	let user pick one song
 *	@param pianobar settings
 *	@param song list
 *	@param input fds
 *	@return pointer to selected item in song list or NULL
 */
PianoSong_t *BarUiSelectSong (const BarSettings_t *settings,
		PianoSong_t *startSong, BarReadlineFds_t *input) {
	PianoSong_t *tmpSong = NULL;
	char buf[100];

	memset (buf, 0, sizeof (buf));

	do {
		BarUiListSongs (settings, startSong, buf);

		BarUiMsg (settings, MSG_QUESTION, "Select song: ");
		if (BarReadlineStr (buf, sizeof (buf), input, BAR_RL_DEFAULT) == 0) {
			return NULL;
		}

		if (isnumeric (buf)) {
			unsigned long i = strtoul (buf, NULL, 0);
			tmpSong = PianoListGetP (startSong, i);
		}
	} while (tmpSong == NULL);

	return tmpSong;
}
Ejemplo n.º 8
0
int main (int argc, char **argv) {
	static BarApp_t app;
	/* terminal attributes _before_ we started messing around with ~ECHO */
	struct termios termOrig;
	struct sigaction action;

	memset (&app, 0, sizeof (app));
	memset (&action, 0, sizeof(action));

	/* set the signal handler for SIGINT and SIGTERM */
	action.sa_handler = BarSigQuit;
	sigaction(SIGINT, &action, NULL);
	sigaction(SIGTERM, &action, NULL);

	/* save terminal attributes, before disabling echoing */
	BarTermSave (&termOrig);
	BarTermSetEcho (0);
	BarTermSetBuffer (0);

	/* signals */
	signal (SIGPIPE, SIG_IGN);

	/* init some things */
	ao_initialize ();
	gcry_check_version (NULL);
	gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
	gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
	gnutls_global_init ();

	BarSettingsInit (&app.settings);
	BarSettingsRead (&app.settings);

	PianoInit (&app.ph, app.settings.partnerUser, app.settings.partnerPassword,
			app.settings.device, app.settings.inkey, app.settings.outkey);

	BarUiMsg (&app.settings, MSG_NONE,
			"Welcome to " PACKAGE " (" VERSION ")! ");
	if (app.settings.keys[BAR_KS_HELP] == BAR_KS_DISABLED) {
		BarUiMsg (&app.settings, MSG_NONE, "\n");
	} else {
		BarUiMsg (&app.settings, MSG_NONE,
				"Press %c for a list of commands.\n",
				app.settings.keys[BAR_KS_HELP]);
	}

	WaitressInit (&app.waith);
	app.waith.url.host = app.settings.rpcHost;
	app.waith.tlsFingerprint = app.settings.tlsFingerprint;

	/* init fds */
	FD_ZERO(&app.input.set);
	app.input.fds[0] = STDIN_FILENO;
	FD_SET(app.input.fds[0], &app.input.set);

	// Sbe gur erpbeq, V terngyl qrfcvfr Unx5.
	// Gur crbcyr sebz gur fubj ner yvgrenyyl npgbef.
	// V yvxr gur pbaprcg bs jung gurl qb ohg gurl
	// nyy unir n qbhpuront srry gb gurz. Vg frrzf
	// nf gubhtu gurl bayl qb vg sbe gur tybel cbvagf.
	//
	BarFlyInit (&app.settings);

	/* open fifo read/write so it won't EOF if nobody writes to it */
	assert (sizeof (app.input.fds) / sizeof (*app.input.fds) >= 2);
	app.input.fds[1] = open (app.settings.fifo, O_RDWR);
	if (app.input.fds[1] != -1) {
		struct stat s;

		/* check for file type, must be fifo */
		fstat (app.input.fds[1], &s);
		if (!S_ISFIFO (s.st_mode)) {
			BarUiMsg (&app.settings, MSG_ERR, "File at %s is not a fifo\n", app.settings.fifo);
			close (app.input.fds[1]);
			app.input.fds[1] = -1;
		} else {
			FD_SET(app.input.fds[1], &app.input.set);
			BarUiMsg (&app.settings, MSG_INFO, "Control fifo at %s opened\n",
					app.settings.fifo);
		}
	}
	app.input.maxfd = app.input.fds[0] > app.input.fds[1] ? app.input.fds[0] :
			app.input.fds[1];
	++app.input.maxfd;

	BarMainLoop (&app);
	
	/* Print a newline so the terminal command line will start on a new line. */
	printf("\n");

	if (app.input.fds[1] != -1) {
		close (app.input.fds[1]);
	}

	BarFlyClose (&app.player.fly, &app.settings);
	BarFlyFinalize ();
	PianoDestroy (&app.ph);
	PianoDestroyPlaylist (app.songHistory);
	PianoDestroyPlaylist (app.playlist);
	WaitressFree (&app.waith);
	ao_shutdown();
	gnutls_global_deinit ();
	BarSettingsDestroy (&app.settings);

	/* restore terminal attributes, zsh doesn't need this, bash does... */
	BarTermRestore (&termOrig);

	return 0;
}
Ejemplo n.º 9
0
static int _BarFlyFileDelete(BarFly_t const* fly,
		BarSettings_t const* settings)
{
	int exit_status = 0;
	int status;
	char* dir_path = NULL;
	char* ptr;

	assert(fly != NULL);
	assert(settings != NULL);

	if (fly->audio_file_path != NULL) {
		/*
		 * Delete the file.
		 */
		BarUiMsg(settings, MSG_DEBUG, "Deleting partially recorded file (%s).\n",
				fly->audio_file_path);
		status = unlink(fly->audio_file_path);
		if (status != 0) {
			BarUiMsg(settings, MSG_ERR, "Failed to delete the partially "
					"recorded file (%s).\n", fly->audio_file_path);
			goto error;
		}

		/*
		 * Delete any empty parent directories.
		 */
		dir_path = strdup(fly->audio_file_path);
		if (dir_path == NULL) {
			BarUiMsg(settings, MSG_ERR,
					"Error copying the file path (%s) (%d:%s).\n",
					fly->audio_file_path, errno, strerror(errno));
			goto error;
		}

		ptr = strrchr(dir_path, '/');
		while (ptr != NULL) {
			*ptr = '\0';

			status = rmdir(dir_path);
			if ((status != 0) && (errno != ENOTEMPTY) && (errno != EEXIST)) {
				BarUiMsg(settings, MSG_ERR,
						"Failed to delete the empty artist directory "
						"(%s) (%d:%s).\n", dir_path, errno, strerror);
				goto error;

			}

			ptr = strrchr(dir_path, '/');
		}
	}

	goto end;

error:
	exit_status = -1;

end:
	if (dir_path != NULL) {
		free(dir_path);
	}

	return exit_status;
}
Ejemplo n.º 10
0
/*	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);
}
Ejemplo n.º 11
0
/*	let user pick one station
 *	@param app handle
 *	@param stations that should be listed
 *	@param prompt string
 *	@param called if input was not a number
 *	@param auto-select if only one station remains after filtering
 *	@return pointer to selected station or NULL
 */
PianoStation_t *BarUiSelectStation (BarApp_t *app, PianoStation_t *stations,
		const char *prompt, BarUiSelectStationCallback_t callback,
		bool autoselect) {
	PianoStation_t **sortedStations = NULL, *retStation = NULL;
	size_t stationCount, i, lastDisplayed, displayCount;
	char buf[100];

	if (stations == NULL) {
		BarUiMsg (&app->settings, MSG_ERR, "No station available.\n");
		return NULL;
	}

	memset (buf, 0, sizeof (buf));

	/* sort and print stations */
	sortedStations = BarSortedStations (stations, &stationCount,
			app->settings.sortOrder);

	do {
		displayCount = 0;
		for (i = 0; i < stationCount; i++) {
			const PianoStation_t *currStation = sortedStations[i];
			/* filter stations */
			if (BarStrCaseStr (currStation->name, buf) != NULL) {
				BarUiMsg (&app->settings, MSG_LIST, "%2i) %c%c%c %s\n", i,
						currStation->useQuickMix ? 'q' : ' ',
						currStation->isQuickMix ? 'Q' : ' ',
						!currStation->isCreator ? 'S' : ' ',
						currStation->name);
				++displayCount;
				lastDisplayed = i;
			}
		}

		BarUiMsg (&app->settings, MSG_QUESTION, prompt);
		if (autoselect && displayCount == 1 && stationCount != 1) {
			/* auto-select last remaining station */
			BarUiMsg (&app->settings, MSG_NONE, "%i\n", lastDisplayed);
			retStation = sortedStations[lastDisplayed];
		} else {
			if (BarReadlineStr (buf, sizeof (buf), &app->input,
					BAR_RL_DEFAULT) == 0) {
				break;
			}

			if (isnumeric (buf)) {
				unsigned long selected = strtoul (buf, NULL, 0);
				if (selected < stationCount) {
					retStation = sortedStations[selected];
				}
			}

			/* hand over buffer to external function if it was not a station number */
			if (retStation == NULL && callback != NULL) {
				callback (app, buf);
			}
		}
	} while (retStation == NULL);

	free (sortedStations);
	return retStation;
}
Ejemplo n.º 12
0
/*	ask for username/password if none were provided in settings
 */
static bool BarMainGetLoginCredentials (BarSettings_t *settings,
		BarReadlineFds_t *input) {
	bool usernameFromConfig = true;

	if (settings->username == NULL) {
		char nameBuf[100];

		BarUiMsg (settings, MSG_QUESTION, "No User name in config? Email: ");
		BarReadlineStr (nameBuf, sizeof (nameBuf), input, BAR_RL_DEFAULT);
		settings->username = strdup (nameBuf);
		usernameFromConfig = false;
	}

	if (settings->password == NULL) {
		char passBuf[100];

		if (usernameFromConfig) {
			BarUiMsg (settings, MSG_QUESTION, "Got username Email: %s\n", settings->username);
		}

		if (settings->passwordCmd == NULL) {
			BarUiMsg (settings, MSG_QUESTION, "Password: "******"");
			settings->password = strdup (passBuf);
		} else {
			pid_t chld;
			int pipeFd[2];

			BarUiMsg (settings, MSG_INFO, "Requesting password from external helper... ");

			if (pipe (pipeFd) == -1) {
				BarUiMsg (settings, MSG_NONE, "Error: %s\n", strerror (errno));
				return false;
			}

			chld = fork ();
			if (chld == 0) {
				/* child */
				close (pipeFd[0]);
				dup2 (pipeFd[1], fileno (stdout));
				execl ("/bin/sh", "/bin/sh", "-c", settings->passwordCmd, (char *) NULL);
				BarUiMsg (settings, MSG_NONE, "Error: %s\n", strerror (errno));
				close (pipeFd[1]);
				exit (1);
			} else if (chld == -1) {
				BarUiMsg (settings, MSG_NONE, "Error: %s\n", strerror (errno));
				return false;
			} else {
				/* parent */
				int status;

				close (pipeFd[1]);
				memset (passBuf, 0, sizeof (passBuf));
				read (pipeFd[0], passBuf, sizeof (passBuf)-1);
				close (pipeFd[0]);

				/* drop trailing newlines */
				ssize_t len = strlen (passBuf)-1;
				while (len >= 0 && passBuf[len] == '\n') {
					passBuf[len] = '\0';
					--len;
				}

				waitpid (chld, &status, 0);
				if (WEXITSTATUS (status) == 0) {
					settings->password = strdup (passBuf);
					BarUiMsg (settings, MSG_NONE, "Ok.\n");
				} else {
					BarUiMsg (settings, MSG_NONE, "Error: Exit status %i.\n", WEXITSTATUS (status));
					return false;
				}
			}
		} /* end else passwordCmd */
	}

	return true;
}
Ejemplo n.º 13
0
int main (int argc, char **argv) {
	static BarApp_t app;
	/* 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);

	/* signals */
	signal (SIGPIPE, SIG_IGN);

	/* init some things */
	ao_initialize ();
	gcry_check_version (NULL);
	gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
	gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
	gnutls_global_init ();

	BarSettingsInit (&app.settings);
	BarSettingsRead (&app.settings);

	PianoInit (&app.ph, app.settings.partnerUser, app.settings.partnerPassword,
			app.settings.device, app.settings.inkey, app.settings.outkey);

	BarUiMsg (&app.settings, MSG_NONE,
			"Welcome to " PACKAGE " (" VERSION ")! ");
	if (app.settings.keys[BAR_KS_HELP] == BAR_KS_DISABLED) {
		BarUiMsg (&app.settings, MSG_NONE, "\n");
	} else {
		BarUiMsg (&app.settings, MSG_NONE,
				"Press %c for a list of commands.\n",
				app.settings.keys[BAR_KS_HELP]);
	}

	WaitressInit (&app.waith);
	app.waith.url.host = app.settings.rpcHost;
	app.waith.tlsFingerprint = app.settings.tlsFingerprint;

	/* init fds */
	FD_ZERO(&app.input.set);
	app.input.fds[0] = STDIN_FILENO;
	FD_SET(app.input.fds[0], &app.input.set);

	/* open fifo read/write so it won't EOF if nobody writes to it */
	assert (sizeof (app.input.fds) / sizeof (*app.input.fds) >= 2);
	app.input.fds[1] = open (app.settings.fifo, O_RDWR);
	if (app.input.fds[1] != -1) {
		struct stat s;

		/* check for file type, must be fifo */
		fstat (app.input.fds[1], &s);
		if (!S_ISFIFO (s.st_mode)) {
			BarUiMsg (&app.settings, MSG_ERR, "File at %s is not a fifo\n", app.settings.fifo);
			close (app.input.fds[1]);
			app.input.fds[1] = -1;
		} else {
			FD_SET(app.input.fds[1], &app.input.set);
			BarUiMsg (&app.settings, MSG_INFO, "Control fifo at %s opened\n",
					app.settings.fifo);
		}
	}
	app.input.maxfd = app.input.fds[0] > app.input.fds[1] ? app.input.fds[0] :
			app.input.fds[1];
	++app.input.maxfd;

	BarMainLoop (&app);

	if (app.input.fds[1] != -1) {
		close (app.input.fds[1]);
	}

	PianoDestroy (&app.ph);
	PianoDestroyPlaylist (app.songHistory);
	PianoDestroyPlaylist (app.playlist);
	WaitressFree (&app.waith);
	ao_shutdown();
	gnutls_global_deinit ();
	BarSettingsDestroy (&app.settings);

	/* restore terminal attributes, zsh doesn't need this, bash does... */
	BarTermRestore (&termOrig);

	return 0;
}
Ejemplo n.º 14
0
/*	mp3 playback callback
 */
static WaitressCbReturn_t BarPlayerMp3Cb (void *ptr, size_t size,
		void *stream) {
	const char *data = ptr;
	struct audioPlayer *player = stream;
	size_t i;

	QUIT_PAUSE_CHECK;

	if (!BarPlayerBufferFill (player, data, size)) {
		return WAITRESS_CB_RET_ERR;
	}

	/* some "prebuffering" */
	if (player->mode < PLAYER_RECV_DATA &&
			player->bufferFilled < BAR_PLAYER_BUFSIZE / 2) {
		return WAITRESS_CB_RET_OK;
	}

	mad_stream_buffer (&player->mp3Stream, player->buffer,
			player->bufferFilled);
	player->mp3Stream.error = 0;
	do {
		/* channels * max samples, found in mad.h */
		signed short int madDecoded[2*1152], *madPtr = madDecoded;

		if (mad_frame_decode (&player->mp3Frame, &player->mp3Stream) != 0) {
			if (player->mp3Stream.error != MAD_ERROR_BUFLEN) {
				BarUiMsg (player->settings, MSG_ERR,
						"mp3 decoding error: %s\n",
						mad_stream_errorstr (&player->mp3Stream));
				return WAITRESS_CB_RET_ERR;
			} else {
				/* rebuffering required => exit loop */
				break;
			}
		}
		mad_synth_frame (&player->mp3Synth, &player->mp3Frame);
		for (i = 0; i < player->mp3Synth.pcm.length; i++) {
			/* left channel */
			*(madPtr++) = applyReplayGain (BarPlayerMadToShort (
					player->mp3Synth.pcm.samples[0][i]), player->scale);

			/* right channel */
			*(madPtr++) = applyReplayGain (BarPlayerMadToShort (
					player->mp3Synth.pcm.samples[1][i]), player->scale);
		}
		if (player->mode < PLAYER_AUDIO_INITIALIZED) {
			ao_sample_format format;
			int audioOutDriver;

			player->channels = player->mp3Synth.pcm.channels;
			player->samplerate = player->mp3Synth.pcm.samplerate;
			audioOutDriver = ao_default_driver_id();
			memset (&format, 0, sizeof (format));
			format.bits = 16;
			format.channels = player->channels;
			format.rate = player->samplerate;
			format.byte_format = AO_FMT_NATIVE;
			if ((player->audioOutDevice = ao_open_live (audioOutDriver,
					&format, NULL)) == NULL) {
				player->aoError = 1;
				BarUiMsg (player->settings, MSG_ERR,
						"Cannot open audio device\n");
				return WAITRESS_CB_RET_ERR;
			}

			/* calc song length using the framerate of the first decoded frame */
			player->songDuration = (unsigned long long int) player->waith.request.contentLength /
					((unsigned long long int) player->mp3Frame.header.bitrate /
					(unsigned long long int) BAR_PLAYER_MS_TO_S_FACTOR / 8LL);

			/* must be > PLAYER_SAMPLESIZE_INITIALIZED, otherwise time won't
			 * be visible to user (ugly, but mp3 decoding != aac decoding) */
			player->mode = PLAYER_RECV_DATA;
		}
		/* samples * length * channels */
		ao_play (player->audioOutDevice, (char *) madDecoded,
				player->mp3Synth.pcm.length * 2 * 2);

		/* avoid division by 0 */
		if (player->mode == PLAYER_RECV_DATA) {
			/* same calculation as in aac player; don't need to divide by
			 * channels, length is number of samples for _one_ channel */
			player->songPlayed += (unsigned long long int) player->mp3Synth.pcm.length *
					(unsigned long long int) BAR_PLAYER_MS_TO_S_FACTOR /
					(unsigned long long int) player->samplerate;
		}

		QUIT_PAUSE_CHECK;
	} while (player->mp3Stream.error != MAD_ERROR_BUFLEN);

	player->bufferRead += player->mp3Stream.next_frame - player->buffer;

	BarPlayerBufferMove (player);

	return WAITRESS_CB_RET_OK;
}
Ejemplo n.º 15
0
static int _BarFlyParseTrackDisc(char const* title, char const* album_xml,
		short unsigned* track_num, short unsigned* disc_num,
		BarSettings_t const* settings)
{
	size_t const MATCH_COUNT = 3;
	size_t const ERROR_MSG_SIZE = 100;

	int exit_status = 0;
	int status;
	char* regex_string = NULL;
	regex_t regex_track;
	regmatch_t match[MATCH_COUNT];
	char error_msg[ERROR_MSG_SIZE];
	char regex_title[BAR_FLY_NAME_LENGTH];
	int index;
	int index2;
	short unsigned track;
	short unsigned disc;

	assert(title != NULL);
	assert(album_xml != NULL);
	assert(track_num != NULL);
	assert(disc_num != NULL);
	assert(settings != NULL);

	memset(&regex_track, 0, sizeof(regex_track));

	/*
	 * Create the regular expression string.
	 * 
	 * The title needs to have all potential regular expresion metacharacters
	 * fixed.  The real correct way to do this would be to escape all of them
	 * with '\'.  But the easy way to it is simply replace them with '.'.
	 *
	 * Additionally Pandora excludes some characters from the titles in the
	 * xml page.  These characters are ignored.
	 */
	index2 = 0;
	for (index = 0; title[index] != '\0'; index++) {
		if (title[index] == '^' ||
			title[index] == '$' ||
			title[index] == '(' ||
			title[index] == ')' ||
			title[index] == '>' ||
			title[index] == '<' ||
			title[index] == '[' ||
			title[index] == '{' ||
			title[index] == '\\' ||
			title[index] == '|' ||
			title[index] == '.' ||
			title[index] == '*' ||
			title[index] == '+' ||
			title[index] == '&') {
			regex_title[index2] = '.';
			index2++;
		} else if (title[index] == '?' ) {
			/*
			 * Skip these characters.
			 */
		} else {
			regex_title[index2] = title[index];
			index2++;
		}
	}
	regex_title[index2] = '\0';

	status = BarFlyasprintf(&regex_string,
			"songTitle *= *\"%s\"[^>]+"
			"discNum *= *\"([0-9]+)\"[^>]+"
			"trackNum *= *\"([0-9]+)\"", regex_title);
	if (status == -1) {
		BarUiMsg(settings, MSG_ERR, "Failed to create the regex string to get "
				"the track and disc numbers (%d:%s).\n", errno,
				strerror(errno));
		goto error;
	}

	/*
	 * Compile and execute the regular expression to get the track and disc
	 * numbers.
	 */
	status = regcomp(&regex_track, regex_string, REG_EXTENDED);
	if (status != 0) {
		regerror(status, &regex_track, error_msg, ERROR_MSG_SIZE);
		BarUiMsg(settings, MSG_ERR, "Failed to compile the regex to get the "
				"track and disc numbers (%d:%s).\n", status, error_msg);
		goto error;
	}

	memset(match, 0, sizeof(match));
	status = regexec(&regex_track, album_xml, MATCH_COUNT, match, 0);
	if (status != 0) {
		regerror(status, &regex_track, error_msg, ERROR_MSG_SIZE);
		BarUiMsg(settings, MSG_DEBUG, "The track and disc numbers were not "
				"included in the album explorer page (%d:%s).\n", status,
				error_msg);
		goto error;
	}

	/*
	 * Copy the track number.
	 */
	status = sscanf(album_xml + match[2].rm_so, "%hu", &track);
	if (status != 1) {
		BarUiMsg(settings, MSG_ERR, "Failed to copy the track number "
				"(%d:%s).\n", errno, strerror(errno));
		goto error;
	}

	/*
	 * Copy the disc number.
	 */
	status = sscanf(album_xml + match[1].rm_so, "%hu", &disc);
	if (status != 1) {
		BarUiMsg(settings, MSG_ERR, "Failed to copy the disc number (%d:%s).\n",
				errno, strerror(errno));
		goto error;
	}

	*track_num = track;
	*disc_num = disc;

	goto end;

error:
	exit_status = -1;

end:
	regfree(&regex_track);

	if (regex_string != NULL) {
		free(regex_string);
	}

	return exit_status;
}
Ejemplo n.º 16
0
static char* _BarFlyParseCoverArtURL(char const* html,
		BarSettings_t const* settings)
{
	size_t const MATCH_COUNT = 2;
	size_t const ERROR_MSG_SIZE = 100;

	char* url = NULL;
	int status;
	regex_t regex_cover;
	regmatch_t cover_match[MATCH_COUNT];
	char error_msg[ERROR_MSG_SIZE];

	assert(html != NULL);

	/*
	 * Search the html page to find the cover art URL.
	 */
	memset(&regex_cover, 0, sizeof(regex_cover));
	status = regcomp(&regex_cover,
			"src *= *\"([^\"]+)\"[^>]*class *= *\"img_cvr\"", REG_EXTENDED);
	if (status != 0) {
		regerror(status, &regex_cover, error_msg, ERROR_MSG_SIZE);
		BarUiMsg(settings, MSG_ERR, "Failed to compile the cover at regex "
				"(%d:%s).\n", status, error_msg);
		goto error;
	}

	memset(cover_match, 0, sizeof(cover_match));
	status = regexec(&regex_cover, html, MATCH_COUNT, cover_match, 0);
	if (status != 0) {
		regerror(status, &regex_cover, error_msg, ERROR_MSG_SIZE);
		BarUiMsg(settings, MSG_DEBUG, "The cover art was not included in the "
				"album detail page (%d:%s).\n", status, error_msg);
		goto error;
	}

	/*
	 * Extract the cover art URL.
	 */
	url = strndup(html + cover_match[1].rm_so,
			cover_match[1].rm_eo - cover_match[1].rm_so);
	if (url == NULL) {
		BarUiMsg(settings, MSG_ERR, "Failed to copy the cover art url "
				"(%d:%s).\n", errno, strerror(errno));
		goto error;
	}

	/*
	 * Make sure this isn't the no_album_art.jpg image.  This check must be
	 * done to only the URL itself and not the whole HTML page since the similar
	 * albums list could also use this image.
	 */
	if (strstr(url, "no_album_art.jpg") != NULL) {
		BarUiMsg(settings, MSG_DEBUG, "This album does not have cover art.\n");
		goto error;
	}

	goto end;

error:
	if (url != NULL) {
		free(url);
		url = NULL;
	}

end:
	regfree(&regex_cover);

	return url;
}
Ejemplo n.º 17
0
static int _BarFlyFileOpen(FILE** file, char const* path,
		BarSettings_t const* settings)
{
	FILE *tmp_file = NULL;
	int exit_status = 0;
	int status;
	char* dir_path = NULL;
	char* ptr;

	assert(file != NULL);
	assert(path != NULL);
	assert(settings != NULL);

	/*
	 * Create any parent directories.
	 */
	ptr = strchr(path, '/');
	while (ptr != NULL) {
		status = BarFlyasprintf(&dir_path, "%.*s", (int)(ptr - path), path);
		if (status == -1) {
			BarUiMsg(settings, MSG_ERR, "Error copying the directory path of "
					"the audio file (%d:%s).\n", errno, strerror(errno));
			exit_status = -1;
			goto error;
		}

		status = mkdir(dir_path, 0755);
		if ((status == -1) && (errno != EEXIST)) {
			BarUiMsg(settings, MSG_ERR, "Error creating a parent directory of "
					"the audio file (%s) (%d:%s).\n", errno, strerror(errno));
			exit_status = -1;
			goto error;
		}

		free(dir_path);
		dir_path = NULL;
		ptr = strchr(ptr + 1, '/');
	}

	/*
	 * Open the audio file for writing.
	 */
	tmp_file = _BarFlyFileOpenStream(path, "wb");
	if ((tmp_file == NULL) && (errno == EEXIST)) {
		BarUiMsg(settings, MSG_DEBUG, "The audio file already exists. It will "
				"not be recorded (%s).\n", path);
		exit_status = -2;
		goto error;
	} else if (tmp_file == NULL) {
		BarUiMsg(settings, MSG_ERR, "Error opening the audio file for reading "
				"(%s) (%d:%s).\n", path, errno, strerror(errno));
		exit_status = -1;
		goto error;
	}

	*file = tmp_file;
	tmp_file = NULL;

	goto end;

error:
end:
	if (dir_path != NULL) {
		free(dir_path);
	}

	if (tmp_file != NULL) {
		fclose(tmp_file);
	}

	return exit_status;
}
Ejemplo n.º 18
0
static char* _BarFlyFileGetPath(char const* artist, char const* album,
		char const* title, short unsigned year, short unsigned track,
		short unsigned disc, PianoAudioFormat_t audio_format,
		BarSettings_t const* settings)
{
	char* path = NULL;
	size_t path_length;
	char path_artist[BAR_FLY_NAME_LENGTH];
	char path_album[BAR_FLY_NAME_LENGTH];
	char path_title[BAR_FLY_NAME_LENGTH];
	char const* extension;
	char const* file_pattern_ptr;
	size_t count;
	char* path_ptr;

	assert(artist != NULL);
	assert(album != NULL);
	assert(title != NULL);
	assert(settings != NULL);

	/*
	 * Get the artist, album, and title.  Translate each of the characters
	 * we don't want to _.
	 */
	_BarFlyNameTranslate(path_artist, artist, BAR_FLY_NAME_LENGTH, settings);
	_BarFlyNameTranslate(path_album, album, BAR_FLY_NAME_LENGTH, settings);
	_BarFlyNameTranslate(path_title, title, BAR_FLY_NAME_LENGTH, settings);

	/*
	 * Get the extension.
	 */
	switch (audio_format) {
		#ifdef ENABLE_FAAD
		case PIANO_AF_AACPLUS:
			extension = ".m4a";
			break;
		#endif

		#ifdef ENABLE_MAD
		case PIANO_AF_MP3:
		case PIANO_AF_MP3_HI:
			extension = ".mp3";
			break;
		#endif

		default:
			BarUiMsg(settings, MSG_ERR, "Unsupported audio format!\n");
			goto error;
			break;
	}

	/*
	 * Calculate the length of the path.
	 */
	path_length = 0;
	file_pattern_ptr = settings->audioFileName;
	while (*file_pattern_ptr != '\0') {
		/*
		 * Get the length of everything up to the next '%'.
		 */
		count = strcspn(file_pattern_ptr, "%");
		path_length += count;
		file_pattern_ptr += count;

		/*
		 * Get the length of each substitution.  The track is always at least
		 * 2 digits.
		 */
		if (*file_pattern_ptr != '\0') {
			if (strncmp("%artist", file_pattern_ptr, 7) == 0) {
				path_length += strlen(path_artist);
				file_pattern_ptr += 7;
			} else if (strncmp("%album", file_pattern_ptr, 6) == 0) {
				path_length += strlen(path_album);
				file_pattern_ptr += 6;
			} else if (strncmp("%title", file_pattern_ptr, 6) == 0) {
				path_length += strlen(path_title);
				file_pattern_ptr += 6;
			} else if (strncmp("%year", file_pattern_ptr, 5) == 0) {
				path_length += snprintf(NULL, 0, "%hu", year);
				file_pattern_ptr += 5;
			} else if (strncmp("%track", file_pattern_ptr, 6) == 0) {
				path_length += snprintf(NULL, 0, "%02hu", track);
				file_pattern_ptr += 6;
			} else if (strncmp("%disc", file_pattern_ptr, 5) == 0) {
				path_length += snprintf(NULL, 0, "%hu", disc);
				file_pattern_ptr += 5;
			} else {
				file_pattern_ptr += 1;
			}
		}
	}
	path_length += strlen(extension);

	/*
	 * Allocate space for the path.
	 */
	path = malloc(path_length + 1);
	if (path == NULL) {
		BarUiMsg(settings, MSG_ERR,
				"Error allocating memory (%d bytes) (%d:%s).\n",
				path_length + 1, errno, strerror(errno));
		goto error;
	}

	/*
	 * Populate the buffer with the path.
	 */
	file_pattern_ptr = settings->audioFileName;
	path_ptr = path;
	while (*file_pattern_ptr != '\0') {
		/*
		 * Copy any any characters before the next substitution.
		 */
		count = strcspn(file_pattern_ptr, "%");
		strncpy(path_ptr, file_pattern_ptr, count);
		file_pattern_ptr += count;
		path_ptr += count;

		/*
		 * Substitute in the value.
		 */
		if (*file_pattern_ptr != '\0') {
			if (strncmp("%artist", file_pattern_ptr, 7) == 0) {
				strcpy(path_ptr, path_artist);
				file_pattern_ptr += 7;
				path_ptr += strlen(path_artist);
			} else if (strncmp("%album", file_pattern_ptr, 6) == 0) {
				strcpy(path_ptr, path_album);
				file_pattern_ptr += 6;
				path_ptr += strlen(path_album);
			} else if (strncmp("%title", file_pattern_ptr, 6) == 0) {
				strcpy(path_ptr, path_title);
				file_pattern_ptr += 6;
				path_ptr += strlen(path_title);
			} else if (strncmp("%year", file_pattern_ptr, 5) == 0) {
				sprintf(path_ptr, "%hu", year);
				file_pattern_ptr += 5;
				path_ptr += snprintf(NULL, 0, "%hu", year);
			} else if (strncmp("%track", file_pattern_ptr, 6) == 0) {
				sprintf(path_ptr, "%02hu", track);
				file_pattern_ptr += 6;
				path_ptr += snprintf(NULL, 0, "%02hu", track);
			} else if (strncmp("%disc", file_pattern_ptr, 5) == 0) {
				sprintf(path_ptr, "%hu", disc);
				file_pattern_ptr += 5;
				path_ptr += snprintf(NULL, 0, "%hu", disc);
			} else {
				file_pattern_ptr += 1;
			}
		}
	}
	strcpy(path_ptr, extension);

	goto end;

error:
	if (path != NULL) {
		free(path);
		path = NULL;
	}

end:
	return path;
}
Ejemplo n.º 19
0
/*	play aac stream
 *	@param streamed data
 *	@param received bytes
 *	@param extra data (player data)
 *	@return received bytes or less on error
 */
static WaitressCbReturn_t BarPlayerAACCb (void *ptr, size_t size,
		void *stream) {
	const char *data = ptr;
	struct audioPlayer *player = stream;

	QUIT_PAUSE_CHECK;

	if (!BarPlayerBufferFill (player, data, size)) {
		return WAITRESS_CB_RET_ERR;
	}

	if (player->mode == PLAYER_RECV_DATA) {
		short int *aacDecoded;
		NeAACDecFrameInfo frameInfo;
		size_t i;

		while ((player->bufferFilled - player->bufferRead) >
				player->sampleSize[player->sampleSizeCurr]) {
			/* decode frame */
			aacDecoded = NeAACDecDecode(player->aacHandle, &frameInfo,
					player->buffer + player->bufferRead,
					player->sampleSize[player->sampleSizeCurr]);
			if (frameInfo.error != 0) {
				BarUiMsg (player->settings, MSG_ERR, "Decoding error: %s\n",
						NeAACDecGetErrorMessage (frameInfo.error));
				break;
			}
			for (i = 0; i < frameInfo.samples; i++) {
				aacDecoded[i] = applyReplayGain (aacDecoded[i], player->scale);
			}
			/* ao_play needs bytes: 1 sample = 16 bits = 2 bytes */
			ao_play (player->audioOutDevice, (char *) aacDecoded,
					frameInfo.samples * 2);
			/* add played frame length to played time, explained below */
			player->songPlayed += (unsigned long long int) frameInfo.samples *
					(unsigned long long int) BAR_PLAYER_MS_TO_S_FACTOR /
					(unsigned long long int) player->samplerate /
					(unsigned long long int) player->channels;
			player->bufferRead += frameInfo.bytesconsumed;
			player->sampleSizeCurr++;
			/* going through this loop can take up to a few seconds =>
			 * allow earlier thread abort */
			QUIT_PAUSE_CHECK;
		}
	} else {
		if (player->mode == PLAYER_INITIALIZED) {
			while (player->bufferRead+4 < player->bufferFilled) {
				if (memcmp (player->buffer + player->bufferRead, "esds",
						4) == 0) {
					player->mode = PLAYER_FOUND_ESDS;
					player->bufferRead += 4;
					break;
				}
				player->bufferRead++;
			}
		}
		if (player->mode == PLAYER_FOUND_ESDS) {
			/* FIXME: is this the correct way? */
			/* we're gonna read 10 bytes */
			while (player->bufferRead+1+4+5 < player->bufferFilled) {
				if (memcmp (player->buffer + player->bufferRead,
						"\x05\x80\x80\x80", 4) == 0) {
					ao_sample_format format;
					int audioOutDriver;

					/* +1+4 needs to be replaced by <something>! */
					player->bufferRead += 1+4;
					char err = NeAACDecInit2 (player->aacHandle, player->buffer +
							player->bufferRead, 5, &player->samplerate,
							&player->channels);
					player->bufferRead += 5;
					if (err != 0) {
						BarUiMsg (player->settings, MSG_ERR,
								"Error while initializing audio decoder "
								"(%i)\n", err);
						return WAITRESS_CB_RET_ERR;
					}
					audioOutDriver = ao_default_driver_id();
					memset (&format, 0, sizeof (format));
					format.bits = 16;
					format.channels = player->channels;
					format.rate = player->samplerate;
					format.byte_format = AO_FMT_NATIVE;
					if ((player->audioOutDevice = ao_open_live (audioOutDriver,
							&format, NULL)) == NULL) {
						/* we're not interested in the errno */
						player->aoError = 1;
						BarUiMsg (player->settings, MSG_ERR,
								"Cannot open audio device\n");
						return WAITRESS_CB_RET_ERR;
					}
					player->mode = PLAYER_AUDIO_INITIALIZED;
					break;
				}
				player->bufferRead++;
			}
		}
		if (player->mode == PLAYER_AUDIO_INITIALIZED) {
			while (player->bufferRead+4+8 < player->bufferFilled) {
				if (memcmp (player->buffer + player->bufferRead, "stsz",
						4) == 0) {
					player->mode = PLAYER_FOUND_STSZ;
					player->bufferRead += 4;
					/* skip version and unknown */
					player->bufferRead += 8;
					break;
				}
				player->bufferRead++;
			}
		}
		/* get frame sizes */
		if (player->mode == PLAYER_FOUND_STSZ) {
			while (player->bufferRead+4 < player->bufferFilled) {
				/* how many frames do we have? */
				if (player->sampleSizeN == 0) {
					/* mp4 uses big endian, convert */
					player->sampleSizeN =
							bigToHostEndian32 (*((uint32_t *) (player->buffer +
							player->bufferRead)));
					player->sampleSize = malloc (player->sampleSizeN *
							sizeof (*player->sampleSize));
					player->bufferRead += 4;
					player->sampleSizeCurr = 0;
					/* set up song duration (assuming one frame always contains
					 * the same number of samples)
					 * calculation: channels * number of frames * samples per
					 * frame / samplerate */
					/* FIXME: Hard-coded number of samples per frame */
					player->songDuration = (unsigned long long int) player->sampleSizeN *
							4096LL * (unsigned long long int) BAR_PLAYER_MS_TO_S_FACTOR /
							(unsigned long long int) player->samplerate /
							(unsigned long long int) player->channels;
					break;
				} else {
					player->sampleSize[player->sampleSizeCurr] =
							bigToHostEndian32 (*((uint32_t *) (player->buffer +
							player->bufferRead)));
					player->sampleSizeCurr++;
					player->bufferRead += 4;
				}
				/* all sizes read, nearly ready for data mode */
				if (player->sampleSizeCurr >= player->sampleSizeN) {
					player->mode = PLAYER_SAMPLESIZE_INITIALIZED;
					break;
				}
			}
		}
		/* search for data atom and let the show begin... */
		if (player->mode == PLAYER_SAMPLESIZE_INITIALIZED) {
			while (player->bufferRead+4 < player->bufferFilled) {
				if (memcmp (player->buffer + player->bufferRead, "mdat",
						4) == 0) {
					player->mode = PLAYER_RECV_DATA;
					player->sampleSizeCurr = 0;
					player->bufferRead += 4;
					break;
				}
				player->bufferRead++;
			}
		}
	}

	BarPlayerBufferMove (player);

	return WAITRESS_CB_RET_OK;
}
Ejemplo n.º 20
0
static void printError (const BarSettings_t * const settings,
		const char * const msg, int ret) {
	char avmsg[128];
	av_strerror (ret, avmsg, sizeof (avmsg));
	BarUiMsg (settings, MSG_ERR, "%s (%s)\n", msg, avmsg);
}
Ejemplo n.º 21
0
/*	player thread; for every song a new thread is started
 *	@param audioPlayer structure
 *	@return PLAYER_RET_*
 */
void *BarPlayerThread (void *data) {
	struct audioPlayer *player = data;
	char extraHeaders[32];
	void *ret = PLAYER_RET_OK;
	#ifdef ENABLE_FAAD
	NeAACDecConfigurationPtr conf;
	#endif
	WaitressReturn_t wRet = WAITRESS_RET_ERR;

	/* init handles */
	player->waith.data = (void *) player;
	/* extraHeaders will be initialized later */
	player->waith.extraHeaders = extraHeaders;
	player->buffer = malloc (BAR_PLAYER_BUFSIZE);

	switch (player->audioFormat) {
		#ifdef ENABLE_FAAD
		case PIANO_AF_AACPLUS:
			player->aacHandle = NeAACDecOpen();
			/* set aac conf */
			conf = NeAACDecGetCurrentConfiguration(player->aacHandle);
			conf->outputFormat = FAAD_FMT_16BIT;
		    conf->downMatrix = 1;
			NeAACDecSetConfiguration(player->aacHandle, conf);

			player->waith.callback = BarPlayerAACCb;
			break;
		#endif /* ENABLE_FAAD */

		#ifdef ENABLE_MAD
		case PIANO_AF_MP3:
			mad_stream_init (&player->mp3Stream);
			mad_frame_init (&player->mp3Frame);
			mad_synth_init (&player->mp3Synth);

			player->waith.callback = BarPlayerMp3Cb;
			break;
		#endif /* ENABLE_MAD */

		default:
			/* FIXME: leaks memory */
			BarUiMsg (player->settings, MSG_ERR, "Unsupported audio format!\n");
			return PLAYER_RET_OK;
			break;
	}
	
	player->mode = PLAYER_INITIALIZED;

	/* This loop should work around song abortions by requesting the
	 * missing part of the song */
	do {
		snprintf (extraHeaders, sizeof (extraHeaders), "Range: bytes=%zu-\r\n",
				player->bytesReceived);
		wRet = WaitressFetchCall (&player->waith);
	} while (wRet == WAITRESS_RET_PARTIAL_FILE || wRet == WAITRESS_RET_TIMEOUT
			|| wRet == WAITRESS_RET_READ_ERR);

	/* If the song was played all the way through tag it. */
	if (wRet == WAITRESS_RET_OK) {
		BarFlyTag(&player->fly, player->settings);
	}

	switch (player->audioFormat) {
		#ifdef ENABLE_FAAD
		case PIANO_AF_AACPLUS:
			NeAACDecClose(player->aacHandle);
			free (player->sampleSize);
			break;
		#endif /* ENABLE_FAAD */

		#ifdef ENABLE_MAD
		case PIANO_AF_MP3:
			mad_synth_finish (&player->mp3Synth);
			mad_frame_finish (&player->mp3Frame);
			mad_stream_finish (&player->mp3Stream);
			break;
		#endif /* ENABLE_MAD */

		default:
			/* this should never happen: thread is aborted above */
			break;
	}

	if (player->aoError) {
		ret = (void *) PLAYER_RET_ERR;
	}

	ao_close(player->audioOutDevice);
	WaitressFree (&player->waith);
	free (player->buffer);

	player->mode = PLAYER_FINISHED_PLAYBACK;

	return ret;
}
Ejemplo n.º 22
0
int BarFlyID3AddFrame(struct id3_tag* tag, char const* type,
		char const* value, BarSettings_t const* settings)
{
	int exit_status = 0;
	int status;
	struct id3_frame* frame = NULL;
	union id3_field* field;
	id3_ucs4_t* ucs4 = NULL;
	int index;

	assert(tag != NULL);
	assert(type != NULL);
	assert(value != NULL);
	assert(settings != NULL);

	/*
	 * Create the frame.
	 */
	frame = id3_frame_new(type);
	if (frame == NULL) {
		BarUiMsg(settings, MSG_ERR, "Failed to create new frame (type = %s).\n",
				type);
		goto error;
	}

	frame->flags &= ~ID3_FRAME_FLAG_FORMATFLAGS;

	/*
	 * Get the string list field of the frame.
	 */
	index = 0;
	do {
		field = id3_frame_field(frame, index);
		index++;
	} while (id3_field_type(field) != ID3_FIELD_TYPE_STRINGLIST);
	assert(id3_field_type(field) == ID3_FIELD_TYPE_STRINGLIST);

	/*
	 * Add the value as a string to the field.
	 */
	ucs4 = id3_latin1_ucs4duplicate((id3_latin1_t*)value);
	if (ucs4 == NULL) {
		BarUiMsg(settings, MSG_ERR, "Could not allocate memory.\n");
		goto error;
	}

	status = id3_field_addstring(field, ucs4);
	if (status != 0) {
		BarUiMsg(settings, MSG_ERR, "Failed to set field value (value = %s).\n",
				value);
		goto error;
	}

	/*
	 * Attach the frame to the tag.
	 */
	status = id3_tag_attachframe(tag, frame);
	if (status != 0) {
		BarUiMsg(settings, MSG_ERR, "Failed to attach frame (type = %s).\n",
				type);
		goto error;
	}

	goto end;
	
error:
	if (frame != NULL) {
		id3_frame_delete(frame);
	}

	exit_status = -1;

end:
	if (ucs4 != NULL) {
		free(ucs4);
	}

	return exit_status;
}
Ejemplo n.º 23
0
int main (int argc, char **argv) {
	static BarApp_t app;

	memset (&app, 0, sizeof (app));

	/* save terminal attributes, before disabling echoing */
	BarTermInit ();

	/* signals */
	signal (SIGPIPE, SIG_IGN);

	/* init some things */
	gcry_check_version (NULL);
	gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
	gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
	BarPlayerInit ();

	BarSettingsInit (&app.settings);
	BarSettingsRead (&app.settings);

	PianoReturn_t pret;
	if ((pret = PianoInit (&app.ph, app.settings.partnerUser,
			app.settings.partnerPassword, app.settings.device,
			app.settings.inkey, app.settings.outkey)) != PIANO_RET_OK) {
		BarUiMsg (&app.settings, MSG_ERR, "Initialization failed:"
				" %s\n", PianoErrorToStr (pret));
		return 0;
	}

	BarUiMsg (&app.settings, MSG_NONE,
			"Welcome to " PACKAGE " (" VERSION ")! ");
	if (app.settings.keys[BAR_KS_HELP] == BAR_KS_DISABLED) {
		BarUiMsg (&app.settings, MSG_NONE, "\n");
	} else {
		BarUiMsg (&app.settings, MSG_NONE,
				"Press %c for a list of commands.\n",
				app.settings.keys[BAR_KS_HELP]);
	}

	curl_global_init (CURL_GLOBAL_DEFAULT);
	app.http = curl_easy_init ();
	assert (app.http != NULL);

	/* init fds */
	FD_ZERO(&app.input.set);

        int fifo_uses_this_fd = 0;
	app.input.fds[0] = STDIN_FILENO;
        if (isatty(fileno(stdin))) {
	    fifo_uses_this_fd = 1;
	    FD_SET(app.input.fds[0], &app.input.set);
            }

	/* open fifo read/write so it won't EOF if nobody writes to it */
	assert (sizeof (app.input.fds) / sizeof (*app.input.fds) >= 2);
	app.input.fds[fifo_uses_this_fd] = open (app.settings.fifo, O_RDWR);
	if (app.input.fds[fifo_uses_this_fd] != -1) {
		struct stat s;

		/* check for file type, must be fifo */
		fstat (app.input.fds[fifo_uses_this_fd], &s);
		if (!S_ISFIFO (s.st_mode)) {
			BarUiMsg (&app.settings, MSG_ERR, "File at %s is not a fifo\n", app.settings.fifo);
			close (app.input.fds[fifo_uses_this_fd]);
			app.input.fds[fifo_uses_this_fd] = -1;
		} else {
			FD_SET(app.input.fds[fifo_uses_this_fd], &app.input.set);
			BarUiMsg (&app.settings, MSG_INFO, "Control fifo at %s opened\n",
					app.settings.fifo);
		}
	}
        app.input.maxfd = app.input.fds[0];
        if (fifo_uses_this_fd>0)
		app.input.maxfd = app.input.fds[0] > app.input.fds[1] ? app.input.fds[0] : app.input.fds[1];
	++app.input.maxfd;

	BarMainLoop (&app);

	if (app.input.fds[fifo_uses_this_fd] != -1) {
		close (app.input.fds[fifo_uses_this_fd]);
	}

	/* write statefile */
	BarSettingsWrite (app.curStation, &app.settings);

	PianoDestroy (&app.ph);
	PianoDestroyPlaylist (app.songHistory);
	PianoDestroyPlaylist (app.playlist);
	curl_easy_cleanup (app.http);
	curl_global_cleanup ();
	BarPlayerDestroy ();
	BarSettingsDestroy (&app.settings);

	/* restore terminal attributes, zsh doesn't need this, bash does... */
	BarTermRestore ();

	return 0;
}
Ejemplo n.º 24
0
int BarFlyID3WriteFile(char const* file_path, struct id3_tag const* tag,
		BarSettings_t const* settings)
{
	/*
	 * './' + artist + '/' + album + '/' + BAR_FLY_TMP_MP3_FILE_NAME + '\0'
	 */
	int const TMP_FILE_PATH_LENGTH = 2 + BAR_FLY_NAME_LENGTH + 1 + 
			BAR_FLY_NAME_LENGTH + 1 + strlen(BAR_FLY_TMP_MP3_FILE_NAME) + 1;

	int exit_status = 0;
	int status_int;
	id3_length_t size1;
	id3_length_t size2;
	id3_byte_t* tag_buffer = NULL;
	FILE* audio_file = NULL;
	FILE* tmp_file = NULL;
	uint8_t audio_buffer[BAR_FLY_COPY_BLOCK_SIZE];
	char tmp_file_path[TMP_FILE_PATH_LENGTH];
	size_t read_count;
	size_t write_count;

	tmp_file_path[0] = '\0';

	/*
	 * For starters libid3tag kinda sucks.  It will only write a tag to a file 
	 * if the new tag is the same size as the old tag.  Which in this case,
	 * since there is no tag, will never work.  So writing of the tag to the
	 * file has to be done manually.
	 */

	/*
	 * Render the tag to a buffer that can then be written to the audio file.
	 */
	size1 = id3_tag_render(tag, NULL);
	tag_buffer = malloc(size1);
	if (tag_buffer == NULL) {
		BarUiMsg(settings, MSG_ERR, "Failed to allocate memory (bytes = %d).\n",
				size1);
		goto error;
	}

	size2 = id3_tag_render(tag, tag_buffer);
	if (size1 != size2) {
		BarUiMsg(settings, MSG_ERR, "Invalid tag size (expected = %d, "
				"recevied = %d).\n", size1, size2);
		goto error;
	}

	/*
	 * Prepending data to a file is not trivial in C.  Here the approach taken
	 * is to create a temporary file, write the tag to the beginning of the
	 * file, copy the audio file block by block to the tmp file, then overwrite
	 * the audio file with the tmp file.  This was done in order to minimize the
	 * chance of ending up with a broken audio file in case the program stopped
	 * durring this process.
	 */

	/*
	 * Open the audio file.
	 */
	audio_file = fopen(file_path, "rb");
	if (audio_file == NULL) {
		BarUiMsg(settings, MSG_ERR, "Could not read the audio file (%s) "
				"(%d:%s).\n", file_path, errno, strerror(errno));
		goto error;
	}

	/*
	 * Open the tmp file.
	 */
	if (strchr(file_path, '/') == NULL) {
		strcpy(tmp_file_path, BAR_FLY_TMP_MP3_FILE_NAME);
	} else {
		strncpy(tmp_file_path, file_path, TMP_FILE_PATH_LENGTH);
		tmp_file_path[TMP_FILE_PATH_LENGTH - 1] = '\0';
		dirname(tmp_file_path);
		strcat(tmp_file_path, "/");
		strcat(tmp_file_path, BAR_FLY_TMP_MP3_FILE_NAME);
	}

	tmp_file = fopen(tmp_file_path, "w+b");
	if (tmp_file == NULL) {
		BarUiMsg(settings, MSG_ERR, "Could not open the temporary file (%s) "
				"(%d:%s).\n", tmp_file_path, errno, strerror(errno));
		goto error;
	}

	/*
	 * Write the tag to the tmp file.
	 */
	write_count = fwrite(tag_buffer, 1, size2, tmp_file);
	if (write_count != size2) {
		BarUiMsg(settings, MSG_ERR, "Could not write the tag to the file (%s) "
				"(%d:%s).\n", tmp_file_path, errno, strerror(errno));
		goto error;
	}

	/*
	 * Read the audio file block by block until the end is reached.  Each block
	 * is written to the tmp file.
	 */
	while (feof(audio_file) == 0) {
		read_count = fread(audio_buffer, 1, BAR_FLY_COPY_BLOCK_SIZE,
				audio_file);
		if ((read_count != BAR_FLY_COPY_BLOCK_SIZE) &&
			(feof(audio_file) == 0)) {
			BarUiMsg(settings, MSG_ERR, "Failed to read the audio file (%s) "
					"(%d:%s).\n", file_path, errno, strerror(errno));
			goto error;
		}

		write_count = fwrite(audio_buffer, 1, read_count, tmp_file);
		if (write_count != read_count) {
			BarUiMsg(settings, MSG_ERR, "Failed to write to the tmp file "
					"(%s).\n", tmp_file_path);
			goto error;
		}
	}

	/*
	 * The entire contents of the audio file was copied to the tmp file.  Close
	 * the two files.
	 */
	fclose(tmp_file);
	tmp_file = NULL;

	fclose(audio_file);
	audio_file = NULL;

	/*
	 * Overwrite the audio file with the tmp file.
	 */
	status_int = rename(tmp_file_path, file_path);
	if (status_int != 0) {
		BarUiMsg(settings, MSG_ERR, "Could not overwrite the audio file "
				"(%d:%s).\n", errno, strerror(errno));
		goto error;
	}

	goto end;

error:
	/*
	 * Delete the tmp file if it exists.
	 */
	unlink(tmp_file_path);

	exit_status = -1;

end:
	if (tag_buffer != NULL) {
		free(tag_buffer);
	}

	if (audio_file != NULL) {
		fclose(audio_file);
	}

	if (tmp_file != NULL) {
		fclose(tmp_file);
	}

	return exit_status;
}
Ejemplo n.º 25
0
/*	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;
}
Ejemplo n.º 26
0
int BarFlyID3AddCover(struct id3_tag* tag, uint8_t const* cover_art,
		size_t cover_size, BarSettings_t const* settings)
{
	/*
	 * http://flac.sourceforge.net/api/group__flac__format.html#ga113
	 */
	int const PICTURE_TYPE_FRONT_COVER = 3;

	char const BAR_FLY_ID3_FRAME_PICTURE[] = "APIC";

	int exit_status = 0;
	int status;
	struct id3_frame* frame = NULL;
	union id3_field* field;
	int index;
	char* mime_type;

	assert(tag != NULL);
	assert(cover_art != NULL);
	assert(settings != NULL);

	/*
	 * Get a new picture frame.
	 */
	frame = id3_frame_new(BAR_FLY_ID3_FRAME_PICTURE);
	if (frame == NULL) {
		BarUiMsg(settings, MSG_ERR, "Failed to create new frame (type = %s).\n", 
				BAR_FLY_ID3_FRAME_PICTURE);
		goto error;
	}
	
	/*
	 * Go through all the frame fields setting the mime type, image type, and
	 * the image data.
	 */
	index = 0;
	field = id3_frame_field(frame, index);
	while (field != NULL) {
		switch (id3_field_type(field)) {
			/*
			 * Set the cover art mime type.
			 */
			case (ID3_FIELD_TYPE_LATIN1):
				if ((cover_art[0] == 0xFF) && (cover_art[1] == 0xD8)) {
					mime_type = "image/jpeg";
				} else if ((cover_art[0] == 0x89) &&
				           (cover_art[1] == 0x50) &&
				           (cover_art[2] == 0x4E) &&
				           (cover_art[3] == 0x47) &&
				           (cover_art[4] == 0x0D) &&
				           (cover_art[5] == 0x0A) &&
				           (cover_art[6] == 0x1A) &&
				           (cover_art[7] == 0x0A)) {
					mime_type = "image/png";
				} else {
					mime_type = NULL;
				}

				id3_field_setlatin1(field, (id3_latin1_t const*)mime_type);
				break;

			/*
			 * Designate this as the front cover.
			 */
			case (ID3_FIELD_TYPE_INT8):
				id3_field_setint(field, PICTURE_TYPE_FRONT_COVER);
				break;

			/*
			 * Set the image data.
			 */
			case (ID3_FIELD_TYPE_BINARYDATA):
				id3_field_setbinarydata(field, cover_art, cover_size);
				break;

			default:
				break;
		}

		index++;
		field = id3_frame_field(frame, index);
	}

	/*
	 * Attach the frame to the tag.
	 */
	status = id3_tag_attachframe(tag, frame);
	if (status != 0) {
		BarUiMsg(settings, MSG_ERR, "Failed to attach cover art frame.\n");
		goto error;
	}

	goto end;

error:
	if (frame != NULL) {
		id3_frame_delete(frame);
	}

	exit_status = -1;

end:
	return exit_status;
}
Ejemplo n.º 27
0
/*	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;
}
Ejemplo n.º 28
0
/*	player thread; for every song a new thread is started
 *	@param audioPlayer structure
 *	@return PLAYER_RET_*
 */
void *BarPlayerThread (void *data) {
	struct audioPlayer *player = data;
	char extraHeaders[32];
	void *ret = PLAYER_RET_OK;
	#ifdef ENABLE_FAAD
	NeAACDecConfigurationPtr conf;
	#endif
	WaitressReturn_t wRet = WAITRESS_RET_ERR;

	/* init handles */
	pthread_mutex_init (&player->pauseMutex, NULL);
	player->waith.data = (void *) player;
	/* extraHeaders will be initialized later */
	player->waith.extraHeaders = extraHeaders;
	player->buffer = malloc (BAR_PLAYER_BUFSIZE);

	switch (player->audioFormat) {
		#ifdef ENABLE_FAAD
		case PIANO_AF_AACPLUS:
			player->aacHandle = NeAACDecOpen();
			/* set aac conf */
			conf = NeAACDecGetCurrentConfiguration(player->aacHandle);
			conf->outputFormat = FAAD_FMT_16BIT;
		    conf->downMatrix = 1;
			NeAACDecSetConfiguration(player->aacHandle, conf);

			player->waith.callback = BarPlayerAACCb;
			break;
		#endif /* ENABLE_FAAD */

		#ifdef ENABLE_MAD
		case PIANO_AF_MP3:
			mad_stream_init (&player->mp3Stream);
			mad_frame_init (&player->mp3Frame);
			mad_synth_init (&player->mp3Synth);

			player->waith.callback = BarPlayerMp3Cb;
			break;
		#endif /* ENABLE_MAD */

		default:
			BarUiMsg (player->settings, MSG_ERR, "Unsupported audio format!\n");
			ret = (void *) PLAYER_RET_HARDFAIL;
			goto cleanup;
			break;
	}
	
	player->mode = PLAYER_INITIALIZED;

	/* This loop should work around song abortions by requesting the
	 * missing part of the song */
	do {
		snprintf (extraHeaders, sizeof (extraHeaders), "Range: bytes=%zu-\r\n",
				player->bytesReceived);
		wRet = WaitressFetchCall (&player->waith);
	} while (wRet == WAITRESS_RET_PARTIAL_FILE || wRet == WAITRESS_RET_TIMEOUT
			|| wRet == WAITRESS_RET_READ_ERR);

	switch (player->audioFormat) {
		#ifdef ENABLE_FAAD
		case PIANO_AF_AACPLUS:
			NeAACDecClose(player->aacHandle);
			free (player->sampleSize);
			break;
		#endif /* ENABLE_FAAD */

		#ifdef ENABLE_MAD
		case PIANO_AF_MP3:
			mad_synth_finish (&player->mp3Synth);
			mad_frame_finish (&player->mp3Frame);
			mad_stream_finish (&player->mp3Stream);
			break;
		#endif /* ENABLE_MAD */

		default:
			/* this should never happen */
			assert (0);
			break;
	}

	if (player->aoError) {
		ret = (void *) PLAYER_RET_HARDFAIL;
	}

	/* Pandora sends broken audio url’s sometimes (“bad request”). ignore them. */
	if (wRet != WAITRESS_RET_OK && wRet != WAITRESS_RET_CB_ABORT) {
		BarUiMsg (player->settings, MSG_ERR, "Cannot access audio file: %s\n",
				WaitressErrorToStr (wRet));
		ret = (void *) PLAYER_RET_SOFTFAIL;
	}

cleanup:
	ao_close (player->audioOutDevice);
	WaitressFree (&player->waith);
	free (player->buffer);

	player->mode = PLAYER_FINISHED_PLAYBACK;

	return ret;
}
Ejemplo n.º 29
0
/*	Excute external event handler
 *	@param settings containing the cmdline
 *	@param event type
 *	@param current station
 *	@param current song
 *	@param piano error-code (PIANO_RET_OK if not applicable)
 *	@param waitress error-code (WAITRESS_RET_OK if not applicable)
 */
void BarUiStartEventCmd (const BarSettings_t *settings, const char *type,
		const PianoStation_t *curStation, const PianoSong_t *curSong,
		const struct audioPlayer *player, PianoStation_t *stations,
                PianoReturn_t pRet, WaitressReturn_t wRet) {
	pid_t chld;
	int pipeFd[2];

	if (settings->eventCmd == NULL) {
		/* nothing to do... */
		return;
	}

	if (pipe (pipeFd) == -1) {
		BarUiMsg (settings, MSG_ERR, "Cannot create eventcmd pipe. (%s)\n", strerror (errno));
		return;
	}

	chld = fork ();
	if (chld == 0) {
		/* child */
		close (pipeFd[1]);
		dup2 (pipeFd[0], fileno (stdin));
		execl (settings->eventCmd, settings->eventCmd, type, (char *) NULL);
		BarUiMsg (settings, MSG_ERR, "Cannot start eventcmd. (%s)\n", strerror (errno));
		close (pipeFd[0]);
		exit (1);
	} else if (chld == -1) {
		BarUiMsg (settings, MSG_ERR, "Cannot fork eventcmd. (%s)\n", strerror (errno));
	} else {
		/* parent */
		int status;
		PianoStation_t *songStation = NULL;
		FILE *pipeWriteFd;

		close (pipeFd[0]);

		pipeWriteFd = fdopen (pipeFd[1], "w");

		if (curSong != NULL && stations != NULL && curStation->isQuickMix) {
			songStation = PianoFindStationById (stations, curSong->stationId);
		}

		fprintf (pipeWriteFd,
				"artist=%s\n"
				"title=%s\n"
				"album=%s\n"
				"coverArt=%s\n"
				"stationName=%s\n"
				"songStationName=%s\n"
				"pRet=%i\n"
				"pRetStr=%s\n"
				"wRet=%i\n"
				"wRetStr=%s\n"
				"songDuration=%lu\n"
				"songPlayed=%lu\n"
				"rating=%i\n"
				"detailUrl=%s\n",
				curSong == NULL ? "" : curSong->artist,
				curSong == NULL ? "" : curSong->title,
				curSong == NULL ? "" : curSong->album,
				curSong == NULL ? "" : curSong->coverArt,
				curStation == NULL ? "" : curStation->name,
				songStation == NULL ? "" : songStation->name,
				pRet,
				PianoErrorToStr (pRet),
				wRet,
				WaitressErrorToStr (wRet),
				player->songDuration,
				player->songPlayed,
				curSong == NULL ? PIANO_RATE_NONE : curSong->rating,
				curSong == NULL ? "" : curSong->detailUrl
				);

		if (stations != NULL) {
			/* send station list */
			PianoStation_t **sortedStations = NULL;
			size_t stationCount;
			sortedStations = BarSortedStations (stations, &stationCount,
					settings->sortOrder);
			assert (sortedStations != NULL);

			fprintf (pipeWriteFd, "stationCount=%zd\n", stationCount);

			for (size_t i = 0; i < stationCount; i++) {
				const PianoStation_t *currStation = sortedStations[i];
				fprintf (pipeWriteFd, "station%zd=%s\n", i,
						currStation->name);
			}
			free (sortedStations);
		} else {
			const char * const msg = "stationCount=0\n";
			fwrite (msg, sizeof (*msg), strlen (msg), pipeWriteFd);
		}
	
		/* closes pipeFd[1] as well */
		fclose (pipeWriteFd);
		/* wait to get rid of the zombie */
		waitpid (chld, &status, 0);
	}
}
Ejemplo n.º 30
0
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;
}