Playlist*: move to queue/

This commit is contained in:
Max Kellermann
2014-02-27 17:12:42 +01:00
parent 681e012fb5
commit 809b89b5af
17 changed files with 14 additions and 14 deletions

333
src/queue/Playlist.cxx Normal file
View File

@@ -0,0 +1,333 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "Playlist.hxx"
#include "PlaylistError.hxx"
#include "PlayerControl.hxx"
#include "DetachedSong.hxx"
#include "Idle.hxx"
#include "Log.hxx"
#include <assert.h>
void
playlist::TagModified(DetachedSong &&song)
{
if (!playing)
return;
assert(current >= 0);
DetachedSong &current_song = queue.GetOrder(current);
if (song.IsSame(current_song))
current_song.MoveTagFrom(std::move(song));
queue.ModifyAtOrder(current);
queue.IncrementVersion();
idle_add(IDLE_PLAYLIST);
}
/**
* Queue a song, addressed by its order number.
*/
static void
playlist_queue_song_order(playlist &playlist, PlayerControl &pc,
unsigned order)
{
assert(playlist.queue.IsValidOrder(order));
playlist.queued = order;
const DetachedSong &song = playlist.queue.GetOrder(order);
FormatDebug(playlist_domain, "queue song %i:\"%s\"",
playlist.queued, song.GetURI());
pc.EnqueueSong(new DetachedSong(song));
}
/**
* Called if the player thread has started playing the "queued" song.
*/
static void
playlist_song_started(playlist &playlist, PlayerControl &pc)
{
assert(pc.next_song == nullptr);
assert(playlist.queued >= -1);
/* queued song has started: copy queued to current,
and notify the clients */
int current = playlist.current;
playlist.current = playlist.queued;
playlist.queued = -1;
if(playlist.queue.consume)
playlist.DeleteOrder(pc, current);
idle_add(IDLE_PLAYER);
}
const DetachedSong *
playlist::GetQueuedSong() const
{
return playing && queued >= 0
? &queue.GetOrder(queued)
: nullptr;
}
void
playlist::UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev)
{
if (!playing)
return;
assert(!queue.IsEmpty());
assert((queued < 0) == (prev == nullptr));
const int next_order = current >= 0
? queue.GetNextOrder(current)
: 0;
if (next_order == 0 && queue.random && !queue.single) {
/* shuffle the song order again, so we get a different
order each time the playlist is played
completely */
const unsigned current_position =
queue.OrderToPosition(current);
queue.ShuffleOrder();
/* make sure that the current still points to
the current song, after the song order has been
shuffled */
current = queue.PositionToOrder(current_position);
}
const DetachedSong *const next_song = next_order >= 0
? &queue.GetOrder(next_order)
: nullptr;
if (prev != nullptr && next_song != prev) {
/* clear the currently queued song */
pc.Cancel();
queued = -1;
}
if (next_order >= 0) {
if (next_song != prev)
playlist_queue_song_order(*this, pc, next_order);
else
queued = next_order;
}
}
void
playlist::PlayOrder(PlayerControl &pc, int order)
{
playing = true;
queued = -1;
const DetachedSong &song = queue.GetOrder(order);
FormatDebug(playlist_domain, "play %i:\"%s\"", order, song.GetURI());
pc.Play(new DetachedSong(song));
current = order;
}
static void
playlist_resume_playback(playlist &playlist, PlayerControl &pc);
void
playlist::SyncWithPlayer(PlayerControl &pc)
{
if (!playing)
/* this event has reached us out of sync: we aren't
playing anymore; ignore the event */
return;
pc.Lock();
const PlayerState pc_state = pc.GetState();
const DetachedSong *pc_next_song = pc.next_song;
pc.Unlock();
if (pc_state == PlayerState::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 */
playlist_resume_playback(*this, pc);
else {
/* check if the player thread has already started
playing the queued song */
if (pc_next_song == nullptr && queued != -1)
playlist_song_started(*this, pc);
pc.Lock();
pc_next_song = pc.next_song;
pc.Unlock();
/* make sure the queued song is always set (if
possible) */
if (pc_next_song == nullptr && queued < 0)
UpdateQueuedSong(pc, nullptr);
}
}
/**
* The player has stopped for some reason. Check the error, and
* decide whether to re-start playback
*/
static void
playlist_resume_playback(playlist &playlist, PlayerControl &pc)
{
assert(playlist.playing);
assert(pc.GetState() == PlayerState::STOP);
const auto error = pc.GetErrorType();
if (error == PlayerError::NONE)
playlist.error_count = 0;
else
++playlist.error_count;
if ((playlist.stop_on_error && error != PlayerError::NONE) ||
error == PlayerError::OUTPUT ||
playlist.error_count >= playlist.queue.GetLength())
/* too many errors, or critical error: stop
playback */
playlist.Stop(pc);
else
/* continue playback at the next song */
playlist.PlayNext(pc);
}
void
playlist::SetRepeat(PlayerControl &pc, bool status)
{
if (status == queue.repeat)
return;
queue.repeat = status;
pc.SetBorderPause(queue.single && !queue.repeat);
/* if the last song is currently being played, the "next song"
might change when repeat mode is toggled */
UpdateQueuedSong(pc, GetQueuedSong());
idle_add(IDLE_OPTIONS);
}
static void
playlist_order(playlist &playlist)
{
if (playlist.current >= 0)
/* update playlist.current, order==position now */
playlist.current = playlist.queue.OrderToPosition(playlist.current);
playlist.queue.RestoreOrder();
}
void
playlist::SetSingle(PlayerControl &pc, bool status)
{
if (status == queue.single)
return;
queue.single = status;
pc.SetBorderPause(queue.single && !queue.repeat);
/* if the last song is currently being played, the "next song"
might change when single mode is toggled */
UpdateQueuedSong(pc, GetQueuedSong());
idle_add(IDLE_OPTIONS);
}
void
playlist::SetConsume(bool status)
{
if (status == queue.consume)
return;
queue.consume = status;
idle_add(IDLE_OPTIONS);
}
void
playlist::SetRandom(PlayerControl &pc, bool status)
{
if (status == queue.random)
return;
const DetachedSong *const queued_song = GetQueuedSong();
queue.random = status;
if (queue.random) {
/* shuffle the queue order, but preserve current */
const int current_position = GetCurrentPosition();
queue.ShuffleOrder();
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.PositionToOrder(current_position);
queue.SwapOrders(0, current_order);
current = 0;
} else
current = -1;
} else
playlist_order(*this);
UpdateQueuedSong(pc, queued_song);
idle_add(IDLE_OPTIONS);
}
int
playlist::GetCurrentPosition() const
{
return current >= 0
? queue.OrderToPosition(current)
: -1;
}
int
playlist::GetNextPosition() const
{
if (current < 0)
return -1;
if (queue.single && queue.repeat)
return queue.OrderToPosition(current);
else if (queue.IsValidOrder(current + 1))
return queue.OrderToPosition(current + 1);
else if (queue.repeat)
return queue.OrderToPosition(0);
return -1;
}

