1 /* the Music Player Daemon (MPD)
2 * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
3 * This project's homepage is: http://www.musicpd.org
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 #include "directory.h"
29 #include "sig_handlers.h"
30 #include "state_file.h"
31 #include "storedPlaylist.h"
36 #include <sys/param.h>
41 #define PLAYLIST_STATE_STOP 0
42 #define PLAYLIST_STATE_PLAY 1
44 #define PLAYLIST_PREV_UNLESS_ELAPSED 10
46 #define PLAYLIST_STATE_FILE_STATE "state: "
47 #define PLAYLIST_STATE_FILE_RANDOM "random: "
48 #define PLAYLIST_STATE_FILE_REPEAT "repeat: "
49 #define PLAYLIST_STATE_FILE_CURRENT "current: "
50 #define PLAYLIST_STATE_FILE_TIME "time: "
51 #define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: "
52 #define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin"
53 #define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end"
55 #define PLAYLIST_STATE_FILE_STATE_PLAY "play"
56 #define PLAYLIST_STATE_FILE_STATE_PAUSE "pause"
57 #define PLAYLIST_STATE_FILE_STATE_STOP "stop"
59 #define PLAYLIST_BUFFER_SIZE 2*MAXPATHLEN
61 #define PLAYLIST_HASH_MULT 4
63 #define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16)
64 #define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS 0
66 static Playlist playlist
;
67 static int playlist_state
= PLAYLIST_STATE_STOP
;
68 static int playlist_max_length
= DEFAULT_PLAYLIST_MAX_LENGTH
;
69 static int playlist_stopOnError
;
70 static int playlist_errorCount
;
71 static int playlist_queueError
;
72 static int playlist_noGoToNext
;
74 int playlist_saveAbsolutePaths
= DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS
;
76 static List
*playlistQueue
;
78 static void swapOrder(int a
, int b
);
79 static int playPlaylistOrderNumber(int fd
, int orderNum
);
80 static void randomizeOrder(int start
, int end
);
81 static void clearPlayerQueue(void);
83 static void incrPlaylistVersion(void)
85 static unsigned long max
= ((mpd_uint32
) 1 << 31) - 1;
87 if (playlist
.version
>= max
) {
90 for (i
= 0; i
< playlist
.length
; i
++) {
91 playlist
.songMod
[i
] = 0;
98 void playlistVersionChange(void)
102 for (i
= 0; i
< playlist
.length
; i
++) {
103 playlist
.songMod
[i
] = playlist
.version
;
106 incrPlaylistVersion();
109 static void incrPlaylistQueueVersion(void)
111 static unsigned long max
= ((mpd_uint32
) 1 << 31) - 1;
112 playlist
.queueversion
++;
113 if (playlist
.queueversion
>= max
)
114 playlist
.queueversion
= 1;
117 static void incrPlaylistCurrent(void)
119 if (playlist
.current
< 0)
122 if (playlist
.current
>= playlist
.length
- 1) {
124 playlist
.current
= 0;
126 playlist
.current
= -1;
131 void initPlaylist(void)
139 playlist
.version
= 1;
140 playlist
.queueversion
= 1;
142 playlist
.queued
= -1;
143 playlist
.current
= -1;
145 param
= getConfigParam(CONF_MAX_PLAYLIST_LENGTH
);
148 playlist_max_length
= strtol(param
->value
, &test
, 10);
150 FATAL("max playlist length \"%s\" is not an integer, "
151 "line %i\n", param
->value
, param
->line
);
155 playlist_saveAbsolutePaths
= getBoolConfigParam(CONF_SAVE_ABSOLUTE_PATHS
);
156 if (playlist_saveAbsolutePaths
== -1)
157 playlist_saveAbsolutePaths
= DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS
;
158 else if (playlist_saveAbsolutePaths
< 0)
161 playlist
.songs
= xmalloc(sizeof(Song
*) * playlist_max_length
);
162 playlist
.songMod
= xmalloc(sizeof(mpd_uint32
) * playlist_max_length
);
163 playlist
.order
= xmalloc(sizeof(int) * playlist_max_length
);
164 playlist
.idToPosition
= xmalloc(sizeof(int) * playlist_max_length
*
166 playlist
.positionToId
= xmalloc(sizeof(int) * playlist_max_length
);
168 memset(playlist
.songs
, 0, sizeof(char *) * playlist_max_length
);
172 for (i
= 0; i
< playlist_max_length
* PLAYLIST_HASH_MULT
; i
++) {
173 playlist
.idToPosition
[i
] = -1;
176 playlistQueue
= makeList(DEFAULT_FREE_DATA_FUNC
, 0);
179 static int getNextId(void)
185 if (cur
>= playlist_max_length
* PLAYLIST_HASH_MULT
) {
188 } while (playlist
.idToPosition
[cur
] != -1);
193 void finishPlaylist(void)
196 for (i
= 0; i
< playlist
.length
; i
++) {
197 if (playlist
.songs
[i
]->type
== SONG_TYPE_URL
) {
198 freeJustSong(playlist
.songs
[i
]);
204 free(playlist
.songs
);
205 playlist
.songs
= NULL
;
206 free(playlist
.songMod
);
207 playlist
.songMod
= NULL
;
208 free(playlist
.order
);
209 playlist
.order
= NULL
;
210 free(playlist
.idToPosition
);
211 playlist
.idToPosition
= NULL
;
212 free(playlist
.positionToId
);
213 playlist
.positionToId
= NULL
;
216 int clearPlaylist(int fd
)
220 if (stopPlaylist(fd
) < 0)
222 clearPlaylistQueue();
224 for (i
= 0; i
< playlist
.length
; i
++) {
225 if (playlist
.songs
[i
]->type
== SONG_TYPE_URL
) {
226 freeJustSong(playlist
.songs
[i
]);
228 playlist
.idToPosition
[playlist
.positionToId
[i
]] = -1;
229 playlist
.songs
[i
] = NULL
;
232 playlist
.current
= -1;
234 incrPlaylistVersion();
239 int clearStoredPlaylist(int fd
, char *utf8file
)
241 removeAllFromStoredPlaylistByPath(fd
, utf8file
);
245 int showPlaylist(int fd
)
249 for (i
= 0; i
< playlist
.length
; i
++) {
250 fdprintf(fd
, "%i:%s\n", i
, getSongUrl(playlist
.songs
[i
]));
256 void savePlaylistState(FILE *fp
)
258 fprintf(fp
, "%s", PLAYLIST_STATE_FILE_STATE
);
259 switch (playlist_state
) {
260 case PLAYLIST_STATE_PLAY
:
261 switch (getPlayerState()) {
262 case PLAYER_STATE_PAUSE
:
263 fprintf(fp
, "%s\n", PLAYLIST_STATE_FILE_STATE_PAUSE
);
266 fprintf(fp
, "%s\n", PLAYLIST_STATE_FILE_STATE_PLAY
);
268 fprintf(fp
, "%s%i\n", PLAYLIST_STATE_FILE_CURRENT
,
269 playlist
.order
[playlist
.current
]);
270 fprintf(fp
, "%s%i\n", PLAYLIST_STATE_FILE_TIME
,
271 getPlayerElapsedTime());
274 fprintf(fp
, "%s\n", PLAYLIST_STATE_FILE_STATE_STOP
);
277 fprintf(fp
, "%s%i\n", PLAYLIST_STATE_FILE_RANDOM
, playlist
.random
);
278 fprintf(fp
, "%s%i\n", PLAYLIST_STATE_FILE_REPEAT
, playlist
.repeat
);
279 fprintf(fp
, "%s%i\n", PLAYLIST_STATE_FILE_CROSSFADE
,
280 (int)(getPlayerCrossFade()));
281 fprintf(fp
, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_BEGIN
);
283 showPlaylist(fileno(fp
));
284 fprintf(fp
, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_END
);
287 static void loadPlaylistFromStateFile(FILE *fp
, char *buffer
,
288 int state
, int current
, int time
)
293 if (!myFgets(buffer
, PLAYLIST_BUFFER_SIZE
, fp
))
295 while (strcmp(buffer
, PLAYLIST_STATE_FILE_PLAYLIST_END
)) {
296 song
= atoi(strtok(buffer
, ":"));
297 if (!(temp
= strtok(NULL
, "")))
299 if (!addToPlaylist(STDERR_FILENO
, temp
, 0) && current
== song
) {
300 if (state
!= PLAYER_STATE_STOP
) {
301 playPlaylist(STDERR_FILENO
,
302 playlist
.length
- 1, 0);
304 if (state
== PLAYER_STATE_PAUSE
) {
305 playerPause(STDERR_FILENO
);
307 if (state
!= PLAYER_STATE_STOP
) {
308 seekSongInPlaylist(STDERR_FILENO
,
309 playlist
.length
- 1, time
);
312 if (!myFgets(buffer
, PLAYLIST_BUFFER_SIZE
, fp
))
317 void readPlaylistState(FILE *fp
)
321 int state
= PLAYER_STATE_STOP
;
322 char buffer
[PLAYLIST_BUFFER_SIZE
];
324 while (myFgets(buffer
, PLAYLIST_BUFFER_SIZE
, fp
)) {
325 if (strncmp(buffer
, PLAYLIST_STATE_FILE_STATE
,
326 strlen(PLAYLIST_STATE_FILE_STATE
)) == 0) {
327 if (strcmp(&(buffer
[strlen(PLAYLIST_STATE_FILE_STATE
)]),
328 PLAYLIST_STATE_FILE_STATE_PLAY
) == 0) {
329 state
= PLAYER_STATE_PLAY
;
332 (&(buffer
[strlen(PLAYLIST_STATE_FILE_STATE
)]),
333 PLAYLIST_STATE_FILE_STATE_PAUSE
)
335 state
= PLAYER_STATE_PAUSE
;
337 } else if (strncmp(buffer
, PLAYLIST_STATE_FILE_TIME
,
338 strlen(PLAYLIST_STATE_FILE_TIME
)) == 0) {
340 atoi(&(buffer
[strlen(PLAYLIST_STATE_FILE_TIME
)]));
343 (buffer
, PLAYLIST_STATE_FILE_REPEAT
,
344 strlen(PLAYLIST_STATE_FILE_REPEAT
)) == 0) {
346 (&(buffer
[strlen(PLAYLIST_STATE_FILE_REPEAT
)]),
348 setPlaylistRepeatStatus(STDERR_FILENO
, 1);
350 setPlaylistRepeatStatus(STDERR_FILENO
, 0);
353 (buffer
, PLAYLIST_STATE_FILE_CROSSFADE
,
354 strlen(PLAYLIST_STATE_FILE_CROSSFADE
)) == 0) {
355 setPlayerCrossFade(atoi
359 (PLAYLIST_STATE_FILE_CROSSFADE
)])));
362 (buffer
, PLAYLIST_STATE_FILE_RANDOM
,
363 strlen(PLAYLIST_STATE_FILE_RANDOM
)) == 0) {
367 [strlen(PLAYLIST_STATE_FILE_RANDOM
)]),
369 setPlaylistRandomStatus(STDERR_FILENO
, 1);
371 setPlaylistRandomStatus(STDERR_FILENO
, 0);
372 } else if (strncmp(buffer
, PLAYLIST_STATE_FILE_CURRENT
,
373 strlen(PLAYLIST_STATE_FILE_CURRENT
))
375 if (strlen(buffer
) ==
376 strlen(PLAYLIST_STATE_FILE_CURRENT
))
378 current
= atoi(&(buffer
380 (PLAYLIST_STATE_FILE_CURRENT
)]));
383 (buffer
, PLAYLIST_STATE_FILE_PLAYLIST_BEGIN
,
384 strlen(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN
)
386 if (state
== PLAYER_STATE_STOP
)
388 loadPlaylistFromStateFile(fp
, buffer
, state
,
394 static void printPlaylistSongInfo(int fd
, int song
)
396 printSongInfo(fd
, playlist
.songs
[song
]);
397 fdprintf(fd
, "Pos: %i\nId: %i\n", song
, playlist
.positionToId
[song
]);
400 int playlistChanges(int fd
, mpd_uint32 version
)
404 for (i
= 0; i
< playlist
.length
; i
++) {
405 if (version
> playlist
.version
||
406 playlist
.songMod
[i
] >= version
||
407 playlist
.songMod
[i
] == 0) {
408 printPlaylistSongInfo(fd
, i
);
415 int playlistChangesPosId(int fd
, mpd_uint32 version
)
419 for (i
= 0; i
< playlist
.length
; i
++) {
420 if (version
> playlist
.version
||
421 playlist
.songMod
[i
] >= version
||
422 playlist
.songMod
[i
] == 0) {
423 fdprintf(fd
, "cpos: %i\nId: %i\n",
424 i
, playlist
.positionToId
[i
]);
431 int playlistInfo(int fd
, int song
)
435 int end
= playlist
.length
;
441 if (song
>= playlist
.length
) {
442 commandError(fd
, ACK_ERROR_NO_EXIST
,
443 "song doesn't exist: \"%i\"", song
);
447 for (i
= begin
; i
< end
; i
++)
448 printPlaylistSongInfo(fd
, i
);
453 # define checkSongId(id) { \
454 if(id < 0 || id >= PLAYLIST_HASH_MULT*playlist_max_length || \
455 playlist.idToPosition[id] == -1 ) \
457 commandError(fd, ACK_ERROR_NO_EXIST, \
458 "song id doesn't exist: \"%i\"", id); \
463 int playlistId(int fd
, int id
)
467 int end
= playlist
.length
;
471 begin
= playlist
.idToPosition
[id
];
475 for (i
= begin
; i
< end
; i
++)
476 printPlaylistSongInfo(fd
, i
);
481 static void swapSongs(int song1
, int song2
)
486 sTemp
= playlist
.songs
[song1
];
487 playlist
.songs
[song1
] = playlist
.songs
[song2
];
488 playlist
.songs
[song2
] = sTemp
;
490 playlist
.songMod
[song1
] = playlist
.version
;
491 playlist
.songMod
[song2
] = playlist
.version
;
493 playlist
.idToPosition
[playlist
.positionToId
[song1
]] = song2
;
494 playlist
.idToPosition
[playlist
.positionToId
[song2
]] = song1
;
496 iTemp
= playlist
.positionToId
[song1
];
497 playlist
.positionToId
[song1
] = playlist
.positionToId
[song2
];
498 playlist
.positionToId
[song2
] = iTemp
;
501 static void queueNextSongInPlaylist(void)
503 if (playlistQueue
->numberOfNodes
!= 0) {
505 /* we need to find where in order[] is first song from queue */
506 for (i
=0;i
< playlist
.length
; i
++)
507 if (playlist
.order
[i
] == playlist
.
508 idToPosition
[*(int *)playlistQueue
->
513 DEBUG("playlist: queue song %i:\"%s\"\n",
516 songs
[playlist
.order
[playlist
.queued
]]));
518 if (queueSong(playlist
.songs
[playlist
.order
[playlist
.queued
]]) <
520 playlist
.queued
= -1;
521 playlist_queueError
= 1;
523 } else if (playlist
.current
< playlist
.length
- 1) {
525 playlist
.queued
= playlist
.current
+ 1;
526 DEBUG("playlist: queue song %i:\"%s\"\n",
529 songs
[playlist
.order
[playlist
.queued
]]));
530 if (queueSong(playlist
.songs
[playlist
.order
[playlist
.queued
]]) <
532 playlist
.queued
= -1;
533 playlist_queueError
= 1;
535 } else if (playlist
.length
&& playlist
.repeat
) {
536 if (playlist
.length
> 1 && playlist
.random
) {
537 randomizeOrder(0, playlist
.length
- 1);
541 DEBUG("playlist: queue song %i:\"%s\"\n",
544 songs
[playlist
.order
[playlist
.queued
]]));
545 if (queueSong(playlist
.songs
[playlist
.order
[playlist
.queued
]]) <
547 playlist
.queued
= -1;
548 playlist_queueError
= 1;
553 static void syncPlaylistWithQueue(int queue
)
555 if (queue
&& getPlayerQueueState() == PLAYER_QUEUE_BLANK
) {
556 queueNextSongInPlaylist();
557 } else if (getPlayerQueueState() == PLAYER_QUEUE_DECODE
) {
558 if (playlist
.queued
!= -1)
559 setQueueState(PLAYER_QUEUE_PLAY
);
561 setQueueState(PLAYER_QUEUE_STOP
);
562 } else if (getPlayerQueueState() == PLAYER_QUEUE_EMPTY
) {
563 setQueueState(PLAYER_QUEUE_BLANK
);
564 if (playlist
.queued
>= 0) {
565 DEBUG("playlist: now playing queued song\n");
566 playlist
.current
= playlist
.queued
;
567 if (playlistQueue
->numberOfNodes
> 0) {
568 deleteFromPlaylistQueueInternal(0);
571 playlist
.queued
= -1;
573 queueNextSongInPlaylist();
577 static void lockPlaylistInteraction(void)
579 if (getPlayerQueueState() == PLAYER_QUEUE_PLAY
||
580 getPlayerQueueState() == PLAYER_QUEUE_FULL
) {
582 syncPlaylistWithQueue(0);
586 static void unlockPlaylistInteraction(void)
591 static void clearPlayerQueue(void)
593 playlist
.queued
= -1;
594 switch (getPlayerQueueState()) {
595 case PLAYER_QUEUE_FULL
:
596 DEBUG("playlist: dequeue song\n");
597 setQueueState(PLAYER_QUEUE_BLANK
);
599 case PLAYER_QUEUE_PLAY
:
600 DEBUG("playlist: stop decoding queued song\n");
601 setQueueState(PLAYER_QUEUE_STOP
);
606 int addToPlaylist(int fd
, char *url
, int printId
)
610 DEBUG("add to playlist: %s\n", url
);
612 if ((song
= getSongFromDB(url
))) {
613 } else if (!(isValidRemoteUtf8Url(url
) &&
614 (song
= newSong(url
, SONG_TYPE_URL
, NULL
)))) {
615 commandError(fd
, ACK_ERROR_NO_EXIST
,
616 "\"%s\" is not in the music db or is "
617 "not a valid url", url
);
621 return addSongToPlaylist(fd
, song
, printId
);
624 int addToStoredPlaylist(int fd
, char *url
, char *utf8file
)
628 DEBUG("add to stored playlist: %s\n", url
);
630 song
= getSongFromDB(url
);
632 appendSongToStoredPlaylistByPath(fd
, utf8file
, song
);
636 if (!isValidRemoteUtf8Url(url
))
639 song
= newSong(url
, SONG_TYPE_URL
, NULL
);
641 appendSongToStoredPlaylistByPath(fd
, utf8file
, song
);
647 commandError(fd
, ACK_ERROR_NO_EXIST
, "\"%s\" is not in the music db"
648 "or is not a valid url", url
);
652 int addSongToPlaylist(int fd
, Song
* song
, int printId
)
656 if (playlist
.length
== playlist_max_length
) {
657 commandError(fd
, ACK_ERROR_PLAYLIST_MAX
,
658 "playlist is at the max size");
662 if (playlist_state
== PLAYLIST_STATE_PLAY
) {
663 if (playlist
.queued
>= 0
664 && playlist
.current
== playlist
.length
- 1) {
665 lockPlaylistInteraction();
667 unlockPlaylistInteraction();
673 playlist
.songs
[playlist
.length
] = song
;
674 playlist
.songMod
[playlist
.length
] = playlist
.version
;
675 playlist
.order
[playlist
.length
] = playlist
.length
;
676 playlist
.positionToId
[playlist
.length
] = id
;
677 playlist
.idToPosition
[playlist
.positionToId
[playlist
.length
]] =
681 if (playlist
.random
) {
684 /*if(playlist_state==PLAYLIST_STATE_STOP) start = 0;
685 else */ if (playlist
.queued
>= 0)
686 start
= playlist
.queued
+ 1;
688 start
= playlist
.current
+ 1;
689 if (start
< playlist
.length
) {
690 swap
= random() % (playlist
.length
- start
);
692 swapOrder(playlist
.length
- 1, swap
);
696 incrPlaylistVersion();
699 fdprintf(fd
, "Id: %i\n", id
);
704 int swapSongsInPlaylist(int fd
, int song1
, int song2
)
707 int currentSong
= -1;
709 if (song1
< 0 || song1
>= playlist
.length
) {
710 commandError(fd
, ACK_ERROR_NO_EXIST
,
711 "song doesn't exist: \"%i\"", song1
);
714 if (song2
< 0 || song2
>= playlist
.length
) {
715 commandError(fd
, ACK_ERROR_NO_EXIST
,
716 "song doesn't exist: \"%i\"", song2
);
720 if (playlist_state
== PLAYLIST_STATE_PLAY
) {
721 if (playlist
.queued
>= 0) {
722 queuedSong
= playlist
.order
[playlist
.queued
];
724 currentSong
= playlist
.order
[playlist
.current
];
726 if (queuedSong
== song1
|| queuedSong
== song2
727 || currentSong
== song1
|| currentSong
== song2
) {
728 lockPlaylistInteraction();
730 unlockPlaylistInteraction();
734 swapSongs(song1
, song2
);
735 if (playlist
.random
) {
739 for (i
= 0; playlist
.order
[i
] != song1
; i
++) {
740 if (playlist
.order
[i
] == song2
)
745 if (playlist
.order
[i
] == song2
)
749 if (playlist
.current
== song1
)
750 playlist
.current
= song2
;
751 else if (playlist
.current
== song2
)
752 playlist
.current
= song1
;
755 incrPlaylistVersion();
760 int swapSongsInPlaylistById(int fd
, int id1
, int id2
)
765 return swapSongsInPlaylist(fd
, playlist
.idToPosition
[id1
],
766 playlist
.idToPosition
[id2
]);
769 #define moveSongFromTo(from, to) { \
770 playlist.idToPosition[playlist.positionToId[from]] = to; \
771 playlist.positionToId[to] = playlist.positionToId[from]; \
772 playlist.songs[to] = playlist.songs[from]; \
773 playlist.songMod[to] = playlist.version; \
776 int deleteFromPlaylist(int fd
, int song
)
782 if (song
< 0 || song
>= playlist
.length
) {
783 commandError(fd
, ACK_ERROR_NO_EXIST
,
784 "song doesn't exist: \"%i\"", song
);
788 /* we need to clear song from queue */
790 qItem
= playlistQueue
->firstNode
;
792 if (playlist
.idToPosition
[*(int *)qItem
->data
] ==
795 qItem
= qItem
->nextNode
;
796 deleteFromPlaylistQueueInternal(i
);
797 /* can be queued multiple times */
801 qItem
= qItem
->nextNode
;
804 if (playlist_state
== PLAYLIST_STATE_PLAY
) {
805 if (playlist
.queued
>= 0
806 && (playlist
.order
[playlist
.queued
] == song
807 || playlist
.order
[playlist
.current
] == song
)) {
808 lockPlaylistInteraction();
810 unlockPlaylistInteraction();
814 if (playlist
.songs
[song
]->type
== SONG_TYPE_URL
) {
815 freeJustSong(playlist
.songs
[song
]);
818 playlist
.idToPosition
[playlist
.positionToId
[song
]] = -1;
820 /* delete song from songs array */
821 for (i
= song
; i
< playlist
.length
- 1; i
++) {
822 moveSongFromTo(i
+ 1, i
);
824 /* now find it in the order array */
825 for (i
= 0; i
< playlist
.length
- 1; i
++) {
826 if (playlist
.order
[i
] == song
)
830 /* delete the entry from the order array */
831 for (; i
< playlist
.length
- 1; i
++)
832 playlist
.order
[i
] = playlist
.order
[i
+ 1];
833 /* readjust values in the order array */
834 for (i
= 0; i
< playlist
.length
- 1; i
++) {
835 if (playlist
.order
[i
] > song
)
838 /* now take care of other misc stuff */
839 playlist
.songs
[playlist
.length
- 1] = NULL
;
842 incrPlaylistVersion();
844 if (playlist_state
!= PLAYLIST_STATE_STOP
845 && playlist
.current
== songOrder
) {
846 /*if(playlist.current>=playlist.length) return playerStop(fd);
847 else return playPlaylistOrderNumber(fd,playlist.current); */
848 playerWait(STDERR_FILENO
);
849 playlist_noGoToNext
= 1;
852 if (playlist
.current
> songOrder
) {
854 } else if (playlist
.current
>= playlist
.length
) {
855 incrPlaylistCurrent();
858 if (playlist
.queued
> songOrder
) {
865 int deleteFromPlaylistById(int fd
, int id
)
869 return deleteFromPlaylist(fd
, playlist
.idToPosition
[id
]);
872 void deleteASongFromPlaylist(Song
* song
)
876 if (NULL
== playlist
.songs
)
879 for (i
= 0; i
< playlist
.length
; i
++) {
880 if (song
== playlist
.songs
[i
]) {
881 deleteFromPlaylist(STDERR_FILENO
, i
);
886 int stopPlaylist(int fd
)
888 DEBUG("playlist: stop\n");
889 if (playerWait(fd
) < 0)
891 playlist
.queued
= -1;
892 playlist_state
= PLAYLIST_STATE_STOP
;
893 playlist_noGoToNext
= 0;
895 randomizeOrder(0, playlist
.length
- 1);
899 static int playPlaylistOrderNumber(int fd
, int orderNum
)
901 if (playerStop(fd
) < 0)
904 playlist_state
= PLAYLIST_STATE_PLAY
;
905 playlist_noGoToNext
= 0;
906 playlist
.queued
= -1;
907 playlist_queueError
= 0;
909 DEBUG("playlist: play %i:\"%s\"\n", orderNum
,
910 getSongUrl(playlist
.songs
[playlist
.order
[orderNum
]]));
912 if (playerPlay(fd
, (playlist
.songs
[playlist
.order
[orderNum
]])) < 0) {
917 playlist
.current
= orderNum
;
919 /* are we playing from queue ? */
920 if (playlistQueue
->numberOfNodes
> 0 &&
921 playlist
.idToPosition
[*(int *)playlistQueue
->
922 firstNode
->data
] == playlist
.order
[orderNum
]) {
923 deleteFromPlaylistQueueInternal(0);
924 queueNextSongInPlaylist();
930 int playNextPlaylistQueue(int fd
, int stopOnError
)
933 if (playlistQueue
->numberOfNodes
== 0)
936 ret
= playPlaylistById(fd
, *(int *)playlistQueue
->firstNode
->data
,
941 int playPlaylist(int fd
, int song
, int stopOnError
)
948 if (playlist
.length
== 0)
951 if (playlist_state
== PLAYLIST_STATE_PLAY
) {
952 return playerSetPause(fd
, 0);
955 if (playlist_state
!= PLAYLIST_STATE_STOP
&&
956 playNextPlaylistQueue(fd
, stopOnError
) == 0) {
960 if (playlist
.current
>= 0 && playlist
.current
< playlist
.length
) {
961 i
= playlist
.current
;
965 } else if (song
< 0 || song
>= playlist
.length
) {
966 commandError(fd
, ACK_ERROR_NO_EXIST
,
967 "song doesn't exist: \"%i\"", song
);
971 if (playlist
.random
) {
972 if (song
== -1 && playlist_state
== PLAYLIST_STATE_PLAY
) {
973 randomizeOrder(0, playlist
.length
- 1);
976 for (i
= 0; song
!= playlist
.order
[i
]; i
++) ;
977 if (playlist_state
== PLAYLIST_STATE_STOP
) {
978 playlist
.current
= 0;
980 swapOrder(i
, playlist
.current
);
981 i
= playlist
.current
;
985 playlist_stopOnError
= stopOnError
;
986 playlist_errorCount
= 0;
988 return playPlaylistOrderNumber(fd
, i
);
991 int playPlaylistById(int fd
, int id
, int stopOnError
)
994 return playPlaylist(fd
, id
, stopOnError
);
999 return playPlaylist(fd
, playlist
.idToPosition
[id
], stopOnError
);
1002 static void syncCurrentPlayerDecodeMetadata(void)
1004 Song
*songPlayer
= playerCurrentDecodeSong();
1011 if (playlist_state
!= PLAYLIST_STATE_PLAY
)
1014 songNum
= playlist
.order
[playlist
.current
];
1015 song
= playlist
.songs
[songNum
];
1017 if (song
->type
== SONG_TYPE_URL
&&
1018 0 == strcmp(getSongUrl(song
), songPlayer
->url
) &&
1019 !mpdTagsAreEqual(song
->tag
, songPlayer
->tag
)) {
1021 freeMpdTag(song
->tag
);
1022 song
->tag
= mpdTagDup(songPlayer
->tag
);
1023 playlist
.songMod
[songNum
] = playlist
.version
;
1024 incrPlaylistVersion();
1028 void syncPlayerAndPlaylist(void)
1030 if (playlist_state
!= PLAYLIST_STATE_PLAY
)
1033 if (getPlayerState() == PLAYER_STATE_STOP
)
1034 playPlaylistIfPlayerStopped();
1036 syncPlaylistWithQueue(!playlist_queueError
);
1038 syncCurrentPlayerDecodeMetadata();
1041 static int currentSongInPlaylist(int fd
)
1043 if (playlist_state
!= PLAYLIST_STATE_PLAY
)
1046 playlist_stopOnError
= 0;
1048 syncPlaylistWithQueue(0);
1050 if (playlist
.current
>= 0 && playlist
.current
< playlist
.length
) {
1051 return playPlaylistOrderNumber(fd
, playlist
.current
);
1053 return stopPlaylist(fd
);
1058 int nextSongInPlaylist(int fd
)
1060 if (playlist_state
!= PLAYLIST_STATE_PLAY
)
1063 syncPlaylistWithQueue(0);
1065 playlist_stopOnError
= 0;
1067 if (playNextPlaylistQueue(fd
, 0) == 0)
1070 if (playlist
.current
< playlist
.length
- 1) {
1071 return playPlaylistOrderNumber(fd
, playlist
.current
+ 1);
1072 } else if (playlist
.length
&& playlist
.repeat
) {
1073 if (playlist
.random
)
1074 randomizeOrder(0, playlist
.length
- 1);
1075 return playPlaylistOrderNumber(fd
, 0);
1077 incrPlaylistCurrent();
1078 return stopPlaylist(fd
);
1084 void playPlaylistIfPlayerStopped(void)
1086 if (getPlayerState() == PLAYER_STATE_STOP
) {
1087 int error
= getPlayerError();
1089 if (error
== PLAYER_ERROR_NOERROR
)
1090 playlist_errorCount
= 0;
1092 playlist_errorCount
++;
1094 if (playlist_state
== PLAYLIST_STATE_PLAY
1095 && ((playlist_stopOnError
&& error
!= PLAYER_ERROR_NOERROR
)
1096 || error
== PLAYER_ERROR_AUDIO
1097 || error
== PLAYER_ERROR_SYSTEM
1098 || playlist_errorCount
>= playlist
.length
)) {
1099 stopPlaylist(STDERR_FILENO
);
1100 } else if (playlist_noGoToNext
)
1101 currentSongInPlaylist(STDERR_FILENO
);
1103 nextSongInPlaylist(STDERR_FILENO
);
1107 int getPlaylistRepeatStatus(void)
1109 return playlist
.repeat
;
1112 int getPlaylistRandomStatus(void)
1114 return playlist
.random
;
1117 int setPlaylistRepeatStatus(int fd
, int status
)
1119 if (status
!= 0 && status
!= 1) {
1120 commandError(fd
, ACK_ERROR_ARG
, "\"%i\" is not 0 or 1", status
);
1124 if (playlist_state
== PLAYLIST_STATE_PLAY
) {
1125 if (playlist
.repeat
&& !status
&& playlist
.queued
== 0) {
1126 lockPlaylistInteraction();
1128 unlockPlaylistInteraction();
1132 playlist
.repeat
= status
;
1137 int moveSongInPlaylist(int fd
, int from
, int to
)
1142 int queuedSong
= -1;
1143 int currentSong
= -1;
1145 if (from
< 0 || from
>= playlist
.length
) {
1146 commandError(fd
, ACK_ERROR_NO_EXIST
,
1147 "song doesn't exist: \"%i\"", from
);
1151 if (to
< 0 || to
>= playlist
.length
) {
1152 commandError(fd
, ACK_ERROR_NO_EXIST
,
1153 "song doesn't exist: \"%i\"", to
);
1157 if (playlist_state
== PLAYLIST_STATE_PLAY
) {
1158 if (playlist
.queued
>= 0) {
1159 queuedSong
= playlist
.order
[playlist
.queued
];
1161 currentSong
= playlist
.order
[playlist
.current
];
1162 if (queuedSong
== from
|| queuedSong
== to
1163 || currentSong
== from
|| currentSong
== to
) {
1164 lockPlaylistInteraction();
1166 unlockPlaylistInteraction();
1170 tmpSong
= playlist
.songs
[from
];
1171 tmpId
= playlist
.positionToId
[from
];
1172 /* move songs to one less in from->to */
1173 for (i
= from
; i
< to
; i
++) {
1174 moveSongFromTo(i
+ 1, i
);
1176 /* move songs to one more in to->from */
1177 for (i
= from
; i
> to
; i
--) {
1178 moveSongFromTo(i
- 1, i
);
1180 /* put song at _to_ */
1181 playlist
.idToPosition
[tmpId
] = to
;
1182 playlist
.positionToId
[to
] = tmpId
;
1183 playlist
.songs
[to
] = tmpSong
;
1184 playlist
.songMod
[to
] = playlist
.version
;
1185 /* now deal with order */
1186 if (playlist
.random
) {
1187 for (i
= 0; i
< playlist
.length
; i
++) {
1188 if (playlist
.order
[i
] > from
&& playlist
.order
[i
] <= to
) {
1189 playlist
.order
[i
]--;
1190 } else if (playlist
.order
[i
] < from
&&
1191 playlist
.order
[i
] >= to
) {
1192 playlist
.order
[i
]++;
1193 } else if (from
== playlist
.order
[i
]) {
1194 playlist
.order
[i
] = to
;
1200 if (playlist
.current
== from
)
1201 playlist
.current
= to
;
1202 else if (playlist
.current
> from
&& playlist
.current
<= to
) {
1204 } else if (playlist
.current
>= to
&& playlist
.current
< from
) {
1208 /* this first if statement isn't necessary since the queue
1209 * would have been cleared out if queued == from */
1210 if (playlist
.queued
== from
)
1211 playlist
.queued
= to
;
1212 else if (playlist
.queued
> from
&& playlist
.queued
<= to
) {
1214 } else if (playlist
.queued
>= to
&& playlist
.queued
< from
) {
1219 incrPlaylistVersion();
1224 int moveSongInPlaylistById(int fd
, int id1
, int to
)
1228 return moveSongInPlaylist(fd
, playlist
.idToPosition
[id1
], to
);
1231 static void orderPlaylist(void)
1235 if (playlist
.current
>= 0 && playlist
.current
< playlist
.length
) {
1236 playlist
.current
= playlist
.order
[playlist
.current
];
1239 if (playlist_state
== PLAYLIST_STATE_PLAY
) {
1240 if (playlist
.queued
>= 0) {
1241 lockPlaylistInteraction();
1243 unlockPlaylistInteraction();
1247 for (i
= 0; i
< playlist
.length
; i
++) {
1248 playlist
.order
[i
] = i
;
1253 static void swapOrder(int a
, int b
)
1255 int bak
= playlist
.order
[a
];
1256 playlist
.order
[a
] = playlist
.order
[b
];
1257 playlist
.order
[b
] = bak
;
1260 static void randomizeOrder(int start
, int end
)
1265 DEBUG("playlist: randomize from %i to %i\n", start
, end
);
1267 if (playlist_state
== PLAYLIST_STATE_PLAY
) {
1268 if (playlist
.queued
>= start
&& playlist
.queued
<= end
) {
1269 lockPlaylistInteraction();
1271 unlockPlaylistInteraction();
1275 for (i
= start
; i
<= end
; i
++) {
1276 ri
= random() % (end
- start
+ 1) + start
;
1277 if (ri
== playlist
.current
)
1278 playlist
.current
= i
;
1279 else if (i
== playlist
.current
)
1280 playlist
.current
= ri
;
1285 int setPlaylistRandomStatus(int fd
, int status
)
1287 int statusWas
= playlist
.random
;
1289 if (status
!= 0 && status
!= 1) {
1290 commandError(fd
, ACK_ERROR_ARG
, "\"%i\" is not 0 or 1", status
);
1294 playlist
.random
= status
;
1296 if (status
!= statusWas
) {
1297 if (playlist
.random
) {
1298 /*if(playlist_state==PLAYLIST_STATE_PLAY) {
1299 randomizeOrder(playlist.current+1,
1302 else */ randomizeOrder(0, playlist
.length
- 1);
1303 if (playlist
.current
>= 0 &&
1304 playlist
.current
< playlist
.length
) {
1305 swapOrder(playlist
.current
, 0);
1306 playlist
.current
= 0;
1315 int previousSongInPlaylist(int fd
)
1317 static time_t lastTime
;
1318 time_t diff
= time(NULL
) - lastTime
;
1322 if (playlist_state
!= PLAYLIST_STATE_PLAY
)
1325 syncPlaylistWithQueue(0);
1327 if (diff
&& getPlayerElapsedTime() > PLAYLIST_PREV_UNLESS_ELAPSED
) {
1328 return playPlaylistOrderNumber(fd
, playlist
.current
);
1330 if (playlist
.current
> 0) {
1331 return playPlaylistOrderNumber(fd
,
1332 playlist
.current
- 1);
1333 } else if (playlist
.repeat
) {
1334 return playPlaylistOrderNumber(fd
, playlist
.length
- 1);
1336 return playPlaylistOrderNumber(fd
, playlist
.current
);
1343 int shufflePlaylist(int fd
)
1348 if (playlist
.length
> 1) {
1349 if (playlist_state
== PLAYLIST_STATE_PLAY
) {
1350 lockPlaylistInteraction();
1352 unlockPlaylistInteraction();
1353 /* put current playing song first */
1354 swapSongs(0, playlist
.order
[playlist
.current
]);
1355 if (playlist
.random
) {
1357 for (j
= 0; 0 != playlist
.order
[j
]; j
++) ;
1358 playlist
.current
= j
;
1360 playlist
.current
= 0;
1364 playlist
.current
= -1;
1366 /* shuffle the rest of the list */
1367 for (; i
< playlist
.length
; i
++) {
1368 ri
= random() % (playlist
.length
- 1) + 1;
1372 incrPlaylistVersion();
1378 int deletePlaylist(int fd
, char *utf8file
)
1380 char *file
= utf8ToFsCharset(utf8file
);
1381 char *rfile
= xmalloc(strlen(file
) + strlen(".") +
1382 strlen(PLAYLIST_FILE_SUFFIX
) + 1);
1385 strcpy(rfile
, file
);
1387 strcat(rfile
, PLAYLIST_FILE_SUFFIX
);
1389 if ((actualFile
= rpp2app(rfile
)) && isPlaylist(actualFile
))
1393 commandError(fd
, ACK_ERROR_NO_EXIST
,
1394 "playlist \"%s\" not found", utf8file
);
1398 if (unlink(actualFile
) < 0) {
1399 commandError(fd
, ACK_ERROR_SYSTEM
,
1400 "problems deleting file");
1407 int savePlaylist(int fd
, char *utf8file
)
1409 StoredPlaylist
*sp
= newStoredPlaylist(utf8file
, fd
, 0);
1413 appendPlaylistToStoredPlaylist(sp
, &playlist
);
1414 if (writeStoredPlaylist(sp
) != 0) {
1415 freeStoredPlaylist(sp
);
1419 freeStoredPlaylist(sp
);
1423 int getPlaylistCurrentSong(void)
1425 if (playlist
.current
>= 0 && playlist
.current
< playlist
.length
) {
1426 return playlist
.order
[playlist
.current
];
1432 unsigned long getPlaylistVersion(void)
1434 return playlist
.version
;
1437 unsigned long getPlaylistQueueVersion(void)
1439 return playlist
.queueversion
;
1442 int getPlaylistLength(void)
1444 return playlist
.length
;
1447 int seekSongInPlaylist(int fd
, int song
, float time
)
1451 if (song
< 0 || song
>= playlist
.length
) {
1452 commandError(fd
, ACK_ERROR_NO_EXIST
,
1453 "song doesn't exist: \"%i\"", song
);
1457 if (playlist
.random
)
1458 for (i
= 0; song
!= playlist
.order
[i
]; i
++) ;
1461 playlist_stopOnError
= 1;
1462 playlist_errorCount
= 0;
1464 if (playlist_state
== PLAYLIST_STATE_PLAY
) {
1465 if (playlist
.queued
>= 0) {
1466 lockPlaylistInteraction();
1468 unlockPlaylistInteraction();
1470 } else if (playPlaylistOrderNumber(fd
, i
) < 0)
1473 if (playlist
.current
!= i
) {
1474 if (playPlaylistOrderNumber(fd
, i
) < 0)
1478 return playerSeek(fd
, playlist
.songs
[playlist
.order
[i
]], time
);
1481 int seekSongInPlaylistById(int fd
, int id
, float time
)
1485 return seekSongInPlaylist(fd
, playlist
.idToPosition
[id
], time
);
1488 int getPlaylistSongId(int song
)
1490 return playlist
.positionToId
[song
];
1493 int PlaylistInfo(int fd
, char *utf8file
, int detail
)
1496 StoredPlaylist
*sp
= loadStoredPlaylist(utf8file
, fd
);
1500 node
= sp
->list
->firstNode
;
1501 while (node
!= NULL
) {
1502 char *temp
= node
->data
;
1506 Song
*song
= getSongFromDB(temp
);
1508 printSongInfo(fd
, song
);
1514 fdprintf(fd
, SONG_FILE
"%s\n", temp
);
1517 node
= node
->nextNode
;
1520 freeStoredPlaylist(sp
);
1524 int loadPlaylist(int fd
, char *utf8file
)
1527 StoredPlaylist
*sp
= loadStoredPlaylist(utf8file
, fd
);
1531 node
= sp
->list
->firstNode
;
1532 while (node
!= NULL
) {
1533 char *temp
= node
->data
;
1534 if ((addToPlaylist(STDERR_FILENO
, temp
, 0)) < 0) {
1535 /* for windows compatibility, convert slashes */
1536 char *temp2
= xstrdup(temp
);
1543 if ((addToPlaylist(STDERR_FILENO
, temp2
, 0)) < 0) {
1544 commandError(fd
, ACK_ERROR_PLAYLIST_LOAD
,
1545 "can't add file \"%s\"", temp2
);
1550 node
= node
->nextNode
;
1553 freeStoredPlaylist(sp
);
1557 void searchForSongsInPlaylist(int fd
, int numItems
, LocateTagItem
* items
)
1560 char **originalNeedles
= xmalloc(numItems
* sizeof(char *));
1562 for (i
= 0; i
< numItems
; i
++) {
1563 originalNeedles
[i
] = items
[i
].needle
;
1564 items
[i
].needle
= strDupToUpper(originalNeedles
[i
]);
1567 for (i
= 0; i
< playlist
.length
; i
++) {
1568 if (strstrSearchTags(playlist
.songs
[i
], numItems
, items
))
1569 printPlaylistSongInfo(fd
, i
);
1572 for (i
= 0; i
< numItems
; i
++) {
1573 free(items
[i
].needle
);
1574 items
[i
].needle
= originalNeedles
[i
];
1577 free(originalNeedles
);
1580 void findSongsInPlaylist(int fd
, int numItems
, LocateTagItem
* items
)
1584 for (i
= 0; i
< playlist
.length
; i
++) {
1585 if (tagItemsFoundAndMatches(playlist
.songs
[i
], numItems
, items
))
1586 printPlaylistSongInfo(fd
, i
);
1590 void clearPlaylistQueue(void)
1592 freeList(playlistQueue
);
1593 playlistQueue
= makeList(DEFAULT_FREE_DATA_FUNC
, 0);
1594 incrPlaylistQueueVersion();
1597 int addToPlaylistQueueById(int fd
, int song
, int toPosition
)
1602 pos
= playlist
.idToPosition
[song
];
1603 if (pos
< 0 || pos
>= playlist
.length
) {
1604 commandError(fd
, ACK_ERROR_NO_EXIST
,
1605 "song doesn't exist: \"%i\"", song
);
1608 if (toPosition
< -1 || toPosition
> playlistQueue
->numberOfNodes
) {
1609 commandError(fd
, ACK_ERROR_ARG
,
1610 "queue position out of range: \"%i\"", toPosition
);
1613 data
= xmalloc(sizeof(int));
1615 if (toPosition
== -1) {
1616 insertInList(playlistQueue
, (char *)1, data
);
1618 prevItem
= getNodeByPosition(playlistQueue
, toPosition
);
1619 if (prevItem
== NULL
) {
1620 insertInList(playlistQueue
, (char *)1, data
);
1622 insertInListBeforeNode(playlistQueue
, prevItem
, -1,
1626 queueNextSongInPlaylist();
1627 incrPlaylistQueueVersion();
1631 int deleteFromPlaylistQueue(int fd
, int song
)
1633 if (song
< 0 || song
>= playlistQueue
->numberOfNodes
) {
1634 commandError(fd
, ACK_ERROR_NO_EXIST
,
1635 "song doesn't exist: \"%i\"", song
);
1639 return deleteFromPlaylistQueueInternal(song
);
1642 int deleteFromPlaylistQueueInternal(int song
)
1646 delItem
= getNodeByPosition(playlistQueue
, song
);
1647 if (delItem
== NULL
)
1650 deleteNodeFromList(playlistQueue
, delItem
);
1652 queueNextSongInPlaylist();
1654 incrPlaylistQueueVersion();
1658 int playlistQueueInfo(int fd
)
1660 ListNode
*cur
= playlistQueue
->firstNode
;
1663 printSongInfo(fd
, playlist
.songs
[playlist
.idToPosition
[*(int *)cur
->data
]]);
1664 fdprintf(fd
, "Pos: %i\nId: %i\n", no
++, *(int *)cur
->data
);
1665 cur
= cur
->nextNode
;