aboutsummaryrefslogblamecommitdiffstats
path: root/src/playlist.c
blob: 615aabec8f1d623c86f4efde6f452d87e850c6fd (plain) (tree)
1
2
                                
                                                                   















                                                                            
                              
                          
                        
                   
                           


                    
                 
                 
                     
                   
                            
                
                 
 

                 



                      

                   
 
                                                          
 
                                                  

                                

 
                                                     
 
                                           
                                

 

                                               
 
                               

                       
                                       
 
                                                          
                                

 

                                        
 
                                    

                                                                     
 

                               

 

                                          
 
                                       

 
                                             
 
                               
 

                                                               

                                                                         

                                              
         
 
                                      
 
                               
 
                                      

 


                                               
           
                                                                    
 

                          
 
                                                           
 
                                 
 
                                                        

                                                 
                                       



                        
 



                                                                      
                                                            
 
                                                             


                                                                   

                                                     

                                      


         
                   
                                                   
 
                                                       
                            
 
                                                                   

 
    
                                                                               



                                     
                               

                       

                                                         
 

                                                                       

                    
                                                        



                                                                      

                                                                   
 
                                                      
 
                                                                       

                                                                  

                                                                 



                                                                  
                                                                          





                                                     
                                      
         
 

                                      
                                                                        
                    
                                                      
         

 
             
                    

                                                                          












                                              
                                                                   






                                                    
                                                           
 
      
 








                                
                                




                                            

                                                                             
 
                          
 
                                            
 

                                
                                                    
 
                                                           

 
                    

                                                        
 
                                  
                    
 
                                            
                                                 
 
                                                    
 
                                                  
 
                                     


                                                                  
                               

                                                     
                    



                                                                                 

         
                                      
 
                                                      
 

                               
 
                                       

 

                                                                              
 

                                  

                                                             
                                                 
 
                                                    
 
                                                   
 

                                                                    

                                                            

                                                                          
                                                                
                                                                          
                                                                 
                

                                                      



                                                         

         
                                      
 
                                                      
 
                                       

 

                                                                              
 

                                                                

                                   
                                                    
 
                                                           

 

                                                            
 
                                  
                           
 
                                                   
                                                 
 
                                                    
 
                                                                    
 
                                                                       

                                                                     

                                                                              
                             
                                          
 

                                                                  



                                                                        
 
                                                      
                                                                 
                                                                             


                                                               
                                               

                              
         
 

                                        

                                                                   
 
                                             
 
                                      
 

                                                         

                                                 
         
 
                                                      
 
                                       

 

                                                              
 
                                                              
                     
                                                    
 
                                                  

 
    
                                                                           
 


                                                                     

                              

 
    
                                                                
 
                          
                  
 

                                 
 
                                                           

                                 

                                                           
 
                         
                                     

 

                                                       
 



                                                                     
                                                     
 
                               

                                                                   
                       
 
                                                  



                                                                      
                                                      
              

                                                                 
                                                


                                                              
                                         
                                                                    
         

 



                                                                

                                                      
 
                                
 
                                  
                                                      
 

                                          
                                          
            
                                        
 
                                                                         
                                                                          
                                                                    

                                                           
                                       
            
                                                        
                                             

 

                                                  
 
                                      

 

                                                  
 
                                      

 
                                                                    
 
                                             

                       
                                        
 

                                                                      

                                                                        
 
                               

 

                                                                    
 
                                  
                        
 
                                                          
                                                 
 

                                                                      
                                                 
 
                                        
                                               
 
                                                    
 



                                                                           


                                                                
                     
                                               
                                                  
                                                                       
                                                       
                                                                              
         
 
                                               
 
                                      
                                           







                                                           
                 

         
                                      
 
                                                      
 
                                       

 

                                                                       
 
                                                               
                     
                                                    
 
                                                      

 
                                                    
 
                                   
                                                                  

                                                                               
 
                                              

 
                                                                    
 

                                  
                                             
                       
 
                                                    
 
                                        
 
                                     
                                                        
                                       
 


                                                                         

                             
                                                      

                                            


                                                                     
                                                
                                                                         
                                                                          

                                                                             

                 
                                        
 
                                                      
 
                               

 
                                               
 
                                  
                   
 
                                                
                       
 
                                                    
 

                                           
                                                            


                                                                               
 


                                                                             
                      
                                              

                                                          

                      
                                                                 
                                             
 
                      
                                       
         

                                          

                                                            
 
                                      
 
                                                      

 
                                                     
 


                                                                  
 
                  

 

                                             
 
                                       

 

                                            
 
                                              

 

                                                           
 
                                                            
 
