PlayerThread: move code into the player class
This commit is contained in:
		| @@ -142,6 +142,132 @@ struct player { | ||||
| 		 cross_fade_chunks(0), | ||||
| 		 cross_fade_tag(NULL), | ||||
| 		 elapsed_time(0.0) {} | ||||
|  | ||||
| 	void ClearAndDeletePipe() { | ||||
| 		pipe->Clear(buffer); | ||||
| 		delete pipe; | ||||
| 	} | ||||
|  | ||||
| 	void ClearAndReplacePipe(MusicPipe *_pipe) { | ||||
| 		ClearAndDeletePipe(); | ||||
| 		pipe = _pipe; | ||||
| 	} | ||||
|  | ||||
| 	void ReplacePipe(MusicPipe *_pipe) { | ||||
| 		delete pipe; | ||||
| 		pipe = _pipe; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Start the decoder. | ||||
| 	 * | ||||
| 	 * Player lock is not held. | ||||
| 	 */ | ||||
| 	void StartDecoder(MusicPipe &pipe); | ||||
|  | ||||
| 	/** | ||||
| 	 * The decoder has acknowledged the "START" command (see | ||||
| 	 * player::WaitForDecoder()).  This function checks if the decoder | ||||
| 	 * initialization has completed yet. | ||||
| 	 * | ||||
| 	 * The player lock is not held. | ||||
| 	 */ | ||||
| 	bool CheckDecoderStartup(); | ||||
|  | ||||
| 	/** | ||||
| 	 * Stop the decoder and clears (and frees) its music pipe. | ||||
| 	 * | ||||
| 	 * Player lock is not held. | ||||
| 	 */ | ||||
| 	void StopDecoder(); | ||||
|  | ||||
| 	/** | ||||
| 	 * Is the decoder still busy on the same song as the player? | ||||
| 	 * | ||||
| 	 * Note: this function does not check if the decoder is already | ||||
| 	 * finished. | ||||
| 	 */ | ||||
| 	gcc_pure | ||||
| 	bool IsDecoderAtCurrentSong() const { | ||||
| 		assert(pipe != nullptr); | ||||
|  | ||||
| 		return dc.pipe == pipe; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns true if the decoder is decoding the next song (or has begun | ||||
| 	 * decoding it, or has finished doing it), and the player hasn't | ||||
| 	 * switched to that song yet. | ||||
| 	 */ | ||||
| 	gcc_pure | ||||
| 	bool IsDecoderAtNextSong() const { | ||||
| 		return dc.pipe != nullptr && !IsDecoderAtCurrentSong(); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * This is the handler for the #PLAYER_COMMAND_SEEK command. | ||||
| 	 * | ||||
| 	 * The player lock is not held. | ||||
| 	 */ | ||||
| 	bool SeekDecoder(); | ||||
|  | ||||
| 	/** | ||||
| 	 * After the decoder has been started asynchronously, wait for | ||||
| 	 * the "START" command to finish.  The decoder may not be | ||||
| 	 * initialized yet, i.e. there is no audio_format information | ||||
| 	 * yet. | ||||
| 	 * | ||||
| 	 * The player lock is not held. | ||||
| 	 */ | ||||
| 	bool WaitForDecoder(); | ||||
|  | ||||
| 	/** | ||||
| 	 * Wrapper for audio_output_all_open().  Upon failure, it pauses the | ||||
| 	 * player. | ||||
| 	 * | ||||
| 	 * @return true on success | ||||
| 	 */ | ||||
| 	bool OpenOutput(); | ||||
|  | ||||
| 	/** | ||||
| 	 * Obtains the next chunk from the music pipe, optionally applies | ||||
| 	 * cross-fading, and sends it to all audio outputs. | ||||
| 	 * | ||||
| 	 * @return true on success, false on error (playback will be stopped) | ||||
| 	 */ | ||||
| 	bool PlayNextChunk(); | ||||
|  | ||||
| 	/** | ||||
| 	 * Sends a chunk of silence to the audio outputs.  This is | ||||
| 	 * called when there is not enough decoded data in the pipe | ||||
| 	 * yet, to prevent underruns in the hardware buffers. | ||||
| 	 * | ||||
| 	 * The player lock is not held. | ||||
| 	 */ | ||||
| 	bool SendSilence(); | ||||
|  | ||||
| 	/** | ||||
| 	 * Player lock must be held before calling. | ||||
| 	 */ | ||||
| 	void ProcessCommand(); | ||||
|  | ||||
| 	/** | ||||
| 	 * This is called at the border between two songs: the audio output | ||||
| 	 * has consumed all chunks of the current song, and we should start | ||||
| 	 * sending chunks from the next one. | ||||
| 	 * | ||||
| 	 * The player lock is not held. | ||||
| 	 * | ||||
| 	 * @return true on success, false on error (playback will be stopped) | ||||
| 	 */ | ||||
| 	bool SongBorder(); | ||||
|  | ||||
| 	/* | ||||
| 	 * The main loop of the player thread, during playback.  This | ||||
| 	 * is basically a state machine, which multiplexes data | ||||
| 	 * between the decoder thread and the output threads. | ||||
| 	 */ | ||||
| 	void Run(); | ||||
| }; | ||||
|  | ||||
| static void | ||||
| @@ -161,18 +287,10 @@ player_command_finished(player_control &pc) | ||||
| 	pc.Unlock(); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Start the decoder. | ||||
|  * | ||||
|  * Player lock is not held. | ||||
|  */ | ||||
| static void | ||||
| player_dc_start(player &player, MusicPipe &pipe) | ||||
| void | ||||
| player::StartDecoder(MusicPipe &_pipe) | ||||
| { | ||||
| 	player_control &pc = player.pc; | ||||
| 	decoder_control &dc = player.dc; | ||||
|  | ||||
| 	assert(player.queued || pc.command == PLAYER_COMMAND_SEEK); | ||||
| 	assert(queued || pc.command == PLAYER_COMMAND_SEEK); | ||||
| 	assert(pc.next_song != NULL); | ||||
|  | ||||
| 	unsigned start_ms = pc.next_song->start_ms; | ||||
| @@ -181,75 +299,33 @@ player_dc_start(player &player, MusicPipe &pipe) | ||||
|  | ||||
| 	dc.Start(pc.next_song->DupDetached(), | ||||
| 		 start_ms, pc.next_song->end_ms, | ||||
| 		 player.buffer, pipe); | ||||
| 		 buffer, _pipe); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Is the decoder still busy on the same song as the player? | ||||
|  * | ||||
|  * Note: this function does not check if the decoder is already | ||||
|  * finished. | ||||
|  */ | ||||
| static bool | ||||
| player_dc_at_current_song(const player &player) | ||||
| void | ||||
| player::StopDecoder() | ||||
| { | ||||
| 	assert(player.pipe != NULL); | ||||
|  | ||||
| 	return player.dc.pipe == player.pipe; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Returns true if the decoder is decoding the next song (or has begun | ||||
|  * decoding it, or has finished doing it), and the player hasn't | ||||
|  * switched to that song yet. | ||||
|  */ | ||||
| static bool | ||||
| player_dc_at_next_song(const player &player) | ||||
| { | ||||
| 	return player.dc.pipe != NULL && !player_dc_at_current_song(player); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Stop the decoder and clears (and frees) its music pipe. | ||||
|  * | ||||
|  * Player lock is not held. | ||||
|  */ | ||||
| static void | ||||
| player_dc_stop(player &player) | ||||
| { | ||||
| 	decoder_control &dc = player.dc; | ||||
|  | ||||
| 	dc.Stop(); | ||||
|  | ||||
| 	if (dc.pipe != NULL) { | ||||
| 		/* clear and free the decoder pipe */ | ||||
|  | ||||
| 		dc.pipe->Clear(player.buffer); | ||||
| 		dc.pipe->Clear(buffer); | ||||
|  | ||||
| 		if (dc.pipe != player.pipe) | ||||
| 		if (dc.pipe != pipe) | ||||
| 			delete dc.pipe; | ||||
|  | ||||
| 		dc.pipe = NULL; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * After the decoder has been started asynchronously, wait for the | ||||
|  * "START" command to finish.  The decoder may not be initialized yet, | ||||
|  * i.e. there is no audio_format information yet. | ||||
|  * | ||||
|  * The player lock is not held. | ||||
|  */ | ||||
| static bool | ||||
| player_wait_for_decoder(player &player) | ||||
| bool | ||||
| player::WaitForDecoder() | ||||
| { | ||||
| 	player_control &pc = player.pc; | ||||
| 	decoder_control &dc = player.dc; | ||||
|  | ||||
| 	assert(player.queued || pc.command == PLAYER_COMMAND_SEEK); | ||||
| 	assert(queued || pc.command == PLAYER_COMMAND_SEEK); | ||||
| 	assert(pc.next_song != NULL); | ||||
|  | ||||
| 	player.queued = false; | ||||
| 	queued = false; | ||||
|  | ||||
| 	Error error = dc.LockGetError(); | ||||
| 	if (error.IsDefined()) { | ||||
| @@ -264,15 +340,15 @@ player_wait_for_decoder(player &player) | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if (player.song != NULL) | ||||
| 		player.song->Free(); | ||||
| 	if (song != nullptr) | ||||
| 		song->Free(); | ||||
|  | ||||
| 	player.song = pc.next_song; | ||||
| 	player.elapsed_time = 0.0; | ||||
| 	song = pc.next_song; | ||||
| 	elapsed_time = 0.0; | ||||
|  | ||||
| 	/* set the "starting" flag, which will be cleared by | ||||
| 	   player_check_decoder_startup() */ | ||||
| 	player.decoder_starting = true; | ||||
| 	decoder_starting = true; | ||||
|  | ||||
| 	pc.Lock(); | ||||
|  | ||||
| @@ -312,26 +388,17 @@ real_song_duration(const Song *song, double decoder_duration) | ||||
| 	return decoder_duration - song->start_ms / 1000.0; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Wrapper for audio_output_all_open().  Upon failure, it pauses the | ||||
|  * player. | ||||
|  * | ||||
|  * @return true on success | ||||
|  */ | ||||
| static bool | ||||
| player_open_output(player &player) | ||||
| bool | ||||
| player::OpenOutput() | ||||
| { | ||||
| 	player_control &pc = player.pc; | ||||
|  | ||||
| 	assert(player.play_audio_format.IsDefined()); | ||||
| 	assert(play_audio_format.IsDefined()); | ||||
| 	assert(pc.state == PLAYER_STATE_PLAY || | ||||
| 	       pc.state == PLAYER_STATE_PAUSE); | ||||
|  | ||||
| 	Error error; | ||||
| 	if (audio_output_all_open(player.play_audio_format, player.buffer, | ||||
| 				  error)) { | ||||
| 		player.output_open = true; | ||||
| 		player.paused = false; | ||||
| 	if (audio_output_all_open(play_audio_format, buffer, error)) { | ||||
| 		output_open = true; | ||||
| 		paused = false; | ||||
|  | ||||
| 		pc.Lock(); | ||||
| 		pc.state = PLAYER_STATE_PLAY; | ||||
| @@ -343,11 +410,11 @@ player_open_output(player &player) | ||||
| 	} else { | ||||
| 		g_warning("%s", error.GetMessage()); | ||||
|  | ||||
| 		player.output_open = false; | ||||
| 		output_open = false; | ||||
|  | ||||
| 		/* pause: the user may resume playback as soon as an | ||||
| 		   audio output becomes available */ | ||||
| 		player.paused = true; | ||||
| 		paused = true; | ||||
|  | ||||
| 		pc.Lock(); | ||||
| 		pc.SetError(PLAYER_ERROR_OUTPUT, std::move(error)); | ||||
| @@ -360,20 +427,10 @@ player_open_output(player &player) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * The decoder has acknowledged the "START" command (see | ||||
|  * player_wait_for_decoder()).  This function checks if the decoder | ||||
|  * initialization has completed yet. | ||||
|  * | ||||
|  * The player lock is not held. | ||||
|  */ | ||||
| static bool | ||||
| player_check_decoder_startup(player &player) | ||||
| bool | ||||
| player::CheckDecoderStartup() | ||||
| { | ||||
| 	player_control &pc = player.pc; | ||||
| 	decoder_control &dc = player.dc; | ||||
|  | ||||
| 	assert(player.decoder_starting); | ||||
| 	assert(decoder_starting); | ||||
|  | ||||
| 	dc.Lock(); | ||||
|  | ||||
| @@ -392,7 +449,7 @@ player_check_decoder_startup(player &player) | ||||
|  | ||||
| 		dc.Unlock(); | ||||
|  | ||||
| 		if (player.output_open && | ||||
| 		if (output_open && | ||||
| 		    !audio_output_all_wait(&pc, 1)) | ||||
| 			/* the output devices havn't finished playing | ||||
| 			   all chunks yet - wait for that */ | ||||
| @@ -405,10 +462,10 @@ player_check_decoder_startup(player &player) | ||||
|  | ||||
| 		idle_add(IDLE_PLAYER); | ||||
|  | ||||
| 		player.play_audio_format = dc.out_audio_format; | ||||
| 		player.decoder_starting = false; | ||||
| 		play_audio_format = dc.out_audio_format; | ||||
| 		decoder_starting = false; | ||||
|  | ||||
| 		if (!player.paused && !player_open_output(player)) { | ||||
| 		if (!paused && !OpenOutput()) { | ||||
| 			char *uri = dc.song->GetURI(); | ||||
| 			g_warning("problems opening audio device " | ||||
| 				  "while playing \"%s\"", uri); | ||||
| @@ -428,30 +485,23 @@ player_check_decoder_startup(player &player) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Sends a chunk of silence to the audio outputs.  This is called when | ||||
|  * there is not enough decoded data in the pipe yet, to prevent | ||||
|  * underruns in the hardware buffers. | ||||
|  * | ||||
|  * The player lock is not held. | ||||
|  */ | ||||
| static bool | ||||
| player_send_silence(player &player) | ||||
| bool | ||||
| player::SendSilence() | ||||
| { | ||||
| 	assert(player.output_open); | ||||
| 	assert(player.play_audio_format.IsDefined()); | ||||
| 	assert(output_open); | ||||
| 	assert(play_audio_format.IsDefined()); | ||||
|  | ||||
| 	struct music_chunk *chunk = player.buffer.Allocate(); | ||||
| 	struct music_chunk *chunk = buffer.Allocate(); | ||||
| 	if (chunk == NULL) { | ||||
| 		g_warning("Failed to allocate silence buffer"); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| #ifndef NDEBUG | ||||
| 	chunk->audio_format = player.play_audio_format; | ||||
| 	chunk->audio_format = play_audio_format; | ||||
| #endif | ||||
|  | ||||
| 	const size_t frame_size = player.play_audio_format.GetFrameSize(); | ||||
| 	const size_t frame_size = play_audio_format.GetFrameSize(); | ||||
| 	/* this formula ensures that we don't send | ||||
| 	   partial frames */ | ||||
| 	unsigned num_frames = sizeof(chunk->data) / frame_size; | ||||
| @@ -463,63 +513,53 @@ player_send_silence(player &player) | ||||
| 	Error error; | ||||
| 	if (!audio_output_all_play(chunk, error)) { | ||||
| 		g_warning("%s", error.GetMessage()); | ||||
| 		player.buffer.Return(chunk); | ||||
| 		buffer.Return(chunk); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * This is the handler for the #PLAYER_COMMAND_SEEK command. | ||||
|  * | ||||
|  * The player lock is not held. | ||||
|  */ | ||||
| static bool player_seek_decoder(player &player) | ||||
| inline bool | ||||
| player::SeekDecoder() | ||||
| { | ||||
| 	player_control &pc = player.pc; | ||||
| 	Song *song = pc.next_song; | ||||
| 	decoder_control &dc = player.dc; | ||||
|  | ||||
| 	assert(pc.next_song != NULL); | ||||
|  | ||||
| 	const unsigned start_ms = song->start_ms; | ||||
| 	const unsigned start_ms = pc.next_song->start_ms; | ||||
|  | ||||
| 	if (!dc.LockIsCurrentSong(song)) { | ||||
| 	if (!dc.LockIsCurrentSong(pc.next_song)) { | ||||
| 		/* the decoder is already decoding the "next" song - | ||||
| 		   stop it and start the previous song again */ | ||||
|  | ||||
| 		player_dc_stop(player); | ||||
| 		StopDecoder(); | ||||
|  | ||||
| 		/* clear music chunks which might still reside in the | ||||
| 		   pipe */ | ||||
| 		player.pipe->Clear(player.buffer); | ||||
| 		pipe->Clear(buffer); | ||||
|  | ||||
| 		/* re-start the decoder */ | ||||
| 		player_dc_start(player, *player.pipe); | ||||
| 		if (!player_wait_for_decoder(player)) { | ||||
| 		StartDecoder(*pipe); | ||||
| 		if (!WaitForDecoder()) { | ||||
| 			/* decoder failure */ | ||||
| 			player_command_finished(pc); | ||||
| 			return false; | ||||
| 		} | ||||
| 	} else { | ||||
| 		if (!player_dc_at_current_song(player)) { | ||||
| 		if (!IsDecoderAtCurrentSong()) { | ||||
| 			/* the decoder is already decoding the "next" song, | ||||
| 			   but it is the same song file; exchange the pipe */ | ||||
| 			player.pipe->Clear(player.buffer); | ||||
| 			delete player.pipe; | ||||
| 			player.pipe = dc.pipe; | ||||
| 			ClearAndReplacePipe(dc.pipe); | ||||
| 		} | ||||
|  | ||||
| 		pc.next_song->Free(); | ||||
| 		pc.next_song = NULL; | ||||
| 		player.queued = false; | ||||
| 		queued = false; | ||||
| 	} | ||||
|  | ||||
| 	/* wait for the decoder to complete initialization */ | ||||
|  | ||||
| 	while (player.decoder_starting) { | ||||
| 		if (!player_check_decoder_startup(player)) { | ||||
| 	while (decoder_starting) { | ||||
| 		if (!CheckDecoderStartup()) { | ||||
| 			/* decoder failure */ | ||||
| 			player_command_finished(pc); | ||||
| 			return false; | ||||
| @@ -540,28 +580,23 @@ static bool player_seek_decoder(player &player) | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	player.elapsed_time = where; | ||||
| 	elapsed_time = where; | ||||
|  | ||||
| 	player_command_finished(pc); | ||||
|  | ||||
| 	player.xfade = XFADE_UNKNOWN; | ||||
| 	xfade = XFADE_UNKNOWN; | ||||
|  | ||||
| 	/* re-fill the buffer after seeking */ | ||||
| 	player.buffering = true; | ||||
| 	buffering = true; | ||||
|  | ||||
| 	audio_output_all_cancel(); | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Player lock must be held before calling. | ||||
|  */ | ||||
| static void player_process_command(player &player) | ||||
| inline void | ||||
| player::ProcessCommand() | ||||
| { | ||||
| 	player_control &pc = player.pc; | ||||
| 	gcc_unused decoder_control &dc = player.dc; | ||||
|  | ||||
| 	switch (pc.command) { | ||||
| 	case PLAYER_COMMAND_NONE: | ||||
| 	case PLAYER_COMMAND_STOP: | ||||
| @@ -578,30 +613,30 @@ static void player_process_command(player &player) | ||||
|  | ||||
| 	case PLAYER_COMMAND_QUEUE: | ||||
| 		assert(pc.next_song != NULL); | ||||
| 		assert(!player.queued); | ||||
| 		assert(!player_dc_at_next_song(player)); | ||||
| 		assert(!queued); | ||||
| 		assert(!IsDecoderAtNextSong()); | ||||
|  | ||||
| 		player.queued = true; | ||||
| 		queued = true; | ||||
| 		player_command_finished_locked(pc); | ||||
| 		break; | ||||
|  | ||||
| 	case PLAYER_COMMAND_PAUSE: | ||||
| 		pc.Unlock(); | ||||
|  | ||||
| 		player.paused = !player.paused; | ||||
| 		if (player.paused) { | ||||
| 		paused = !paused; | ||||
| 		if (paused) { | ||||
| 			audio_output_all_pause(); | ||||
| 			pc.Lock(); | ||||
|  | ||||
| 			pc.state = PLAYER_STATE_PAUSE; | ||||
| 		} else if (!player.play_audio_format.IsDefined()) { | ||||
| 		} else if (!play_audio_format.IsDefined()) { | ||||
| 			/* the decoder hasn't provided an audio format | ||||
| 			   yet - don't open the audio device yet */ | ||||
| 			pc.Lock(); | ||||
|  | ||||
| 			pc.state = PLAYER_STATE_PLAY; | ||||
| 		} else { | ||||
| 			player_open_output(player); | ||||
| 			OpenOutput(); | ||||
|  | ||||
| 			pc.Lock(); | ||||
| 		} | ||||
| @@ -611,7 +646,7 @@ static void player_process_command(player &player) | ||||
|  | ||||
| 	case PLAYER_COMMAND_SEEK: | ||||
| 		pc.Unlock(); | ||||
| 		player_seek_decoder(player); | ||||
| 		SeekDecoder(); | ||||
| 		pc.Lock(); | ||||
| 		break; | ||||
|  | ||||
| @@ -624,22 +659,22 @@ static void player_process_command(player &player) | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		if (player_dc_at_next_song(player)) { | ||||
| 		if (IsDecoderAtNextSong()) { | ||||
| 			/* the decoder is already decoding the song - | ||||
| 			   stop it and reset the position */ | ||||
| 			pc.Unlock(); | ||||
| 			player_dc_stop(player); | ||||
| 			StopDecoder(); | ||||
| 			pc.Lock(); | ||||
| 		} | ||||
|  | ||||
| 		pc.next_song->Free(); | ||||
| 		pc.next_song = NULL; | ||||
| 		player.queued = false; | ||||
| 		queued = false; | ||||
| 		player_command_finished_locked(pc); | ||||
| 		break; | ||||
|  | ||||
| 	case PLAYER_COMMAND_REFRESH: | ||||
| 		if (player.output_open && !player.paused) { | ||||
| 		if (output_open && !paused) { | ||||
| 			pc.Unlock(); | ||||
| 			audio_output_all_check(); | ||||
| 			pc.Lock(); | ||||
| @@ -647,7 +682,7 @@ static void player_process_command(player &player) | ||||
|  | ||||
| 		pc.elapsed_time = audio_output_all_get_elapsed_time(); | ||||
| 		if (pc.elapsed_time < 0.0) | ||||
| 			pc.elapsed_time = player.elapsed_time; | ||||
| 			pc.elapsed_time = elapsed_time; | ||||
|  | ||||
| 		player_command_finished_locked(pc); | ||||
| 		break; | ||||
| @@ -714,18 +749,9 @@ play_chunk(player_control &pc, | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Obtains the next chunk from the music pipe, optionally applies | ||||
|  * cross-fading, and sends it to all audio outputs. | ||||
|  * | ||||
|  * @return true on success, false on error (playback will be stopped) | ||||
|  */ | ||||
| static bool | ||||
| play_next_chunk(player &player) | ||||
| inline bool | ||||
| player::PlayNextChunk() | ||||
| { | ||||
| 	player_control &pc = player.pc; | ||||
| 	decoder_control &dc = player.dc; | ||||
|  | ||||
| 	if (!audio_output_all_wait(&pc, 64)) | ||||
| 		/* the output pipe is still large enough, don't send | ||||
| 		   another chunk */ | ||||
| @@ -733,38 +759,36 @@ play_next_chunk(player &player) | ||||
|  | ||||
| 	unsigned cross_fade_position; | ||||
| 	struct music_chunk *chunk = NULL; | ||||
| 	if (player.xfade == XFADE_ENABLED && | ||||
| 	    player_dc_at_next_song(player) && | ||||
| 	    (cross_fade_position = player.pipe->GetSize()) | ||||
| 	    <= player.cross_fade_chunks) { | ||||
| 	if (xfade == XFADE_ENABLED && IsDecoderAtNextSong() && | ||||
| 	    (cross_fade_position = pipe->GetSize()) <= cross_fade_chunks) { | ||||
| 		/* perform cross fade */ | ||||
| 		music_chunk *other_chunk = dc.pipe->Shift(); | ||||
|  | ||||
| 		if (!player.cross_fading) { | ||||
| 		if (!cross_fading) { | ||||
| 			/* beginning of the cross fade - adjust | ||||
| 			   crossFadeChunks which might be bigger than | ||||
| 			   the remaining number of chunks in the old | ||||
| 			   song */ | ||||
| 			player.cross_fade_chunks = cross_fade_position; | ||||
| 			player.cross_fading = true; | ||||
| 			cross_fade_chunks = cross_fade_position; | ||||
| 			cross_fading = true; | ||||
| 		} | ||||
|  | ||||
| 		if (other_chunk != NULL) { | ||||
| 			chunk = player.pipe->Shift(); | ||||
| 			chunk = pipe->Shift(); | ||||
| 			assert(chunk != NULL); | ||||
| 			assert(chunk->other == NULL); | ||||
|  | ||||
| 			/* don't send the tags of the new song (which | ||||
| 			   is being faded in) yet; postpone it until | ||||
| 			   the current song is faded out */ | ||||
| 			player.cross_fade_tag = | ||||
| 				Tag::MergeReplace(player.cross_fade_tag, | ||||
| 			cross_fade_tag = | ||||
| 				Tag::MergeReplace(cross_fade_tag, | ||||
| 						  other_chunk->tag); | ||||
| 			other_chunk->tag = NULL; | ||||
|  | ||||
| 			if (std::isnan(pc.mixramp_delay_seconds)) { | ||||
| 				chunk->mix_ratio = ((float)cross_fade_position) | ||||
| 					     / player.cross_fade_chunks; | ||||
| 					     / cross_fade_chunks; | ||||
| 			} else { | ||||
| 				chunk->mix_ratio = nan(""); | ||||
| 			} | ||||
| @@ -777,7 +801,7 @@ play_next_chunk(player &player) | ||||
| 				   beginning of the new song, we can | ||||
| 				   easily recover by throwing it away | ||||
| 				   now */ | ||||
| 				player.buffer.Return(other_chunk); | ||||
| 				buffer.Return(other_chunk); | ||||
| 				other_chunk = NULL; | ||||
| 			} | ||||
|  | ||||
| @@ -792,7 +816,7 @@ play_next_chunk(player &player) | ||||
| 				   cross fading */ | ||||
| 				dc.Unlock(); | ||||
|  | ||||
| 				player.xfade = XFADE_DISABLED; | ||||
| 				xfade = XFADE_DISABLED; | ||||
| 			} else { | ||||
| 				/* wait for the decoder */ | ||||
| 				dc.Signal(); | ||||
| @@ -805,26 +829,24 @@ play_next_chunk(player &player) | ||||
| 	} | ||||
|  | ||||
| 	if (chunk == NULL) | ||||
| 		chunk = player.pipe->Shift(); | ||||
| 		chunk = pipe->Shift(); | ||||
|  | ||||
| 	assert(chunk != NULL); | ||||
|  | ||||
| 	/* insert the postponed tag if cross-fading is finished */ | ||||
|  | ||||
| 	if (player.xfade != XFADE_ENABLED && player.cross_fade_tag != NULL) { | ||||
| 		chunk->tag = Tag::MergeReplace(chunk->tag, | ||||
| 					       player.cross_fade_tag); | ||||
| 		player.cross_fade_tag = NULL; | ||||
| 	if (xfade != XFADE_ENABLED && cross_fade_tag != nullptr) { | ||||
| 		chunk->tag = Tag::MergeReplace(chunk->tag, cross_fade_tag); | ||||
| 		cross_fade_tag = nullptr; | ||||
| 	} | ||||
|  | ||||
| 	/* play the current chunk */ | ||||
|  | ||||
| 	Error error; | ||||
| 	if (!play_chunk(player.pc, player.song, chunk, player.buffer, | ||||
| 			player.play_audio_format, error)) { | ||||
| 	if (!play_chunk(pc, song, chunk, buffer, play_audio_format, error)) { | ||||
| 		g_warning("%s", error.GetMessage()); | ||||
|  | ||||
| 		player.buffer.Return(chunk); | ||||
| 		buffer.Return(chunk); | ||||
|  | ||||
| 		pc.Lock(); | ||||
|  | ||||
| @@ -833,7 +855,7 @@ play_next_chunk(player &player) | ||||
| 		/* pause: the user may resume playback as soon as an | ||||
| 		   audio output becomes available */ | ||||
| 		pc.state = PLAYER_STATE_PAUSE; | ||||
| 		player.paused = true; | ||||
| 		paused = true; | ||||
|  | ||||
| 		pc.Unlock(); | ||||
|  | ||||
| @@ -848,45 +870,34 @@ play_next_chunk(player &player) | ||||
| 	dc.Lock(); | ||||
| 	if (!dc.IsIdle() && | ||||
| 	    dc.pipe->GetSize() <= (pc.buffered_before_play + | ||||
| 				   player.buffer.GetSize() * 3) / 4) | ||||
| 				   buffer.GetSize() * 3) / 4) | ||||
| 		dc.Signal(); | ||||
| 	dc.Unlock(); | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * This is called at the border between two songs: the audio output | ||||
|  * has consumed all chunks of the current song, and we should start | ||||
|  * sending chunks from the next one. | ||||
|  * | ||||
|  * The player lock is not held. | ||||
|  * | ||||
|  * @return true on success, false on error (playback will be stopped) | ||||
|  */ | ||||
| static bool | ||||
| player_song_border(player &player) | ||||
| inline bool | ||||
| player::SongBorder() | ||||
| { | ||||
| 	player.xfade = XFADE_UNKNOWN; | ||||
| 	xfade = XFADE_UNKNOWN; | ||||
|  | ||||
| 	char *uri = player.song->GetURI(); | ||||
| 	char *uri = song->GetURI(); | ||||
| 	g_message("played \"%s\"", uri); | ||||
| 	g_free(uri); | ||||
|  | ||||
| 	delete player.pipe; | ||||
| 	player.pipe = player.dc.pipe; | ||||
| 	ReplacePipe(dc.pipe); | ||||
|  | ||||
| 	audio_output_all_song_border(); | ||||
|  | ||||
| 	if (!player_wait_for_decoder(player)) | ||||
| 	if (!WaitForDecoder()) | ||||
| 		return false; | ||||
|  | ||||
| 	player_control &pc = player.pc; | ||||
| 	pc.Lock(); | ||||
|  | ||||
| 	const bool border_pause = pc.border_pause; | ||||
| 	if (border_pause) { | ||||
| 		player.paused = true; | ||||
| 		paused = true; | ||||
| 		pc.state = PLAYER_STATE_PAUSE; | ||||
| 	} | ||||
|  | ||||
| @@ -898,28 +909,20 @@ player_song_border(player &player) | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * The main loop of the player thread, during playback.  This is | ||||
|  * basically a state machine, which multiplexes data between the | ||||
|  * decoder thread and the output threads. | ||||
|  */ | ||||
| static void | ||||
| do_play(player_control &pc, decoder_control &dc, | ||||
| 	MusicBuffer &buffer) | ||||
| inline void | ||||
| player::Run() | ||||
| { | ||||
| 	player player(pc, dc, buffer); | ||||
|  | ||||
| 	pc.Unlock(); | ||||
|  | ||||
| 	player.pipe = new MusicPipe(); | ||||
| 	pipe = new MusicPipe(); | ||||
|  | ||||
| 	player_dc_start(player, *player.pipe); | ||||
| 	if (!player_wait_for_decoder(player)) { | ||||
| 		assert(player.song == NULL); | ||||
| 	StartDecoder(*pipe); | ||||
| 	if (!WaitForDecoder()) { | ||||
| 		assert(song == NULL); | ||||
|  | ||||
| 		player_dc_stop(player); | ||||
| 		StopDecoder(); | ||||
| 		player_command_finished(pc); | ||||
| 		delete player.pipe; | ||||
| 		delete pipe; | ||||
| 		GlobalEvents::Emit(GlobalEvents::PLAYLIST); | ||||
| 		pc.Lock(); | ||||
| 		return; | ||||
| @@ -929,12 +932,12 @@ do_play(player_control &pc, decoder_control &dc, | ||||
| 	pc.state = PLAYER_STATE_PLAY; | ||||
|  | ||||
| 	if (pc.command == PLAYER_COMMAND_SEEK) | ||||
| 		player.elapsed_time = pc.seek_where; | ||||
| 		elapsed_time = pc.seek_where; | ||||
|  | ||||
| 	player_command_finished_locked(pc); | ||||
|  | ||||
| 	while (true) { | ||||
| 		player_process_command(player); | ||||
| 		ProcessCommand(); | ||||
| 		if (pc.command == PLAYER_COMMAND_STOP || | ||||
| 		    pc.command == PLAYER_COMMAND_EXIT || | ||||
| 		    pc.command == PLAYER_COMMAND_CLOSE_AUDIO) { | ||||
| @@ -945,19 +948,18 @@ do_play(player_control &pc, decoder_control &dc, | ||||
|  | ||||
| 		pc.Unlock(); | ||||
|  | ||||
| 		if (player.buffering) { | ||||
| 		if (buffering) { | ||||
| 			/* buffering at the start of the song - wait | ||||
| 			   until the buffer is large enough, to | ||||
| 			   prevent stuttering on slow machines */ | ||||
|  | ||||
| 			if (player.pipe->GetSize() < pc.buffered_before_play && | ||||
| 			if (pipe->GetSize() < pc.buffered_before_play && | ||||
| 			    !dc.LockIsIdle()) { | ||||
| 				/* not enough decoded buffer space yet */ | ||||
|  | ||||
| 				if (!player.paused && | ||||
| 				    player.output_open && | ||||
| 				if (!paused && output_open && | ||||
| 				    audio_output_all_check() < 4 && | ||||
| 				    !player_send_silence(player)) | ||||
| 				    !SendSilence()) | ||||
| 					break; | ||||
|  | ||||
| 				dc.Lock(); | ||||
| @@ -968,14 +970,14 @@ do_play(player_control &pc, decoder_control &dc, | ||||
| 				continue; | ||||
| 			} else { | ||||
| 				/* buffering is complete */ | ||||
| 				player.buffering = false; | ||||
| 				buffering = false; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if (player.decoder_starting) { | ||||
| 		if (decoder_starting) { | ||||
| 			/* wait until the decoder is initialized completely */ | ||||
|  | ||||
| 			if (!player_check_decoder_startup(player)) | ||||
| 			if (!CheckDecoderStartup()) | ||||
| 				break; | ||||
|  | ||||
| 			pc.Lock(); | ||||
| @@ -985,31 +987,30 @@ do_play(player_control &pc, decoder_control &dc, | ||||
| #ifndef NDEBUG | ||||
| 		/* | ||||
| 		music_pipe_check_format(&play_audio_format, | ||||
| 					player.next_song_chunk, | ||||
| 					next_song_chunk, | ||||
| 					&dc.out_audio_format); | ||||
| 		*/ | ||||
| #endif | ||||
|  | ||||
| 		if (dc.LockIsIdle() && player.queued && | ||||
| 		    dc.pipe == player.pipe) { | ||||
| 		if (dc.LockIsIdle() && queued && dc.pipe == pipe) { | ||||
| 			/* the decoder has finished the current song; | ||||
| 			   make it decode the next song */ | ||||
|  | ||||
| 			assert(dc.pipe == NULL || dc.pipe == player.pipe); | ||||
| 			assert(dc.pipe == NULL || dc.pipe == pipe); | ||||
|  | ||||
| 			player_dc_start(player, *new MusicPipe()); | ||||
| 			StartDecoder(*new MusicPipe()); | ||||
| 		} | ||||
|  | ||||
| 		if (/* no cross-fading if MPD is going to pause at the | ||||
| 		       end of the current song */ | ||||
| 		    !pc.border_pause && | ||||
| 		    player_dc_at_next_song(player) && | ||||
| 		    player.xfade == XFADE_UNKNOWN && | ||||
| 		    IsDecoderAtNextSong() && | ||||
| 		    xfade == XFADE_UNKNOWN && | ||||
| 		    !dc.LockIsStarting()) { | ||||
| 			/* enable cross fading in this song?  if yes, | ||||
| 			   calculate how many chunks will be required | ||||
| 			   for it */ | ||||
| 			player.cross_fade_chunks = | ||||
| 			cross_fade_chunks = | ||||
| 				cross_fade_calc(pc.cross_fade_seconds, dc.total_time, | ||||
| 						pc.mixramp_db, | ||||
| 						pc.mixramp_delay_seconds, | ||||
| @@ -1018,29 +1019,29 @@ do_play(player_control &pc, decoder_control &dc, | ||||
| 						dc.mixramp_start, | ||||
| 						dc.mixramp_prev_end, | ||||
| 						dc.out_audio_format, | ||||
| 						player.play_audio_format, | ||||
| 						player.buffer.GetSize() - | ||||
| 						play_audio_format, | ||||
| 						buffer.GetSize() - | ||||
| 						pc.buffered_before_play); | ||||
| 			if (player.cross_fade_chunks > 0) { | ||||
| 				player.xfade = XFADE_ENABLED; | ||||
| 				player.cross_fading = false; | ||||
| 			if (cross_fade_chunks > 0) { | ||||
| 				xfade = XFADE_ENABLED; | ||||
| 				cross_fading = false; | ||||
| 			} else | ||||
| 				/* cross fading is disabled or the | ||||
| 				   next song is too short */ | ||||
| 				player.xfade = XFADE_DISABLED; | ||||
| 				xfade = XFADE_DISABLED; | ||||
| 		} | ||||
|  | ||||
| 		if (player.paused) { | ||||
| 		if (paused) { | ||||
| 			pc.Lock(); | ||||
|  | ||||
| 			if (pc.command == PLAYER_COMMAND_NONE) | ||||
| 				pc.Wait(); | ||||
| 			continue; | ||||
| 		} else if (!player.pipe->IsEmpty()) { | ||||
| 		} else if (!pipe->IsEmpty()) { | ||||
| 			/* at least one music chunk is ready - send it | ||||
| 			   to the audio output */ | ||||
|  | ||||
| 			play_next_chunk(player); | ||||
| 			PlayNextChunk(); | ||||
| 		} else if (audio_output_all_check() > 0) { | ||||
| 			/* not enough data from decoder, but the | ||||
| 			   output thread is still busy, so it's | ||||
| @@ -1048,45 +1049,44 @@ do_play(player_control &pc, decoder_control &dc, | ||||
|  | ||||
| 			/* XXX synchronize in a better way */ | ||||
| 			g_usleep(10000); | ||||
| 		} else if (player_dc_at_next_song(player)) { | ||||
| 		} else if (IsDecoderAtNextSong()) { | ||||
| 			/* at the beginning of a new song */ | ||||
|  | ||||
| 			if (!player_song_border(player)) | ||||
| 			if (!SongBorder()) | ||||
| 				break; | ||||
| 		} else if (dc.LockIsIdle()) { | ||||
| 			/* check the size of the pipe again, because | ||||
| 			   the decoder thread may have added something | ||||
| 			   since we last checked */ | ||||
| 			if (player.pipe->IsEmpty()) { | ||||
| 			if (pipe->IsEmpty()) { | ||||
| 				/* wait for the hardware to finish | ||||
| 				   playback */ | ||||
| 				audio_output_all_drain(); | ||||
| 				break; | ||||
| 			} | ||||
| 		} else if (player.output_open) { | ||||
| 		} else if (output_open) { | ||||
| 			/* the decoder is too busy and hasn't provided | ||||
| 			   new PCM data in time: send silence (if the | ||||
| 			   output pipe is empty) */ | ||||
| 			if (!player_send_silence(player)) | ||||
| 			if (!SendSilence()) | ||||
| 				break; | ||||
| 		} | ||||
|  | ||||
| 		pc.Lock(); | ||||
| 	} | ||||
|  | ||||
| 	player_dc_stop(player); | ||||
| 	StopDecoder(); | ||||
|  | ||||
| 	player.pipe->Clear(player.buffer); | ||||
| 	delete player.pipe; | ||||
| 	ClearAndDeletePipe(); | ||||
|  | ||||
| 	delete player.cross_fade_tag; | ||||
| 	delete cross_fade_tag; | ||||
|  | ||||
| 	if (player.song != NULL) | ||||
| 		player.song->Free(); | ||||
| 	if (song != nullptr) | ||||
| 		song->Free(); | ||||
|  | ||||
| 	pc.Lock(); | ||||
|  | ||||
| 	if (player.queued) { | ||||
| 	if (queued) { | ||||
| 		assert(pc.next_song != NULL); | ||||
| 		pc.next_song->Free(); | ||||
| 		pc.next_song = NULL; | ||||
| @@ -1101,6 +1101,14 @@ do_play(player_control &pc, decoder_control &dc, | ||||
| 	pc.Lock(); | ||||
| } | ||||
|  | ||||
| static void | ||||
| do_play(player_control &pc, decoder_control &dc, | ||||
| 	MusicBuffer &buffer) | ||||
| { | ||||
| 	player player(pc, dc, buffer); | ||||
| 	player.Run(); | ||||
| } | ||||
|  | ||||
| static gpointer | ||||
| player_task(gpointer arg) | ||||
| { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Max Kellermann
					Max Kellermann