264
src/queue/Playlist.hxx Normal file
View File

@@ -0,0 +1,264 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_PLAYLIST_HXX
#define MPD_PLAYLIST_HXX
#include "queue/Queue.hxx"
#include "PlaylistError.hxx"
enum TagType : uint8_t;
struct PlayerControl;
class DetachedSong;
class Database;
class Error;
class SongLoader;
struct playlist {
/**
* The song queue - it contains the "real" playlist.
*/
struct Queue queue;
/**
* This value is true if the player is currently playing (or
* should be playing).
*/
bool playing;
/**
* If true, then any error is fatal; if false, MPD will
* attempt to play the next song on non-fatal errors. During
* seeking, this flag is set.
*/
bool stop_on_error;
/**
* Number of errors since playback was started. If this
* number exceeds the length of the playlist, MPD gives up,
* because all songs have been tried.
*/
unsigned error_count;
/**
* The "current song pointer". This is the song which is
* played when we get the "play" command. It is also the song
* which is currently being played.
*/
int current;
/**
* The "next" song to be played, when the current one
* finishes. The decoder thread may start decoding and
* buffering it, while the "current" song is still playing.
*
* This variable is only valid if #playing is true.
*/
int queued;
playlist(unsigned max_length)
:queue(max_length), playing(false), current(-1), queued(-1) {
}
~playlist() {
}
uint32_t GetVersion() const {
return queue.version;
}
unsigned GetLength() const {
return queue.GetLength();
}
unsigned PositionToId(unsigned position) const {
return queue.PositionToId(position);
}
gcc_pure
int GetCurrentPosition() const;
gcc_pure
int GetNextPosition() const;
/**
* Returns the song object which is currently queued. Returns
* none if there is none (yet?) or if MPD isn't playing.
*/
gcc_pure
const DetachedSong *GetQueuedSong() const;
/**
* 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 SyncWithPlayer(PlayerControl &pc);
protected:
/**
* Called by all editing methods after a modification.
* Updates the queue version and emits #IDLE_PLAYLIST.
*/
void OnModified();
/**
* Updates the "queued song". Calculates the next song
* according to the current one (if MPD isn't playing, it
* takes the first song), and queues this song. Clears the
* old queued song if there was one.
*
* @param prev the song which was previously queued, as
* determined by playlist_get_queued_song()
*/
void UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev);
public:
void Clear(PlayerControl &pc);
/**
* A tag in the play queue has been modified by the player
* thread. Apply the given song's tag to the current song if
* the song matches.
*/
void TagModified(DetachedSong &&song);
#ifdef ENABLE_DATABASE
/**
* The database has been modified. Pull all updates.
*/
void DatabaseModified(const Database &db);
#endif
PlaylistResult AppendSong(PlayerControl &pc,
DetachedSong &&song,
unsigned *added_id=nullptr);
PlaylistResult AppendURI(PlayerControl &pc,
const SongLoader &loader,
const char *uri_utf8,
unsigned *added_id=nullptr);
protected:
void DeleteInternal(PlayerControl &pc,
unsigned song, const DetachedSong **queued_p);
public:
PlaylistResult DeletePosition(PlayerControl &pc,
unsigned position);
PlaylistResult DeleteOrder(PlayerControl &pc,
unsigned order) {
return DeletePosition(pc, queue.OrderToPosition(order));
}
PlaylistResult DeleteId(PlayerControl &pc, unsigned id);
/**
* Deletes a range of songs from the playlist.
*
* @param start the position of the first song to delete
* @param end the position after the last song to delete
*/
PlaylistResult DeleteRange(PlayerControl &pc,
unsigned start, unsigned end);
void DeleteSong(PlayerControl &pc, const char *uri);
void Shuffle(PlayerControl &pc, unsigned start, unsigned end);
PlaylistResult MoveRange(PlayerControl &pc,
unsigned start, unsigned end, int to);
PlaylistResult MoveId(PlayerControl &pc, unsigned id, int to);
PlaylistResult SwapPositions(PlayerControl &pc,
unsigned song1, unsigned song2);
PlaylistResult SwapIds(PlayerControl &pc,
unsigned id1, unsigned id2);
PlaylistResult SetPriorityRange(PlayerControl &pc,
unsigned start_position,
unsigned end_position,
uint8_t priority);
PlaylistResult SetPriorityId(PlayerControl &pc,
unsigned song_id, uint8_t priority);
bool AddSongIdTag(unsigned id, TagType tag_type, const char *value,
Error &error);
bool ClearSongIdTag(unsigned id, TagType tag_type, Error &error);
void Stop(PlayerControl &pc);
PlaylistResult PlayPosition(PlayerControl &pc, int position);
void PlayOrder(PlayerControl &pc, int order);
PlaylistResult PlayId(PlayerControl &pc, int id);
void PlayNext(PlayerControl &pc);
void PlayPrevious(PlayerControl &pc);
PlaylistResult SeekSongPosition(PlayerControl &pc,
unsigned song_position,
float seek_time);
PlaylistResult SeekSongId(PlayerControl &pc,
unsigned song_id, float seek_time);
/**
* Seek within the current song. Fails if MPD is not currently
* playing.
*
* @param time the time in seconds
* @param relative if true, then the specified time is relative to the
* current position
*/
PlaylistResult SeekCurrent(PlayerControl &pc,
float seek_time, bool relative);
bool GetRepeat() const {
return queue.repeat;
}
void SetRepeat(PlayerControl &pc, bool new_value);
bool GetRandom() const {
return queue.random;
}
void SetRandom(PlayerControl &pc, bool new_value);
bool GetSingle() const {
return queue.single;
}
void SetSingle(PlayerControl &pc, bool new_value);
bool GetConsume() const {
return queue.consume;
}
void SetConsume(bool new_value);
};
#endif