/* the Music Player Daemon (MPD)
 * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
 * This project's homepage is: http://www.musicpd.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "playlist_internal.h"
#include "playlist_save.h"
#include "queue_print.h"
#include "locate.h"
#include "player_control.h"
#include "command.h"
#include "ls.h"
#include "tag.h"
#include "song.h"
#include "conf.h"
#include "database.h"
#include "mapper.h"
#include "stored_playlist.h"
#include "ack.h"
#include "idle.h"

#include <glib.h>

#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>

static void incrPlaylistVersion(struct playlist *playlist)
{
	queue_increment_version(&playlist->queue);

	idle_add(IDLE_PLAYLIST);
}

void playlistVersionChange(struct playlist *playlist)
{
	queue_modify_all(&playlist->queue);
	idle_add(IDLE_PLAYLIST);
}

void
playlist_tag_changed(struct playlist *playlist)
{
	if (!playlist->playing)
		return;

	assert(playlist->current >= 0);

	queue_modify(&playlist->queue, playlist->current);
	idle_add(IDLE_PLAYLIST);
}

void
playlist_init(struct playlist *playlist)
{
	queue_init(&playlist->queue,
		   config_get_positive(CONF_MAX_PLAYLIST_LENGTH,
				       DEFAULT_PLAYLIST_MAX_LENGTH));

	playlist->queued = -1;
	playlist->current = -1;
}

void
playlist_finish(struct playlist *playlist)
{
	queue_finish(&playlist->queue);
}

void clearPlaylist(struct playlist *playlist)
{
	stopPlaylist(playlist);

	/* make sure there are no references to allocated songs
	   anymore */
	for (unsigned i = 0; i < queue_length(&playlist->queue); i++) {
		const struct song *song = queue_get(&playlist->queue, i);
		if (!song_in_database(song))
			pc_song_deleted(song);
	}

	queue_clear(&playlist->queue);

	playlist->current = -1;

	incrPlaylistVersion(playlist);
}

/**
 * Queue a song, addressed by its order number.
 */
static void
playlist_queue_song_order(struct playlist *playlist, unsigned order)
{
	struct song *song;
	char *uri;

	assert(queue_valid_order(&playlist->queue, order));

	playlist->queued = order;

	song = queue_get_order(&playlist->queue, order);
	uri = song_get_uri(song);
	g_debug("playlist: queue song %i:\"%s\"",
		playlist->queued, uri);
	g_free(uri);

	queueSong(song);
}

/**
 * Check if the player thread has already started playing the "queued"
 * song.
 */
static void syncPlaylistWithQueue(struct playlist *playlist)
{
	if (pc.next_song == NULL && playlist->queued != -1) {
		/* queued song has started: copy queued to current,
		   and notify the clients */

		playlist->current = playlist->queued;
		playlist->queued = -1;

		idle_add(IDLE_PLAYER);
	}
}

const struct song *
playlist_get_queued_song(struct playlist *playlist)
{
	if (!playlist->playing || playlist->queued < 0)
		return NULL;

	return queue_get_order(&playlist->queue, playlist->queued);
}

void
playlist_update_queued_song(struct playlist *playlist, const struct song *prev)
{
	int next_order;
	const struct song *next_song;

	if (!playlist->playing)
		return;

	assert(!queue_is_empty(&playlist->queue));
	assert((playlist->queued < 0) == (prev == NULL));

	next_order = playlist->current >= 0
		? queue_next_order(&playlist->queue, playlist->current)
		: 0;

	if (next_order == 0 && playlist->queue.random) {
		/* shuffle the song order again, so we get a different
		   order each time the playlist is played
		   completely */
		unsigned current_position =
			queue_order_to_position(&playlist->queue,
						playlist->current);

		queue_shuffle_order(&playlist->queue);

		/* make sure that the playlist->current still points to
		   the current song, after the song order has been
		   shuffled */
		playlist->current =
			queue_position_to_order(&playlist->queue,
						current_position);
	}

	if (next_order >= 0)
		next_song = queue_get_order(&playlist->queue, next_order);
	else
		next_song = NULL;

	if (prev != NULL && next_song != prev) {
		/* clear the currently queued song */
		pc_cancel();
		playlist->queued = -1;
	}

	if (next_order >= 0) {
		if (next_song != prev)
			playlist_queue_song_order(playlist, next_order);
		else
			playlist->queued = next_order;
	}
}

