373 lines
7.4 KiB
C++
373 lines
7.4 KiB
C++
/*
|
|
* Copyright 2013 (c) Anna Schumaker.
|
|
*/
|
|
#include <callback.h>
|
|
#include <library.h>
|
|
#include <playqueue.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <algorithm>
|
|
#include <sstream>
|
|
|
|
#define O_MINUTES (60)
|
|
#define O_HOURS (60 * O_MINUTES)
|
|
#define O_DAYS (24 * O_HOURS)
|
|
|
|
Playqueue :: Playqueue()
|
|
: flags(0), cur(-1), length(0)
|
|
{
|
|
}
|
|
|
|
Playqueue :: Playqueue(playqueue_flags f)
|
|
: flags(f), cur(-1), length(0)
|
|
{
|
|
}
|
|
|
|
Playqueue :: ~Playqueue()
|
|
{
|
|
}
|
|
|
|
void Playqueue :: write(File &f)
|
|
{
|
|
f << flags << " " << tracks.size();
|
|
for (unsigned int i = 0; i < tracks.size(); i++)
|
|
f << " " << tracks[i];
|
|
}
|
|
|
|
void Playqueue :: read(File &f)
|
|
{
|
|
unsigned int n;
|
|
f >> flags >> n;
|
|
tracks.resize(n);
|
|
for (unsigned int i = 0; i < n; i++)
|
|
f >> tracks[i];
|
|
}
|
|
|
|
void Playqueue :: set_flag(playqueue_flags f)
|
|
{
|
|
flags |= f;
|
|
get_callbacks()->on_queue_changed();
|
|
}
|
|
|
|
void Playqueue :: unset_flag(playqueue_flags f)
|
|
{
|
|
flags &= ~f;
|
|
get_callbacks()->on_queue_changed();
|
|
}
|
|
|
|
const unsigned int Playqueue :: get_flags()
|
|
{
|
|
return flags;
|
|
}
|
|
|
|
unsigned int Playqueue :: get_length()
|
|
{
|
|
return length;
|
|
}
|
|
|
|
static inline void add_duration(std::stringstream &ss, unsigned int dur,
|
|
unsigned int remaining, const std::string &field)
|
|
{
|
|
if (dur > 0) {
|
|
ss << dur << " " << field;
|
|
if (dur > 1)
|
|
ss << "s";
|
|
if (remaining > 0)
|
|
ss << ", ";
|
|
}
|
|
}
|
|
|
|
std::string Playqueue :: get_length_str()
|
|
{
|
|
std::stringstream ss;
|
|
unsigned int len = length;
|
|
|
|
unsigned int days = len / O_DAYS;
|
|
len -= days * O_DAYS;
|
|
add_duration(ss, days, len, "day");
|
|
|
|
unsigned int hours = len / O_HOURS;
|
|
len -= hours *O_HOURS;
|
|
add_duration(ss, hours, len, "hour");
|
|
unsigned int mins = len / O_MINUTES;
|
|
add_duration(ss, mins, len, "minute");
|
|
unsigned int secs = len - (mins * O_MINUTES);
|
|
add_duration(ss, secs, 0, "second");
|
|
|
|
return ss.str();
|
|
}
|
|
|
|
/*
|
|
* Returns:
|
|
* 0: lhs == rhs
|
|
* < 0: lhs < rhs
|
|
* > 0: lhs > rhs
|
|
*/
|
|
static inline int compare_uint(unsigned int a, unsigned int b)
|
|
{
|
|
if (a == b)
|
|
return 0;
|
|
if (a < b)
|
|
return -1;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Returns:
|
|
* 0: lhs == rhs
|
|
* < 0: lhs < rhs, or rhs is empty
|
|
* > 0: lhs > rhs, or lhs is empty
|
|
*/
|
|
static inline int compare_string(const std::string &a, const std::string &b)
|
|
{
|
|
if (a.size() == 0)
|
|
return 1;
|
|
else if (b.size() == 0)
|
|
return -1;
|
|
return a.compare(b);
|
|
}
|
|
|
|
/*
|
|
* std::string.compare() returns
|
|
* 0: Strings are equal
|
|
* < 0: a < b
|
|
* > 0: a > b
|
|
*/
|
|
static inline int track_compare(library :: Song &lhs, library :: Song &rhs,
|
|
sort_t field)
|
|
{
|
|
switch (field) {
|
|
case SORT_ARTIST:
|
|
return compare_string(lhs.artist->key_lower, rhs.artist->key_lower);
|
|
case SORT_ALBUM:
|
|
return compare_string(lhs.album->name_lower, rhs.album->name_lower);
|
|
case SORT_COUNT:
|
|
return compare_uint(lhs.track->track, rhs.track->track);
|
|
case SORT_GENRE:
|
|
return compare_string(lhs.genre->key_lower, rhs.genre->key_lower);
|
|
case SORT_LENGTH:
|
|
return compare_uint(lhs.track->length, rhs.track->length);
|
|
case SORT_PLAYED:
|
|
return compare_uint(lhs.track->play_count, rhs.track->play_count);
|
|
case SORT_TITLE:
|
|
return compare_string(lhs.track->title_lower, rhs.track->title_lower);
|
|
case SORT_TRACK:
|
|
return compare_uint(lhs.track->track, rhs.track->track);
|
|
default: //case SORT_YEAR
|
|
return compare_uint(lhs.album->year, rhs.album->year);
|
|
}
|
|
}
|
|
|
|
static bool track_less_than(library :: Song &lhs, library :: Song &rhs,
|
|
std::list<sort_info> &order)
|
|
{
|
|
std::list<sort_info>::iterator it;
|
|
int res;
|
|
|
|
for (it = order.begin(); it != order.end(); it++) {
|
|
if (it->ascending == true)
|
|
res = track_compare(lhs, rhs, it->field);
|
|
else
|
|
res = track_compare(rhs, lhs, it->field);
|
|
if (res != 0)
|
|
return res < 0;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
unsigned int Playqueue :: add_sorted(unsigned int track_id, library :: Song &rhs)
|
|
{
|
|
library :: Song lhs;
|
|
unsigned int cur, start = 0, end = (tracks.size() - 1);
|
|
|
|
if (tracks.size() == 0) {
|
|
tracks.push_back(track_id);
|
|
return 0;
|
|
}
|
|
|
|
while (end - start > 1) {
|
|
cur = start + ((end - start) / 2);
|
|
library :: lookup(tracks[cur], &lhs);
|
|
if (track_less_than(lhs, rhs, sort_order)) {
|
|
//if (end - start == 1)
|
|
// cur = end;
|
|
start = cur;
|
|
} else
|
|
end = cur;
|
|
}
|
|
|
|
if (end - start == 1) {
|
|
cur = end;
|
|
library :: lookup(tracks[cur], &lhs);
|
|
if (track_less_than(lhs, rhs, sort_order))
|
|
cur++;
|
|
}
|
|
|
|
tracks.insert(tracks.begin() + cur, track_id);
|
|
return cur;
|
|
}
|
|
|
|
unsigned int Playqueue :: add(unsigned int track_id)
|
|
{
|
|
unsigned int id = tracks.size();
|
|
library :: Song song;
|
|
library :: lookup(track_id, &song);
|
|
|
|
if (sort_order.size() > 0)
|
|
add_sorted(track_id, song);
|
|
else
|
|
tracks.push_back(track_id);
|
|
|
|
length += song.track->length;
|
|
get_callbacks()->on_queue_track_add(this, id);
|
|
if (!(flags & PQ_DISABLE_CHANGED_SIZE))
|
|
get_callbacks()->on_queue_changed();
|
|
return id;
|
|
}
|
|
|
|
unsigned int Playqueue :: add_front(unsigned int track_id)
|
|
{
|
|
library :: Song song;
|
|
tracks.insert(tracks.begin(), track_id);
|
|
|
|
library :: lookup(track_id, &song);
|
|
length += song.track->length;
|
|
get_callbacks()->on_queue_track_add(this, 0);
|
|
if (!(flags & PQ_DISABLE_CHANGED_SIZE))
|
|
get_callbacks()->on_queue_changed();
|
|
return 0;
|
|
}
|
|
|
|
void Playqueue :: del(unsigned int plist_id)
|
|
{
|
|
library :: Song song;
|
|
unsigned int track_id = tracks[plist_id];
|
|
|
|
tracks.erase(tracks.begin() + plist_id);
|
|
library :: lookup(track_id, &song);
|
|
length -= song.track->length;
|
|
get_callbacks()->on_queue_track_del(this, plist_id);
|
|
if (!(flags & PQ_DISABLE_CHANGED_SIZE))
|
|
get_callbacks()->on_queue_changed();
|
|
}
|
|
|
|
void Playqueue :: del_track(unsigned int track_id)
|
|
{
|
|
unsigned int i = 0;
|
|
while (i < tracks.size()) {
|
|
if (tracks[i] == track_id)
|
|
del(i);
|
|
else
|
|
i++;
|
|
}
|
|
}
|
|
|
|
unsigned int Playqueue :: size()
|
|
{
|
|
return tracks.size();
|
|
}
|
|
|
|
|
|
/* Sorting function */
|
|
class SortTracks {
|
|
private:
|
|
std::list<sort_info> fields;
|
|
public:
|
|
SortTracks(std::list<sort_info> f) : fields(f) {}
|
|
bool operator()(unsigned int a, unsigned int b)
|
|
{
|
|
library::Song lhs, rhs;
|
|
library :: lookup(a, &lhs);
|
|
library :: lookup(b, &rhs);
|
|
return track_less_than(lhs, rhs, fields);
|
|
}
|
|
};
|
|
|
|
void Playqueue :: _add_sort(sort_t field)
|
|
{
|
|
struct sort_info info;
|
|
std::list<sort_info>::iterator it;
|
|
|
|
/* Is field already in the sort_order? */
|
|
for (it = sort_order.begin(); it != sort_order.end(); it++) {
|
|
if (it->field == field) {
|
|
it->ascending = !it->ascending;
|
|
return;
|
|
}
|
|
}
|
|
|
|
info.field = field;
|
|
info.ascending = true;
|
|
sort_order.push_back(info);
|
|
if (sort_order.size() >= 4)
|
|
sort_order.erase(sort_order.begin());
|
|
}
|
|
|
|
void Playqueue :: add_sort(sort_t field)
|
|
{
|
|
if (flags & PQ_NEVER_SORT)
|
|
return;
|
|
|
|
_add_sort(field);
|
|
std::stable_sort(tracks.begin(), tracks.end(), SortTracks(sort_order));
|
|
|
|
for (unsigned int i = 0; i < tracks.size(); i++)
|
|
get_callbacks()->on_queue_track_changed(this, i);
|
|
get_callbacks()->on_queue_changed();
|
|
}
|
|
|
|
void Playqueue :: reset_sort(sort_t field)
|
|
{
|
|
if (flags & PQ_NEVER_SORT)
|
|
return;
|
|
sort_order.clear();
|
|
add_sort(field);
|
|
}
|
|
|
|
std::list<sort_info> &Playqueue :: get_sort_order()
|
|
{
|
|
return sort_order;
|
|
}
|
|
|
|
unsigned int Playqueue :: operator[](unsigned int i)
|
|
{
|
|
return tracks[i];
|
|
}
|
|
|
|
unsigned int Playqueue :: next()
|
|
{
|
|
unsigned int res;
|
|
|
|
if (tracks.size() == 0)
|
|
throw -E_EXIST;
|
|
else if (tracks.size() == 1)
|
|
cur = 0;
|
|
else if (flags & PQ_RANDOM)
|
|
cur += rand() % (tracks.size() / 2) + 1;
|
|
else
|
|
cur++;
|
|
|
|
if (cur >= tracks.size())
|
|
cur -= tracks.size();
|
|
|
|
res = tracks[cur];
|
|
if (!(flags & PQ_REPEAT)) {
|
|
del(cur);
|
|
cur--;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void Playqueue :: reset_cur()
|
|
{
|
|
cur = 0;
|
|
}
|
|
|
|
#ifdef CONFIG_TEST
|
|
void Playqueue :: reset()
|
|
{
|
|
tracks.clear();
|
|
reset_cur();
|
|
}
|
|
#endif /* CONFIG_TEST */
|