View File

@@ -0,0 +1,260 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
* Functions for controlling playback on the playlist level.
*
*/
#include "config.h"
#include "Playlist.hxx"
#include "PlaylistError.hxx"
#include "PlayerControl.hxx"
#include "DetachedSong.hxx"
#include "Log.hxx"
void
playlist::Stop(PlayerControl &pc)
{
if (!playing)
return;
assert(current >= 0);
FormatDebug(playlist_domain, "stop");
pc.Stop();
queued = -1;
playing = false;
if (queue.random) {
/* shuffle the playlist, so the next playback will
result in a new random order */
unsigned current_position = queue.OrderToPosition(current);
queue.ShuffleOrder();
/* make sure that "current" stays valid, and the next
"play" command plays the same song again */
current = queue.PositionToOrder(current_position);
}
}
PlaylistResult
playlist::PlayPosition(PlayerControl &pc, int song)
{
pc.ClearError();
unsigned i = song;
if (song == -1) {
/* play any song ("current" song, or the first song */
if (queue.IsEmpty())
return PlaylistResult::SUCCESS;
if (playing) {
/* already playing: unpause playback, just in
case it was paused, and return */
pc.SetPause(false);
return PlaylistResult::SUCCESS;
}
/* select a song: "current" song, or the first one */
i = current >= 0
? current
: 0;
} else if (!queue.IsValidPosition(song))
return PlaylistResult::BAD_RANGE;
if (queue.random) {
if (song >= 0)
/* "i" is currently the song position (which
would be equal to the order number in
no-random mode); convert it to a order
number, because random mode is enabled */
i = queue.PositionToOrder(song);
if (!playing)
current = 0;
/* swap the new song with the previous "current" one,
so playback continues as planned */
queue.SwapOrders(i, current);
i = current;
}
stop_on_error = false;
error_count = 0;
PlayOrder(pc, i);
return PlaylistResult::SUCCESS;
}
PlaylistResult
playlist::PlayId(PlayerControl &pc, int id)
{
if (id == -1)
return PlayPosition(pc, id);
int song = queue.IdToPosition(id);
if (song < 0)
return PlaylistResult::NO_SUCH_SONG;
return PlayPosition(pc, song);
}
void
playlist::PlayNext(PlayerControl &pc)
{
if (!playing)
return;
assert(!queue.IsEmpty());
assert(queue.IsValidOrder(current));
const int old_current = current;
stop_on_error = false;
/* determine the next song from the queue's order list */
const int next_order = queue.GetNextOrder(current);
if (next_order < 0) {
/* no song after this one: stop playback */
Stop(pc);
/* reset "current song" */
current = -1;
}
else
{
if (next_order == 0 && queue.random) {
/* The queue told us that the next song is the first
song. This means we are in repeat mode. Shuffle
the queue order, so this time, the user hears the
songs in a different than before */
assert(queue.repeat);
queue.ShuffleOrder();
/* note that current and queued are
now invalid, but playlist_play_order() will
discard them anyway */
}
PlayOrder(pc, next_order);
}
/* Consume mode removes each played songs. */
if (queue.consume)
DeleteOrder(pc, old_current);
}
void
playlist::PlayPrevious(PlayerControl &pc)
{
if (!playing)
return;
assert(!queue.IsEmpty());
int order;
if (current > 0) {
/* play the preceding song */
order = current - 1;
} else if (queue.repeat) {
/* play the last song in "repeat" mode */
order = queue.GetLength() - 1;
} else {
/* re-start playing the current song if it's
the first one */
order = current;
}
PlayOrder(pc, order);
}
PlaylistResult
playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time)
{
if (!queue.IsValidPosition(song))
return PlaylistResult::BAD_RANGE;
const DetachedSong *queued_song = GetQueuedSong();
unsigned i = queue.random
? queue.PositionToOrder(song)
: song;
pc.ClearError();
stop_on_error = true;
error_count = 0;
if (!playing || (unsigned)current != i) {
/* seeking is not within the current song - prepare
song change */
playing = true;
current = i;
queued_song = nullptr;
}
if (!pc.Seek(new DetachedSong(queue.GetOrder(i)), seek_time)) {
UpdateQueuedSong(pc, queued_song);
return PlaylistResult::NOT_PLAYING;
}
queued = -1;
UpdateQueuedSong(pc, nullptr);
return PlaylistResult::SUCCESS;
}
PlaylistResult
playlist::SeekSongId(PlayerControl &pc, unsigned id, float seek_time)
{
int song = queue.IdToPosition(id);
if (song < 0)
return PlaylistResult::NO_SUCH_SONG;
return SeekSongPosition(pc, song, seek_time);
}
PlaylistResult
playlist::SeekCurrent(PlayerControl &pc, float seek_time, bool relative)
{
if (!playing)
return PlaylistResult::NOT_PLAYING;
if (relative) {
const auto status = pc.GetStatus();
if (status.state != PlayerState::PLAY &&
status.state != PlayerState::PAUSE)
return PlaylistResult::NOT_PLAYING;
seek_time += (int)status.elapsed_time;
}
if (seek_time < 0)
seek_time = 0;
return SeekSongPosition(pc, current, seek_time);
}