#ifndef WIN32
enum playlist_result
playlist_append_file(struct playlist *playlist, const char *path, int uid,
		     unsigned *added_id)
{
	int ret;
	struct stat st;
	struct song *song;

	if (uid <= 0)
		/* unauthenticated client */
		return PLAYLIST_RESULT_DENIED;

	ret = stat(path, &st);
	if (ret < 0)
		return PLAYLIST_RESULT_ERRNO;

	if (st.st_uid != (uid_t)uid && (st.st_mode & 0444) != 0444)
		/* client is not owner */
		return PLAYLIST_RESULT_DENIED;

	song = song_file_load(path, NULL);
	if (song == NULL)
		return PLAYLIST_RESULT_NO_SUCH_SONG;

	return addSongToPlaylist(playlist, song, added_id);
}
#endif

static struct song *
song_by_url(const char *url)
{
	struct song *song;

	song = db_get_song(url);
	if (song != NULL)
		return song;

	if (uri_has_scheme(url))
		return song_remote_new(url);

	return NULL;
}

enum playlist_result
addToPlaylist(struct playlist *playlist, const char *url, unsigned *added_id)
{
	struct song *song;

	g_debug("add to playlist: %s", url);

	song = song_by_url(url);
	if (song == NULL)
		return PLAYLIST_RESULT_NO_SUCH_SONG;

	return addSongToPlaylist(playlist, song, added_id);
}

enum playlist_result
addSongToPlaylist(struct playlist *playlist,
		  struct song *song, unsigned *added_id)
{
	const struct song *queued;
	unsigned id;

	if (queue_is_full(&playlist->queue))
		return PLAYLIST_RESULT_TOO_LARGE;

	queued = playlist_get_queued_song(playlist);

	id = queue_append(&playlist->queue, song);

	if (playlist->queue.random) {
		/* shuffle the new song into the list of remaining
		   songs to play */

		unsigned start;
		if (playlist->queued >= 0)
			start = playlist->queued + 1;
		else
			start = playlist->current + 1;
		if (start < queue_length(&playlist->queue))
			queue_shuffle_order_last(&playlist->queue, start,
						 queue_length(&playlist->queue));
	}

	incrPlaylistVersion(playlist);

	playlist_update_queued_song(playlist, queued);

	if (added_id)
		*added_id = id;

	return PLAYLIST_RESULT_SUCCESS;
}

enum playlist_result
swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2)
{
	const struct song *queued;

	if (!queue_valid_position(&playlist->queue, song1) ||
	    !queue_valid_position(&playlist->queue, song2))
		return PLAYLIST_RESULT_BAD_RANGE;

	queued = playlist_get_queued_song(playlist);

	queue_swap(&playlist->queue, song1, song2);

	if (playlist->queue.random) {
		/* update the queue order, so that playlist->current
		   still points to the current song order */

		queue_swap_order(&playlist->queue,
				 queue_position_to_order(&playlist->queue,
							 song1),
				 queue_position_to_order(&playlist->queue,
							 song2));
	} else {
		/* correct the "current" song order */

		if (playlist->current == (int)song1)
			playlist->current = song2;
		else if (playlist->current == (int)song2)
			playlist->current = song1;
	}

	incrPlaylistVersion(playlist);

	playlist_update_queued_song(playlist, queued);

	return PLAYLIST_RESULT_SUCCESS;
}

enum playlist_result
swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2)
{
	int song1 = queue_id_to_position(&playlist->queue, id1);
	int song2 = queue_id_to_position(&playlist->queue, id2);

	if (song1 < 0 || song2 < 0)
		return PLAYLIST_RESULT_NO_SUCH_SONG;

	return swapSongsInPlaylist(playlist, song1, song2);
}

enum playlist_result
deleteFromPlaylist(struct playlist *playlist, unsigned song)
{
	const struct song *queued;
	unsigned songOrder;

	if (song >= queue_length(&playlist->queue))
		return PLAYLIST_RESULT_BAD_RANGE;

	queued = playlist_get_queued_song(playlist);

	songOrder = queue_position_to_order(&playlist->queue, song);

	if (playlist->playing && playlist->current == (int)songOrder) {
		bool paused = getPlayerState() == PLAYER_STATE_PAUSE;

		/* the current song is going to be deleted: stop the player */

		playerWait();
		playlist->playing = false;

		/* see which song is going to be played instead */

		playlist->current = queue_next_order(&playlist->queue,
						     playlist->current);
		if (playlist->current == (int)songOrder)
			playlist->current = -1;

		if (playlist->current >= 0 && !paused)
			/* play the song after the deleted one */
			playPlaylistOrderNumber(playlist, playlist->current);
		else
			/* no songs left to play, stop playback
			   completely */
			stopPlaylist(playlist);

		queued = NULL;
	}

	/* now do it: remove the song */

	if (!song_in_database(queue_get(&playlist->queue, song)))
		pc_song_deleted(queue_get(&playlist->queue, song));

	queue_delete(&playlist->queue, song);

	incrPlaylistVersion(playlist);

	/* update the "current" and "queued" variables */

	if (playlist->current > (int)songOrder) {
		playlist->current--;
	}

	playlist_update_queued_song(playlist, queued);

	return PLAYLIST_RESULT_SUCCESS;
}

