So, hier der erste Entwurf. Das Playlist Item in einer eigenen Klasse zu Kapseln, finde ich eine gute Idee:
Und hier die zum Bild gehörige Playlist.h
Playlist.hpp
#pragma once
#include <WString.h>
#include <stdlib.h>
/// @brief Object representing a single entry in the playlist
struct MediaItem {
const String uri {String()}; //< playable uri of the entry
struct Metadata {
String title {String()}; //< title of the track
String artist {String()}; //< artist
String albumTitle {String()}; //< album title
String albumArtist {String()}; //< album artist, use artist if not set
String displayTitle {String()}; //< overrides title if set
size_t trackNumber; //< track number _in the album_
size_t totalTrackCount; //< total number of track _in the album_
uint8_t releaseYear; //< year of the song/album release
uint8_t releaseMonth; //< month of the song/album release
uint8_t releaseDay; //< day of the song/album release
// embedded artwork
String artworkMimeType {String()}; //< mime string for the album artwork
std::vector<uint8_t> artworkData {std::vector<uint8_t>()}; //< byte array of the artwork
String artworkUri {String()}; //< uri pointing to the external artwork
Metadata() = default;
/// @brief Compare operator overload for MediaMetadata, returns true if lhs==rhs
friend bool operator==(Metadata const &lhs, Metadata const &rhs) {
return lhs.title == rhs.title && lhs.artist == rhs.artist && lhs.albumTitle == rhs.albumTitle
&& lhs.albumArtist == rhs.albumArtist && lhs.displayTitle == rhs.displayTitle
&& lhs.trackNumber == rhs.trackNumber && lhs.totalTrackCount == rhs.totalTrackCount
&& lhs.releaseYear == rhs.releaseYear && lhs.releaseMonth == rhs.releaseMonth
&& lhs.releaseDay == rhs.releaseDay && lhs.artworkMimeType == rhs.artworkMimeType
&& lhs.artworkData == rhs.artworkData && lhs.artworkUri == rhs.artworkUri;
}
};
std::unique_ptr<Metadata> metadata {nullptr}; //< optional metadata for the entry
/// @brief Compare operator overload for MediaItem, returns true if lhs==rhs
friend bool operator==(MediaItem const &lhs, MediaItem const &rhs) {
const auto comparePointer = [](const std::unique_ptr<Metadata> &lhs, const std::unique_ptr<Metadata> &rhs) -> bool {
if (lhs == rhs) {
return true;
}
if (lhs && rhs) {
return *lhs == *rhs;
}
return false;
};
return lhs.uri == rhs.uri && comparePointer(lhs.metadata, rhs.metadata);
}
MediaItem() = default;
MediaItem(const String &uri)
: uri(uri) { }
~MediaItem() = default;
};
/// @brief Interface class representing a playlist
class Playlist {
public:
/**
* @brief signature for the compare function
* @param a First element for the compare
* @param a Second element for the compare
* @return true if the expression a<b allpies (so if the first element is *less* than the second)
*/
using CompareFunc = std::function<bool(const MediaItem &a, const MediaItem &b)>;
Playlist() = default;
virtual ~Playlist() = default;
/**
* @brief get the status of the playlist
* @return true If the playlist has at least 1 playable entry
* @return false If the playlist is invalid
*/
virtual bool isValid() const { return false; }
/**
* @brief Allow explicit bool conversions, like when calling `if(playlist)`
* @see isValid()
* @return A bool conversion representing the status of the playlist
*/
explicit operator bool() const { return isValid(); }
/**
* @brief Get the number of entries in the playlist
* @return size_t The number of MediaItem elemenets in the underlying container
*/
virtual size_t size() const = 0;
/**
* @brief Get the element at index
* @param idx The queried index
* @return const MediaItem the data at the index
*/
virtual const MediaItem &at(size_t idx) const = 0;
/**
* @brief Add an item at the end of the container
* @param item The new item ot be added
* @return const MediaItem the data at the index
*/
virtual void addMediaItem(const MediaItem &&item) = 0;
/**
* @brief Add an item at the end of the container
* @param item The new item ot be added
* @param idx after which entry it'll be added
* @return const MediaItem the data at the index
*/
virtual void addMediaItem(const MediaItem &&item, int idx) = 0;
/**
* @brief Sort the underlying container according to the supplied sort functions
* @param comp The compare function to use, defaults to strcmp between the two uri objects
*/
virtual void sort(CompareFunc comp = [](const MediaItem &a, const MediaItem &b) -> bool { return a.uri < b.uri; }) { }
/**
* @brief Randomize the underlying entries
*/
virtual void shuffle() { }
/**
* @brief Array opertor override for item access
* @param idx the queried index
* @return const MediaItem& Reference to the MediaItem at the index
*/
const MediaItem &operator[](size_t idx) const { return at(idx); };
};
Alle Funktionen in Playlist sind virtual (d.h. können bzw. müssen) von der eigentlichen Implementation bereit gestellt werden. Ich muss mal tief in mich gehen, ob und welche gemeinsamen Funktionen in der Playlist Sinn machen und diese dann in die Klasse schieben.
Über die PlaylistFlags & PlaylistType habe ich mir noch keine genauen Gedanken gemacht (flags könnten zB Repeate Track, Repeate Playlist, etc sein, type zB Files, Webstream, Audiobook, etc.)
Das MediaItem hält aktuell eine URI/Pfad zu dem Objekt und optional ein Pointer auf die Metadaten (mit den Feldern sind die Metadaten leer aktuell 150 byte). Abstraktion habe ich (noch) nicht vorgesehen, können wir überlegen, ob und wo hier eine Virtualisierung Sinn macht (zB. definitiv den Destruktor, damit man überhaupt ableiten kann).
Ob die ID3-Tags schon vor dem Abspielen ausgelesen werden können habe ich noch nicht geprüft, würde uns aber sicherlich das Leben leichter machen (zB könnten wir den Album Cover vorab aus der MP3 laden, wenn wir PSRAM zur Verfügung haben).
Wie gesagt, ein erster schneller Entwurf. Wie immer, Ideen willkommen 