410
src/queue/PlaylistEdit.cxx Normal file
View File

@@ -0,0 +1,410 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
* Functions for editing the playlist (adding, removing, reordering
* songs in the queue).
*
*/
#include "config.h"
#include "Playlist.hxx"
#include "PlaylistError.hxx"
#include "PlayerControl.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
#include "DetachedSong.hxx"
#include "SongLoader.hxx"
#include "Idle.hxx"
#include "Log.hxx"
#include <stdlib.h>
void
playlist::OnModified()
{
queue.IncrementVersion();
idle_add(IDLE_PLAYLIST);
}
void
playlist::Clear(PlayerControl &pc)
{
Stop(pc);
queue.Clear();
current = -1;
OnModified();
}
PlaylistResult
playlist::AppendSong(PlayerControl &pc,
DetachedSong &&song, unsigned *added_id)
{
unsigned id;
if (queue.IsFull())
return PlaylistResult::TOO_LARGE;
const DetachedSong *const queued_song = GetQueuedSong();
id = queue.Append(std::move(song), 0);
if (queue.random) {
/* shuffle the new song into the list of remaning
songs to play */
unsigned start;
if (queued >= 0)
start = queued + 1;
else
start = current + 1;
if (start < queue.GetLength())
queue.ShuffleOrderLast(start, queue.GetLength());
}
UpdateQueuedSong(pc, queued_song);
OnModified();
if (added_id)
*added_id = id;
return PlaylistResult::SUCCESS;
}
PlaylistResult
playlist::AppendURI(PlayerControl &pc,
const SongLoader &loader,
const char *uri, unsigned *added_id)
{
FormatDebug(playlist_domain, "add to playlist: %s", uri);
Error error;
DetachedSong *song = loader.LoadSong(uri, error);
if (song == nullptr) {
// TODO: return the Error
LogError(error);
return error.IsDomain(playlist_domain)
? PlaylistResult(error.GetCode())
: PlaylistResult::NO_SUCH_SONG;
}
PlaylistResult result = AppendSong(pc, std::move(*song), added_id);
delete song;
return result;
}
PlaylistResult
playlist::SwapPositions(PlayerControl &pc, unsigned song1, unsigned song2)
{
if (!queue.IsValidPosition(song1) || !queue.IsValidPosition(song2))
return PlaylistResult::BAD_RANGE;
const DetachedSong *const queued_song = GetQueuedSong();
queue.SwapPositions(song1, song2);
if (queue.random) {
/* update the queue order, so that current
still points to the current song order */
queue.SwapOrders(queue.PositionToOrder(song1),
queue.PositionToOrder(song2));
} else {
/* correct the "current" song order */
if (current == (int)song1)
current = song2;
else if (current == (int)song2)
current = song1;
}
UpdateQueuedSong(pc, queued_song);
OnModified();
return PlaylistResult::SUCCESS;
}
PlaylistResult
playlist::SwapIds(PlayerControl &pc, unsigned id1, unsigned id2)
{
int song1 = queue.IdToPosition(id1);
int song2 = queue.IdToPosition(id2);
if (song1 < 0 || song2 < 0)
return PlaylistResult::NO_SUCH_SONG;
return SwapPositions(pc, song1, song2);
}
PlaylistResult
playlist::SetPriorityRange(PlayerControl &pc,
unsigned start, unsigned end,
uint8_t priority)
{
if (start >= GetLength())
return PlaylistResult::BAD_RANGE;
if (end > GetLength())
end = GetLength();
if (start >= end)
return PlaylistResult::SUCCESS;
/* remember "current" and "queued" */
const int current_position = GetCurrentPosition();
const DetachedSong *const queued_song = GetQueuedSong();
/* apply the priority changes */
queue.SetPriorityRange(start, end, priority, current);
/* restore "current" and choose a new "queued" */
if (current_position >= 0)
current = queue.PositionToOrder(current_position);
UpdateQueuedSong(pc, queued_song);
OnModified();
return PlaylistResult::SUCCESS;
}
PlaylistResult
playlist::SetPriorityId(PlayerControl &pc,
unsigned song_id, uint8_t priority)
{
int song_position = queue.IdToPosition(song_id);
if (song_position < 0)
return PlaylistResult::NO_SUCH_SONG;
return SetPriorityRange(pc, song_position, song_position + 1,
priority);
}
void
playlist::DeleteInternal(PlayerControl &pc,
unsigned song, const DetachedSong **queued_p)
{
assert(song < GetLength());
unsigned songOrder = queue.PositionToOrder(song);
if (playing && current == (int)songOrder) {
const bool paused = pc.GetState() == PlayerState::PAUSE;
/* the current song is going to be deleted: stop the player */
pc.Stop();
playing = false;
/* see which song is going to be played instead */
current = queue.GetNextOrder(current);
if (current == (int)songOrder)
current = -1;
if (current >= 0 && !paused)
/* play the song after the deleted one */
PlayOrder(pc, current);
else
/* no songs left to play, stop playback
completely */
Stop(pc);
*queued_p = nullptr;
} else if (current == (int)songOrder)
/* there's a "current song" but we're not playing
currently - clear "current" */
current = -1;
/* now do it: remove the song */
queue.DeletePosition(song);
/* update the "current" and "queued" variables */
if (current > (int)songOrder)
current--;
}
PlaylistResult
playlist::DeletePosition(PlayerControl &pc, unsigned song)
{
if (song >= queue.GetLength())
return PlaylistResult::BAD_RANGE;
const DetachedSong *queued_song = GetQueuedSong();
DeleteInternal(pc, song, &queued_song);
UpdateQueuedSong(pc, queued_song);
OnModified();
return PlaylistResult::SUCCESS;
}
PlaylistResult
playlist::DeleteRange(PlayerControl &pc, unsigned start, unsigned end)
{
if (start >= queue.GetLength())
return PlaylistResult::BAD_RANGE;
if (end > queue.GetLength())
end = queue.GetLength();
if (start >= end)
return PlaylistResult::SUCCESS;
const DetachedSong *queued_song = GetQueuedSong();
do {
DeleteInternal(pc, --end, &queued_song);
} while (end != start);
UpdateQueuedSong(pc, queued_song);
OnModified();
return PlaylistResult::SUCCESS;
}
PlaylistResult
playlist::DeleteId(PlayerControl &pc, unsigned id)
{
int song = queue.IdToPosition(id);
if (song < 0)
return PlaylistResult::NO_SUCH_SONG;
return DeletePosition(pc, song);
}
void
playlist::DeleteSong(PlayerControl &pc, const char *uri)
{
for (int i = queue.GetLength() - 1; i >= 0; --i)
if (queue.Get(i).IsURI(uri))
DeletePosition(pc, i);
}
PlaylistResult
playlist::MoveRange(PlayerControl &pc, unsigned start, unsigned end, int to)
{
if (!queue.IsValidPosition(start) || !queue.IsValidPosition(end - 1))
return PlaylistResult::BAD_RANGE;
if ((to >= 0 && to + end - start - 1 >= GetLength()) ||
(to < 0 && unsigned(abs(to)) > GetLength()))
return PlaylistResult::BAD_RANGE;
if ((int)start == to)
/* nothing happens */
return PlaylistResult::SUCCESS;
const DetachedSong *const queued_song = GetQueuedSong();
/*
* (to < 0) => move to offset from current song
* (-playlist.length == to) => move to position BEFORE current song
*/
const int currentSong = GetCurrentPosition();
if (to < 0) {
if (currentSong < 0)
/* can't move relative to current song,
because there is no current song */
return PlaylistResult::BAD_RANGE;
if (start <= (unsigned)currentSong && (unsigned)currentSong < end)
/* no-op, can't be moved to offset of itself */
return PlaylistResult::SUCCESS;
to = (currentSong + abs(to)) % GetLength();
if (start < (unsigned)to)
to--;
}
queue.MoveRange(start, end, to);
if (!queue.random) {
/* update current/queued */
if ((int)start <= current && (unsigned)current < end)
current += to - start;
else if (current >= (int)end && current <= to)
current -= end - start;
else if (current >= to && current < (int)start)
current += end - start;
}
UpdateQueuedSong(pc, queued_song);
OnModified();
return PlaylistResult::SUCCESS;
}
PlaylistResult
playlist::MoveId(PlayerControl &pc, unsigned id1, int to)
{
int song = queue.IdToPosition(id1);
if (song < 0)
return PlaylistResult::NO_SUCH_SONG;
return MoveRange(pc, song, song + 1, to);
}
void
playlist::Shuffle(PlayerControl &pc, unsigned start, unsigned end)
{
if (end > GetLength())
/* correct the "end" offset */
end = GetLength();
if (start + 1 >= end)
/* needs at least two entries. */
return;
const DetachedSong *const queued_song = GetQueuedSong();
if (playing && current >= 0) {
unsigned current_position = queue.OrderToPosition(current);
if (current_position >= start && current_position < end) {
/* put current playing song first */
queue.SwapPositions(start, current_position);
if (queue.random) {
current = queue.PositionToOrder(start);
} else
current = start;
/* start shuffle after the current song */
start++;
}
} else {
/* no playback currently: reset current */
current = -1;
}
queue.ShuffleRange(start, end);
UpdateQueuedSong(pc, queued_song);
OnModified();
}