enum playlist_result
deleteFromPlaylistById(struct playlist *playlist, unsigned id)
{
	int song = queue_id_to_position(&playlist->queue, id);
	if (song < 0)
		return PLAYLIST_RESULT_NO_SUCH_SONG;

	return deleteFromPlaylist(playlist, song);
}

void
deleteASongFromPlaylist(struct playlist *playlist, const struct song *song)
{
	for (int i = queue_length(&playlist->queue) - 1; i >= 0; --i)
		if (song == queue_get(&playlist->queue, i))
			deleteFromPlaylist(playlist, i);

	pc_song_deleted(song);
}

void
playPlaylistOrderNumber(struct playlist *playlist, int orderNum)
{
	struct song *song;
	char *uri;

	playlist->playing = true;
	playlist->queued = -1;

	song = queue_get_order(&playlist->queue, orderNum);

	uri = song_get_uri(song);
	g_debug("playlist: play %i:\"%s\"", orderNum, uri);
	g_free(uri);

	playerPlay(song);
	playlist->current = orderNum;
}

static void
playPlaylistIfPlayerStopped(struct playlist *playlist);

/**
 * This is the "PLAYLIST" event handler.  It is invoked by the player
 * thread whenever it requests a new queued song, or when it exits.
 */
void syncPlayerAndPlaylist(struct playlist *playlist)
{
	if (!playlist->playing)
		/* this event has reached us out of sync: we aren't
		   playing anymore; ignore the event */
		return;

	if (getPlayerState() == PLAYER_STATE_STOP)
		/* the player thread has stopped: check if playback
		   should be restarted with the next song.  That can
		   happen if the playlist isn't filling the queue fast
		   enough */
		playPlaylistIfPlayerStopped(playlist);
	else {
		/* check if the player thread has already started
		   playing the queued song */
		syncPlaylistWithQueue(playlist);

		/* make sure the queued song is always set (if
		   possible) */
		if (pc.next_song == NULL)
			playlist_update_queued_song(playlist, NULL);
	}
}

/**
 * The player has stopped for some reason.  Check the error, and
 * decide whether to re-start playback
 */
static void
playPlaylistIfPlayerStopped(struct playlist *playlist)
{
	enum player_error error;

	assert(playlist->playing);
	assert(getPlayerState() == PLAYER_STATE_STOP);

	error = getPlayerError();
	if (error == PLAYER_ERROR_NOERROR)
		playlist->error_count = 0;
	else
		++playlist->error_count;

	if ((playlist->stop_on_error && error != PLAYER_ERROR_NOERROR) ||
	    error == PLAYER_ERROR_AUDIO || error == PLAYER_ERROR_SYSTEM ||
	    playlist->error_count >= queue_length(&playlist->queue))
		/* too many errors, or critical error: stop
		   playback */
		stopPlaylist(playlist);
	else
		/* continue playback at the next song */
		nextSongInPlaylist(playlist);
}

bool
getPlaylistRepeatStatus(struct playlist *playlist)
{
	return playlist->queue.repeat;
}

bool
getPlaylistRandomStatus(struct playlist *playlist)
{
	return playlist->queue.random;
}

void setPlaylistRepeatStatus(struct playlist *playlist, bool status)
{
	if (status == playlist->queue.repeat)
		return;

	playlist->queue.repeat = status;

	/* if the last song is currently being played, the "next song"
	   might change when repeat mode is toggled */
	playlist_update_queued_song(playlist,
				    playlist_get_queued_song(playlist));

	idle_add(IDLE_OPTIONS);
}