245
src/queue/PlaylistState.cxx Normal file
View File

@@ -0,0 +1,245 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
* Saving and loading the playlist to/from the state file.
*
*/
#include "config.h"
#include "PlaylistState.hxx"
#include "PlaylistError.hxx"
#include "Playlist.hxx"
#include "queue/QueueSave.hxx"
#include "fs/TextFile.hxx"
#include "PlayerControl.hxx"
#include "config/ConfigGlobal.hxx"
#include "config/ConfigOption.hxx"
#include "fs/Limits.hxx"
#include "util/CharUtil.hxx"
#include "util/StringUtil.hxx"
#include "Log.hxx"
#include <string.h>
#include <stdlib.h>
#define PLAYLIST_STATE_FILE_STATE "state: "
#define PLAYLIST_STATE_FILE_RANDOM "random: "
#define PLAYLIST_STATE_FILE_REPEAT "repeat: "
#define PLAYLIST_STATE_FILE_SINGLE "single: "
#define PLAYLIST_STATE_FILE_CONSUME "consume: "
#define PLAYLIST_STATE_FILE_CURRENT "current: "
#define PLAYLIST_STATE_FILE_TIME "time: "
#define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: "
#define PLAYLIST_STATE_FILE_MIXRAMPDB "mixrampdb: "
#define PLAYLIST_STATE_FILE_MIXRAMPDELAY "mixrampdelay: "
#define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin"
#define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end"
#define PLAYLIST_STATE_FILE_STATE_PLAY "play"
#define PLAYLIST_STATE_FILE_STATE_PAUSE "pause"
#define PLAYLIST_STATE_FILE_STATE_STOP "stop"
#define PLAYLIST_BUFFER_SIZE 2*MPD_PATH_MAX
void
playlist_state_save(FILE *fp, const struct playlist &playlist,
PlayerControl &pc)
{
const auto player_status = pc.GetStatus();
fputs(PLAYLIST_STATE_FILE_STATE, fp);
if (playlist.playing) {
switch (player_status.state) {
case PlayerState::PAUSE:
fputs(PLAYLIST_STATE_FILE_STATE_PAUSE "\n", fp);
break;
default:
fputs(PLAYLIST_STATE_FILE_STATE_PLAY "\n", fp);
}
fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n",
playlist.queue.OrderToPosition(playlist.current));
fprintf(fp, PLAYLIST_STATE_FILE_TIME "%i\n",
(int)player_status.elapsed_time);
} else {
fputs(PLAYLIST_STATE_FILE_STATE_STOP "\n", fp);
if (playlist.current >= 0)
fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n",
playlist.queue.OrderToPosition(playlist.current));
}
fprintf(fp, PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist.queue.random);
fprintf(fp, PLAYLIST_STATE_FILE_REPEAT "%i\n", playlist.queue.repeat);
fprintf(fp, PLAYLIST_STATE_FILE_SINGLE "%i\n", playlist.queue.single);
fprintf(fp, PLAYLIST_STATE_FILE_CONSUME "%i\n",
playlist.queue.consume);
fprintf(fp, PLAYLIST_STATE_FILE_CROSSFADE "%i\n",
(int)pc.GetCrossFade());
fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n",
pc.GetMixRampDb());
fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n",
pc.GetMixRampDelay());
fputs(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n", fp);
queue_save(fp, playlist.queue);
fputs(PLAYLIST_STATE_FILE_PLAYLIST_END "\n", fp);
}
static void
playlist_state_load(TextFile &file, const SongLoader &song_loader,
struct playlist &playlist)
{
const char *line = file.ReadLine();
if (line == nullptr) {
LogWarning(playlist_domain, "No playlist in state file");
return;
}
while (!StringStartsWith(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) {
queue_load_song(file, song_loader, line, playlist.queue);
line = file.ReadLine();
if (line == nullptr) {
LogWarning(playlist_domain,
"'" PLAYLIST_STATE_FILE_PLAYLIST_END
"' not found in state file");
break;
}
}
playlist.queue.IncrementVersion();
}
bool
playlist_state_restore(const char *line, TextFile &file,
const SongLoader &song_loader,
struct playlist &playlist, PlayerControl &pc)
{
int current = -1;
int seek_time = 0;
bool random_mode = false;
if (!StringStartsWith(line, PLAYLIST_STATE_FILE_STATE))
return false;
line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1;
PlayerState state;
if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PLAY) == 0)
state = PlayerState::PLAY;
else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0)
state = PlayerState::PAUSE;
else
state = PlayerState::STOP;
while ((line = file.ReadLine()) != nullptr) {
if (StringStartsWith(line, PLAYLIST_STATE_FILE_TIME)) {
seek_time =
atoi(&(line[strlen(PLAYLIST_STATE_FILE_TIME)]));
} else if (StringStartsWith(line, PLAYLIST_STATE_FILE_REPEAT)) {
playlist.SetRepeat(pc,
strcmp(&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]),
"1") == 0);
} else if (StringStartsWith(line, PLAYLIST_STATE_FILE_SINGLE)) {
playlist.SetSingle(pc,
strcmp(&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]),
"1") == 0);
} else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CONSUME)) {
playlist.SetConsume(strcmp(&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]),
"1") == 0);
} else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CROSSFADE)) {
pc.SetCrossFade(atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE)));
} else if (StringStartsWith(line, PLAYLIST_STATE_FILE_MIXRAMPDB)) {
pc.SetMixRampDb(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB)));
} else if (StringStartsWith(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) {
const char *p = line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY);
/* this check discards "nan" which was used
prior to MPD 0.18 */
if (IsDigitASCII(*p))
pc.SetMixRampDelay(atof(p));
} else if (StringStartsWith(line, PLAYLIST_STATE_FILE_RANDOM)) {
random_mode =
strcmp(line + strlen(PLAYLIST_STATE_FILE_RANDOM),
"1") == 0;
} else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CURRENT)) {
current = atoi(&(line
[strlen
(PLAYLIST_STATE_FILE_CURRENT)]));
} else if (StringStartsWith(line,
PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) {
playlist_state_load(file, song_loader, playlist);
}
}
playlist.SetRandom(pc, random_mode);
if (!playlist.queue.IsEmpty()) {
if (!playlist.queue.IsValidPosition(current))
current = 0;
if (state == PlayerState::PLAY &&
config_get_bool(CONF_RESTORE_PAUSED, false))
/* the user doesn't want MPD to auto-start
playback after startup; fall back to
"pause" */
state = PlayerState::PAUSE;
/* enable all devices for the first time; this must be
called here, after the audio output states were
restored, before playback begins */
if (state != PlayerState::STOP)
pc.UpdateAudio();
if (state == PlayerState::STOP /* && config_option */)
playlist.current = current;
else if (seek_time == 0)
playlist.PlayPosition(pc, current);
else
playlist.SeekSongPosition(pc, current, seek_time);
if (state == PlayerState::PAUSE)
pc.Pause();
}
return true;
}
unsigned
playlist_state_get_hash(const playlist &playlist,
PlayerControl &pc)
{
const auto player_status = pc.GetStatus();
return playlist.queue.version ^
(player_status.state != PlayerState::STOP
? ((int)player_status.elapsed_time << 8)
: 0) ^
(playlist.current >= 0
? (playlist.queue.OrderToPosition(playlist.current) << 16)
: 0) ^
((int)pc.GetCrossFade() << 20) ^
(unsigned(player_status.state) << 24) ^
(playlist.queue.random << 27) ^
(playlist.queue.repeat << 28) ^
(playlist.queue.single << 29) ^
(playlist.queue.consume << 30) ^
(playlist.queue.random << 31);
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
* Saving and loading the playlist to/from the state file.
*
*/
#ifndef MPD_PLAYLIST_STATE_HXX
#define MPD_PLAYLIST_STATE_HXX
#include <stdio.h>
struct playlist;
struct PlayerControl;
class TextFile;
class SongLoader;
void
playlist_state_save(FILE *fp, const playlist &playlist,
PlayerControl &pc);
bool
playlist_state_restore(const char *line, TextFile &file,
const SongLoader &song_loader,
playlist &playlist, PlayerControl &pc);
/**
* Generates a hash number for the current state of the playlist and
* the playback options. This is used by timer_save_state_file() to
* determine whether the state has changed and the state file should
* be saved.
*/
unsigned
playlist_state_get_hash(const playlist &playlist,
PlayerControl &c);
#endif

93
src/queue/PlaylistTag.cxx Normal file
View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
* Functions for editing the playlist (adding, removing, reordering
* songs in the queue).
*
*/
#include "config.h"
#include "Playlist.hxx"
#include "PlaylistError.hxx"
#include "DetachedSong.hxx"
#include "tag/Tag.hxx"
#include "tag/TagBuilder.hxx"
#include "util/Error.hxx"
bool
playlist::AddSongIdTag(unsigned id, TagType tag_type, const char *value,
Error &error)
{
const int position = queue.IdToPosition(id);
if (position < 0) {
error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG),
"No such song");
return false;
}
DetachedSong &song = queue.Get(position);
if (song.IsFile()) {
error.Set(playlist_domain, int(PlaylistResult::DENIED),
"Cannot edit tags of local file");
return false;
}
{
TagBuilder tag(std::move(song.WritableTag()));
tag.AddItem(tag_type, value);
song.SetTag(tag.Commit());
}
queue.ModifyAtPosition(position);
OnModified();
return true;
}
bool
playlist::ClearSongIdTag(unsigned id, TagType tag_type,
Error &error)
{
const int position = queue.IdToPosition(id);
if (position < 0) {
error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG),
"No such song");
return false;
}
DetachedSong &song = queue.Get(position);
if (song.IsFile()) {
error.Set(playlist_domain, int(PlaylistResult::DENIED),
"Cannot edit tags of local file");
return false;
}
{
TagBuilder tag(std::move(song.WritableTag()));
if (tag_type == TAG_NUM_OF_ITEM_TYPES)
tag.RemoveAll();
else
tag.RemoveType(tag_type);
song.SetTag(tag.Commit());
}
queue.ModifyAtPosition(position);
OnModified();
return true;
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "Playlist.hxx"
#include "db/Interface.hxx"
#include "db/LightSong.hxx"
#include "DetachedSong.hxx"
#include "tag/Tag.hxx"
#include "Idle.hxx"
#include "util/Error.hxx"
static bool
UpdatePlaylistSong(const Database &db, DetachedSong &song)
{
if (!song.IsInDatabase() || !song.IsFile())
/* only update Songs instances that are "detached"
from the Database */
return false;
const LightSong *original = db.GetSong(song.GetURI(), IgnoreError());
if (original == nullptr)
/* not found - shouldn't happen, because the update
thread should ensure that all stale Song instances
have been purged */
return false;
if (original->mtime == song.GetLastModified()) {
/* not modified */
db.ReturnSong(original);
return false;
}
song.SetLastModified(original->mtime);
song.SetTag(*original->tag);
db.ReturnSong(original);
return true;
}
void
playlist::DatabaseModified(const Database &db)
{
bool modified = false;
for (unsigned i = 0, n = queue.GetLength(); i != n; ++i) {
if (UpdatePlaylistSong(db, queue.Get(i))) {
queue.ModifyAtPosition(i);
modified = true;
}
}
if (modified) {
queue.IncrementVersion();
idle_add(IDLE_PLAYLIST);
}
}