enum playlist_result
moveSongInPlaylist(struct playlist *playlist, unsigned from, int to)
{
	const struct song *queued;
	int currentSong;

	if (!queue_valid_position(&playlist->queue, from))
		return PLAYLIST_RESULT_BAD_RANGE;

	if ((to >= 0 && to >= (int)queue_length(&playlist->queue)) ||
	    (to < 0 && abs(to) > (int)queue_length(&playlist->queue)))
		return PLAYLIST_RESULT_BAD_RANGE;

	if ((int)from == to) /* no-op */
		return PLAYLIST_RESULT_SUCCESS;

	queued = playlist_get_queued_song(playlist);

	/*
	 * (to < 0) => move to offset from current song
	 * (-playlist.length == to) => move to position BEFORE current song
	 */
	currentSong = playlist->current >= 0
		? (int)queue_order_to_position(&playlist->queue,
					      playlist->current)
		: -1;
	if (to < 0 && playlist->current >= 0) {
		if ((unsigned)currentSong == from)
			/* no-op, can't be moved to offset of itself */
			return PLAYLIST_RESULT_SUCCESS;
		to = (currentSong + abs(to)) % queue_length(&playlist->queue);
	}

	queue_move(&playlist->queue, from, to);

	if (!playlist->queue.random) {
		/* update current/queued */
		if (playlist->current == (int)from)
			playlist->current = to;
		else if (playlist->current > (int)from &&
			 playlist->current <= to) {
			playlist->current--;
		} else if (playlist->current >= to &&
			   playlist->current < (int)from) {
			playlist->current++;
		}
	}

	incrPlaylistVersion(playlist);

	playlist_update_queued_song(playlist, queued);

	return PLAYLIST_RESULT_SUCCESS;
}

enum playlist_result
moveSongInPlaylistById(struct playlist *playlist, unsigned id1, int to)
{
	int song = queue_id_to_position(&playlist->queue, id1);
	if (song < 0)
		return PLAYLIST_RESULT_NO_SUCH_SONG;

	return moveSongInPlaylist(playlist, song, to);
}

static void orderPlaylist(struct playlist *playlist)
{
	if (playlist->current >= 0)
		/* update playlist.current, order==position now */
		playlist->current = queue_order_to_position(&playlist->queue,
							    playlist->current);

	queue_restore_order(&playlist->queue);
}

void setPlaylistRandomStatus(struct playlist *playlist, bool status)
{
	const struct song *queued;

	if (status == playlist->queue.random)
		return;

	queued = playlist_get_queued_song(playlist);

	playlist->queue.random = status;

	if (playlist->queue.random) {
		/* shuffle the queue order, but preserve
		   playlist->current */

		int current_position = playlist->current >= 0
			? (int)queue_order_to_position(&playlist->queue,
						       playlist->current)
			: -1;

		queue_shuffle_order(&playlist->queue);

		if (current_position >= 0) {
			/* make sure the current song is the first in
			   the order list, so the whole rest of the
			   playlist is played after that */
			unsigned current_order =
				queue_position_to_order(&playlist->queue,
							current_position);
			queue_swap_order(&playlist->queue, 0, current_order);
			playlist->current = 0;
		}
	} else
		orderPlaylist(playlist);

	playlist_update_queued_song(playlist, queued);

	idle_add(IDLE_OPTIONS);
}

void shufflePlaylist(struct playlist *playlist)
{
	const struct song *queued;
	unsigned i;

	if (queue_length(&playlist->queue) <= 1)
		return;

	queued = playlist_get_queued_song(playlist);

	if (playlist->playing) {
		if (playlist->current >= 0)
			/* put current playing song first */
			queue_swap(&playlist->queue, 0,
				   queue_order_to_position(&playlist->queue,
							   playlist->current));

		if (playlist->queue.random) {
			playlist->current =
				queue_position_to_order(&playlist->queue, 0);
		} else
			playlist->current = 0;

		/* start shuffle after the current song */
		i = 1;
	} else {
		/* no playback currently: shuffle everything, and
		   reset playlist->current */

		i = 0;
		playlist->current = -1;
	}

	/* shuffle the rest of the list */
	queue_shuffle_range(&playlist->queue, i,
			    queue_length(&playlist->queue));

	incrPlaylistVersion(playlist);

	playlist_update_queued_song(playlist, queued);
}

int getPlaylistCurrentSong(struct playlist *playlist)
{
	if (playlist->current >= 0)
		return queue_order_to_position(&playlist->queue,
					       playlist->current);

	return -1;
}

unsigned long
getPlaylistVersion(struct playlist *playlist)
{
	return playlist->queue.version;
}

int
getPlaylistLength(struct playlist *playlist)
{
	return queue_length(&playlist->queue);
}

unsigned
getPlaylistSongId(struct playlist *playlist, unsigned song)
{
	return queue_position_to_id(&playlist->queue, song);
}