Implement start of Playback Engine, refactored most classes to leverage KotoCartographer, etc.
This commit is contained in:
parent
a77efdb0aa
commit
05d90afc58
25 changed files with 1015 additions and 304 deletions
|
@ -65,7 +65,7 @@ void koto_cartographer_add_playlist(KotoCartographer *self, KotoPlaylist *playli
|
|||
g_object_get(playlist, "uuid", &playlist_uuid, NULL);
|
||||
|
||||
if ((playlist_uuid != NULL) && (!koto_cartographer_has_playlist_by_uuid(self, playlist_uuid))) { // Don't have this album
|
||||
g_hash_table_replace(self->artists, playlist_uuid, playlist);
|
||||
g_hash_table_replace(self->playlists, playlist_uuid, playlist);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,7 +74,7 @@ void koto_cartographer_add_track(KotoCartographer *self, KotoIndexedTrack *track
|
|||
g_object_get(track, "uuid", &track_uuid, NULL);
|
||||
|
||||
if ((track_uuid != NULL) && (!koto_cartographer_has_playlist_by_uuid(self, track_uuid))) { // Don't have this album
|
||||
g_hash_table_replace(self->artists, track_uuid, track);
|
||||
g_hash_table_replace(self->tracks, track_uuid, track);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,9 @@ void close_db() {
|
|||
int create_db_tables() {
|
||||
char *tables_creation_queries = "CREATE TABLE IF NOT EXISTS artists(id string UNIQUE PRIMARY KEY, path string, type int, name string, art_path string);"
|
||||
"CREATE TABLE IF NOT EXISTS albums(id string UNIQUE PRIMARY KEY, path string, artist_id string, name string, art_path string, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"
|
||||
"CREATE TABLE IF NOT EXISTS tracks(id string UNIQUE PRIMARY KEY, path string, type int, artist_id string, album_id string, file_name string, name string, disc int, position int, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);";
|
||||
"CREATE TABLE IF NOT EXISTS tracks(id string UNIQUE PRIMARY KEY, path string, type int, artist_id string, album_id string, file_name string, name string, disc int, position int, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"
|
||||
"CREATE TABLE IF NOT EXISTS playlist_meta(id string UNIQUE PRIMARY KEY, name string, art_path string);"
|
||||
"CREATE TABLE IF NOT EXISTS playlist_tracks(playlist_id string PRIMARY KEY, track_id string, position int, current int, FOREIGN KEY(playlist_id) REFERENCES playlist_meta(id), FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE);";
|
||||
|
||||
gchar *create_tables_errmsg = NULL;
|
||||
int rc = sqlite3_exec(koto_db, tables_creation_queries, 0,0, &create_tables_errmsg);
|
||||
|
|
|
@ -19,23 +19,25 @@
|
|||
#include <magic.h>
|
||||
#include <sqlite3.h>
|
||||
#include <stdio.h>
|
||||
#include "../db/cartographer.h"
|
||||
#include "../playlist/current.h"
|
||||
#include "../playlist/playlist.h"
|
||||
#include "structs.h"
|
||||
#include "koto-utils.h"
|
||||
|
||||
extern KotoCartographer *koto_maps;
|
||||
extern KotoCurrentPlaylist *current_playlist;
|
||||
extern sqlite3 *koto_db;
|
||||
|
||||
struct _KotoIndexedAlbum {
|
||||
GObject parent_instance;
|
||||
KotoIndexedArtist *artist;
|
||||
gchar *uuid;
|
||||
gchar *path;
|
||||
|
||||
gchar *name;
|
||||
gchar *art_path;
|
||||
GHashTable *tracks;
|
||||
gchar *artist_uuid;
|
||||
GList *tracks;
|
||||
|
||||
gboolean has_album_art;
|
||||
gboolean do_initial_index;
|
||||
|
@ -45,12 +47,12 @@ G_DEFINE_TYPE(KotoIndexedAlbum, koto_indexed_album, G_TYPE_OBJECT);
|
|||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_ARTIST,
|
||||
PROP_UUID,
|
||||
PROP_DO_INITIAL_INDEX,
|
||||
PROP_PATH,
|
||||
PROP_ALBUM_NAME,
|
||||
PROP_ART_PATH,
|
||||
PROP_ARTIST_UUID,
|
||||
N_PROPERTIES
|
||||
};
|
||||
|
||||
|
@ -65,14 +67,6 @@ static void koto_indexed_album_class_init(KotoIndexedAlbumClass *c) {
|
|||
gobject_class->set_property = koto_indexed_album_set_property;
|
||||
gobject_class->get_property = koto_indexed_album_get_property;
|
||||
|
||||
props[PROP_ARTIST] = g_param_spec_object(
|
||||
"artist",
|
||||
"Artist associated with Album",
|
||||
"Artist associated with Album",
|
||||
KOTO_TYPE_INDEXED_ARTIST,
|
||||
G_PARAM_CONSTRUCT_ONLY|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
props[PROP_UUID] = g_param_spec_string(
|
||||
"uuid",
|
||||
"UUID to Album in database",
|
||||
|
@ -113,37 +107,36 @@ static void koto_indexed_album_class_init(KotoIndexedAlbumClass *c) {
|
|||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
props[PROP_ARTIST_UUID] = g_param_spec_string(
|
||||
"artist-uuid",
|
||||
"UUID of Artist associated with Album",
|
||||
"UUID of Artist associated with Album",
|
||||
NULL,
|
||||
G_PARAM_CONSTRUCT_ONLY|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
|
||||
}
|
||||
|
||||
static void koto_indexed_album_init(KotoIndexedAlbum *self) {
|
||||
self->has_album_art = FALSE;
|
||||
self->tracks = NULL;
|
||||
}
|
||||
|
||||
void koto_indexed_album_add_file(KotoIndexedAlbum *self, KotoIndexedTrack *track) {
|
||||
void koto_indexed_album_add_track(KotoIndexedAlbum *self, KotoIndexedTrack *track) {
|
||||
if (track == NULL) { // Not a file
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->tracks == NULL) { // No HashTable yet
|
||||
self->tracks = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable
|
||||
gchar *track_uuid;
|
||||
g_object_get(track, "uuid", &track_uuid, NULL);
|
||||
|
||||
if (g_list_index(self->tracks, track_uuid) == -1) {
|
||||
self->tracks = g_list_insert_sorted_with_data(self->tracks, track_uuid, koto_indexed_album_sort_tracks, NULL);
|
||||
}
|
||||
|
||||
gchar *track_name;
|
||||
g_object_get(track, "parsed-name", &track_name, NULL);
|
||||
|
||||
if (g_hash_table_contains(self->tracks, track_name)) { // If we have this file already
|
||||
g_free(track_name);
|
||||
return;
|
||||
}
|
||||
|
||||
g_hash_table_insert(self->tracks, track_name, track); // Add the file
|
||||
}
|
||||
|
||||
void koto_indexed_album_commit(KotoIndexedAlbum *self) {
|
||||
gchar *artist_uuid;
|
||||
g_object_get(self->artist, "uuid", &artist_uuid, NULL); // Get the artist UUID
|
||||
|
||||
if (self->art_path == NULL) { // If art_path isn't defined when committing
|
||||
koto_indexed_album_set_album_art(self, ""); // Set to an empty string
|
||||
}
|
||||
|
@ -154,7 +147,7 @@ void koto_indexed_album_commit(KotoIndexedAlbum *self) {
|
|||
"ON CONFLICT(id) DO UPDATE SET path=excluded.path, name=excluded.name, art_path=excluded.art_path;",
|
||||
self->uuid,
|
||||
self->path,
|
||||
artist_uuid,
|
||||
self->artist_uuid,
|
||||
self->name,
|
||||
self->art_path
|
||||
);
|
||||
|
@ -314,7 +307,7 @@ void koto_indexed_album_find_tracks(KotoIndexedAlbum *self, magic_t magic_cookie
|
|||
KotoIndexedTrack *track = koto_indexed_track_new(self, full_path, cd);
|
||||
|
||||
if (track != NULL) { // Is a file
|
||||
koto_indexed_album_add_file(self, track); // Add our file
|
||||
koto_indexed_album_add_track(self, track); // Add our file
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -326,9 +319,6 @@ static void koto_indexed_album_get_property(GObject *obj, guint prop_id, GValue
|
|||
KotoIndexedAlbum *self = KOTO_INDEXED_ALBUM(obj);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_ARTIST:
|
||||
g_value_set_object(val, self->artist);
|
||||
break;
|
||||
case PROP_UUID:
|
||||
g_value_set_string(val, self->uuid);
|
||||
break;
|
||||
|
@ -344,6 +334,38 @@ static void koto_indexed_album_get_property(GObject *obj, guint prop_id, GValue
|
|||
case PROP_ART_PATH:
|
||||
g_value_set_string(val, koto_indexed_album_get_album_art(self));
|
||||
break;
|
||||
case PROP_ARTIST_UUID:
|
||||
g_value_set_string(val, self->artist_uuid);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void koto_indexed_album_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec){
|
||||
KotoIndexedAlbum *self = KOTO_INDEXED_ALBUM(obj);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_UUID:
|
||||
self->uuid = g_strdup(g_value_get_string(val));
|
||||
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_UUID]);
|
||||
break;
|
||||
case PROP_DO_INITIAL_INDEX:
|
||||
self->do_initial_index = g_value_get_boolean(val);
|
||||
break;
|
||||
case PROP_PATH: // Path to the album
|
||||
koto_indexed_album_update_path(self, g_value_get_string(val));
|
||||
break;
|
||||
case PROP_ALBUM_NAME: // Name of album
|
||||
koto_indexed_album_set_album_name(self, g_value_get_string(val));
|
||||
break;
|
||||
case PROP_ART_PATH: // Path to art
|
||||
koto_indexed_album_set_album_art(self, g_value_get_string(val));
|
||||
break;
|
||||
case PROP_ARTIST_UUID:
|
||||
koto_indexed_album_set_artist_uuid(self, g_value_get_string(val));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
|
@ -351,15 +373,11 @@ static void koto_indexed_album_get_property(GObject *obj, guint prop_id, GValue
|
|||
}
|
||||
|
||||
gchar* koto_indexed_album_get_album_art(KotoIndexedAlbum *self) {
|
||||
return g_strdup((self->has_album_art) ? self->art_path : "");
|
||||
return g_strdup((self->has_album_art && (self->art_path != NULL) && (strcmp(self->art_path, "") != 0)) ? self->art_path : "");
|
||||
}
|
||||
|
||||
GList* koto_indexed_album_get_files(KotoIndexedAlbum *self) {
|
||||
if (self->tracks == NULL) { // No HashTable yet
|
||||
self->tracks = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable
|
||||
}
|
||||
|
||||
return g_hash_table_get_values(self->tracks);
|
||||
GList* koto_indexed_album_get_tracks(KotoIndexedAlbum *self) {
|
||||
return self->tracks; // Return
|
||||
}
|
||||
|
||||
void koto_indexed_album_set_album_art(KotoIndexedAlbum *self, const gchar *album_art) {
|
||||
|
@ -381,22 +399,9 @@ void koto_indexed_album_remove_file(KotoIndexedAlbum *self, KotoIndexedTrack *tr
|
|||
return;
|
||||
}
|
||||
|
||||
if (self->tracks == NULL) { // No HashTable yet
|
||||
self->tracks = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable
|
||||
}
|
||||
|
||||
gchar *track_name;
|
||||
g_object_get(track, "parsed-name", &track_name, NULL);
|
||||
g_hash_table_remove(self->tracks, track_name);
|
||||
}
|
||||
|
||||
void koto_indexed_album_remove_file_by_name(KotoIndexedAlbum *self, const gchar *track_name) {
|
||||
if (track_name == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
KotoIndexedTrack *track = g_hash_table_lookup(self->tracks, track_name); // Get the files
|
||||
koto_indexed_album_remove_file(self, track);
|
||||
gchar *track_uuid;
|
||||
g_object_get(track, "parsed-name", &track_uuid, NULL);
|
||||
self->tracks = g_list_remove(self->tracks, track_uuid);
|
||||
}
|
||||
|
||||
void koto_indexed_album_set_album_name(KotoIndexedAlbum *self, const gchar *album_name) {
|
||||
|
@ -411,49 +416,77 @@ void koto_indexed_album_set_album_name(KotoIndexedAlbum *self, const gchar *albu
|
|||
self->name = g_strdup(album_name);
|
||||
}
|
||||
|
||||
void koto_indexed_album_set_artist_uuid(KotoIndexedAlbum *self, const gchar *artist_uuid) {
|
||||
if (artist_uuid == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->artist_uuid != NULL) {
|
||||
g_free(self->artist_uuid);
|
||||
}
|
||||
|
||||
self->artist_uuid = g_strdup(artist_uuid);
|
||||
}
|
||||
|
||||
void koto_indexed_album_set_as_current_playlist(KotoIndexedAlbum *self) {
|
||||
if (self->tracks == NULL) { // No files to add to the playlist
|
||||
return;
|
||||
}
|
||||
|
||||
KotoPlaylist *new_album_playlist = koto_playlist_new(); // Create a new playlist
|
||||
g_object_set(new_album_playlist, "ephemeral", TRUE, NULL); // Set as ephemeral / temporary
|
||||
|
||||
GList *tracks_list_uuids = g_hash_table_get_keys(self->tracks); // Get the UUIDs
|
||||
for (guint i = 0; i < g_list_length(tracks_list_uuids); i++) { // For each of the tracks
|
||||
koto_playlist_add_track_by_uuid(new_album_playlist, (gchar*) g_list_nth_data(tracks_list_uuids, i)); // Add the UUID
|
||||
GList *t;
|
||||
for (t = self->tracks; t != NULL; t = t->next) { // For each of the tracks
|
||||
koto_playlist_add_track_by_uuid(new_album_playlist, (gchar*) t->data); // Add the UUID
|
||||
}
|
||||
|
||||
g_list_free(tracks_list_uuids); // Free the uuids
|
||||
|
||||
koto_current_playlist_set_playlist(current_playlist, new_album_playlist); // Set our new current playlist
|
||||
}
|
||||
|
||||
static void koto_indexed_album_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec){
|
||||
KotoIndexedAlbum *self = KOTO_INDEXED_ALBUM(obj);
|
||||
gint koto_indexed_album_sort_tracks(gconstpointer track1_uuid, gconstpointer track2_uuid, gpointer user_data) {
|
||||
(void) user_data;
|
||||
KotoIndexedTrack *track1 = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) track1_uuid);
|
||||
KotoIndexedTrack *track2 = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) track2_uuid);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_ARTIST:
|
||||
self->artist = (KotoIndexedArtist*) g_value_get_object(val);
|
||||
break;
|
||||
case PROP_UUID:
|
||||
self->uuid = g_strdup(g_value_get_string(val));
|
||||
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_UUID]);
|
||||
break;
|
||||
case PROP_DO_INITIAL_INDEX:
|
||||
self->do_initial_index = g_value_get_boolean(val);
|
||||
break;
|
||||
case PROP_PATH: // Path to the album
|
||||
koto_indexed_album_update_path(self, g_value_get_string(val));
|
||||
break;
|
||||
case PROP_ALBUM_NAME: // Name of album
|
||||
koto_indexed_album_set_album_name(self, g_value_get_string(val));
|
||||
break;
|
||||
case PROP_ART_PATH: // Path to art
|
||||
koto_indexed_album_set_album_art(self, g_value_get_string(val));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
if ((track1 == NULL) && (track2 == NULL)) { // Neither tracks actually exist
|
||||
return 0;
|
||||
} else if ((track1 != NULL) && (track2 == NULL)) { // Only track2 does not exist
|
||||
return -1;
|
||||
} else if ((track1 == NULL) && (track2 != NULL)) { // Only track1 does not exist
|
||||
return 1;
|
||||
}
|
||||
|
||||
guint *track1_disc = (guint*) 1;
|
||||
guint *track2_disc = (guint*) 2;
|
||||
|
||||
g_object_get(track1, "cd", &track1_disc, NULL);
|
||||
g_object_get(track2, "cd", &track2_disc, NULL);
|
||||
|
||||
if (track1_disc < track2_disc) { // Track 2 is in a later CD / Disc
|
||||
return -1;
|
||||
} else if (track1_disc > track2_disc) { // Track1 is later
|
||||
return 1;
|
||||
}
|
||||
|
||||
guint16 *track1_pos;
|
||||
guint16 *track2_pos;
|
||||
|
||||
g_object_get(track1, "position", &track1_pos, NULL);
|
||||
g_object_get(track2, "position", &track2_pos, NULL);
|
||||
|
||||
if (track1_pos == track2_pos) { // Identical positions (like reported as 0)
|
||||
gchar *track1_name;
|
||||
gchar *track2_name;
|
||||
|
||||
g_object_get(track1, "parsed-name", &track1_name, NULL);
|
||||
g_object_get(track2, "parsed-name", &track2_name, NULL);
|
||||
|
||||
return g_utf8_collate(track1_name, track2_name);
|
||||
} else if (track1_pos < track2_pos) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -478,8 +511,11 @@ void koto_indexed_album_update_path(KotoIndexedAlbum *self, const gchar* new_pat
|
|||
}
|
||||
|
||||
KotoIndexedAlbum* koto_indexed_album_new(KotoIndexedArtist *artist, const gchar *path) {
|
||||
gchar *artist_uuid = NULL;
|
||||
g_object_get(artist, "uuid", &artist_uuid, NULL);
|
||||
|
||||
KotoIndexedAlbum* album = g_object_new(KOTO_TYPE_INDEXED_ALBUM,
|
||||
"artist", artist,
|
||||
"artist-uuid", artist_uuid,
|
||||
"uuid", g_strdup(g_uuid_string_random()),
|
||||
"do-initial-index", TRUE,
|
||||
"path", path,
|
||||
|
@ -493,8 +529,11 @@ KotoIndexedAlbum* koto_indexed_album_new(KotoIndexedArtist *artist, const gchar
|
|||
}
|
||||
|
||||
KotoIndexedAlbum* koto_indexed_album_new_with_uuid(KotoIndexedArtist *artist, const gchar *uuid) {
|
||||
gchar *artist_uuid = NULL;
|
||||
g_object_get(artist, "uuid", &artist_uuid, NULL);
|
||||
|
||||
return g_object_new(KOTO_TYPE_INDEXED_ALBUM,
|
||||
"artist", artist,
|
||||
"artist-uuid", artist,
|
||||
"uuid", g_strdup(uuid),
|
||||
"do-initial-index", FALSE,
|
||||
NULL
|
||||
|
|
|
@ -29,7 +29,7 @@ struct _KotoIndexedArtist {
|
|||
|
||||
gboolean has_artist_art;
|
||||
gchar *artist_name;
|
||||
GHashTable *albums;
|
||||
GList *albums;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(KotoIndexedArtist, koto_indexed_artist, G_TYPE_OBJECT);
|
||||
|
@ -108,7 +108,7 @@ void koto_indexed_artist_commit(KotoIndexedArtist *self) {
|
|||
|
||||
static void koto_indexed_artist_init(KotoIndexedArtist *self) {
|
||||
self->has_artist_art = FALSE;
|
||||
self->albums = NULL; // Set to null initially maybe
|
||||
self->albums = NULL; // Create a new GList
|
||||
}
|
||||
|
||||
static void koto_indexed_artist_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) {
|
||||
|
@ -130,7 +130,7 @@ static void koto_indexed_artist_get_property(GObject *obj, guint prop_id, GValue
|
|||
}
|
||||
}
|
||||
|
||||
static void koto_indexed_artist_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec){
|
||||
static void koto_indexed_artist_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec) {
|
||||
KotoIndexedArtist *self = KOTO_INDEXED_ARTIST(obj);
|
||||
|
||||
switch (prop_id) {
|
||||
|
@ -150,45 +150,20 @@ static void koto_indexed_artist_set_property(GObject *obj, guint prop_id, const
|
|||
}
|
||||
}
|
||||
|
||||
void koto_indexed_artist_add_album(KotoIndexedArtist *self, KotoIndexedAlbum *album) {
|
||||
if (album == NULL) { // No album really defined
|
||||
void koto_indexed_artist_add_album(KotoIndexedArtist *self, gchar *album_uuid) {
|
||||
if ((album_uuid == NULL) || (strcmp(album_uuid, "") == 0)) { // No album UUID really defined
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->albums == NULL) { // No HashTable yet
|
||||
self->albums = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable
|
||||
gchar *uuid = g_strdup(album_uuid); // Duplicate our UUID
|
||||
|
||||
if (g_list_index(self->albums, uuid) == -1) {
|
||||
self->albums = g_list_append(self->albums, uuid); // Push to end of list
|
||||
}
|
||||
|
||||
gchar *album_uuid;
|
||||
g_object_get(album, "uuid", &album_uuid, NULL);
|
||||
|
||||
if (album_uuid == NULL) {
|
||||
g_free(album_uuid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_hash_table_contains(self->albums, album_uuid)) { // If we have this album already
|
||||
g_free(album_uuid);
|
||||
return;
|
||||
}
|
||||
|
||||
g_hash_table_insert(self->albums, album_uuid, album); // Add the album
|
||||
}
|
||||
|
||||
GList* koto_indexed_artist_get_albums(KotoIndexedArtist *self) {
|
||||
if (self->albums == NULL) { // No HashTable yet
|
||||
self->albums = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable
|
||||
}
|
||||
|
||||
return g_hash_table_get_values(self->albums);
|
||||
}
|
||||
|
||||
KotoIndexedAlbum* koto_indexed_artist_get_album(KotoIndexedArtist *self, gchar *album_uuid) {
|
||||
if (self->albums == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return g_hash_table_lookup(self->albums, album_uuid);
|
||||
return g_list_copy(self->albums);
|
||||
}
|
||||
|
||||
void koto_indexed_artist_remove_album(KotoIndexedArtist *self, KotoIndexedAlbum *album) {
|
||||
|
@ -196,14 +171,9 @@ void koto_indexed_artist_remove_album(KotoIndexedArtist *self, KotoIndexedAlbum
|
|||
return;
|
||||
}
|
||||
|
||||
if (self->albums == NULL) { // No HashTable yet
|
||||
self->albums = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable
|
||||
}
|
||||
|
||||
gchar *album_uuid;
|
||||
g_object_get(album, "uuid", &album_uuid, NULL);
|
||||
|
||||
g_hash_table_remove(self->albums, album_uuid);
|
||||
self->albums = g_list_remove(self->albums, album_uuid);
|
||||
}
|
||||
|
||||
void koto_indexed_artist_update_path(KotoIndexedArtist *self, const gchar *new_path) {
|
||||
|
|
|
@ -20,10 +20,12 @@
|
|||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <taglib/tag_c.h>
|
||||
#include "../db/cartographer.h"
|
||||
#include "../db/db.h"
|
||||
#include "structs.h"
|
||||
#include "../koto-utils.h"
|
||||
#include "structs.h"
|
||||
|
||||
extern KotoCartographer *koto_maps;
|
||||
extern sqlite3 *koto_db;
|
||||
|
||||
struct _KotoIndexedLibrary {
|
||||
|
@ -76,14 +78,15 @@ void koto_indexed_library_add_artist(KotoIndexedLibrary *self, KotoIndexedArtist
|
|||
koto_indexed_library_get_artists(self); // Call to generate if needed
|
||||
|
||||
gchar *artist_name;
|
||||
g_object_get(artist, "name", &artist_name, NULL);
|
||||
gchar *artist_uuid;
|
||||
g_object_get(artist, "name", &artist_name, "uuid", &artist_uuid, NULL);
|
||||
|
||||
if (g_hash_table_contains(self->music_artists, artist_name)) { // Already have the artist
|
||||
g_free(artist_name);
|
||||
return;
|
||||
}
|
||||
|
||||
g_hash_table_insert(self->music_artists, artist_name, artist); // Add the artist by its name (this needs to be done so we can get the artist when doing the depth of 2 indexing for the album)
|
||||
g_hash_table_insert(self->music_artists, artist_name, artist_uuid); // Add the artist by its name (this needs to be done so we can get the artist when doing the depth of 2 indexing for the album)
|
||||
}
|
||||
|
||||
KotoIndexedArtist* koto_indexed_library_get_artist(KotoIndexedLibrary *self, gchar *artist_name) {
|
||||
|
@ -93,7 +96,14 @@ KotoIndexedArtist* koto_indexed_library_get_artist(KotoIndexedLibrary *self, gch
|
|||
|
||||
koto_indexed_library_get_artists(self); // Call to generate if needed
|
||||
|
||||
return g_hash_table_lookup(self->music_artists, (KotoIndexedArtist*) artist_name);
|
||||
gchar *artist_uuid = g_hash_table_lookup(self->music_artists, artist_name); // Get the UUID from our music artists
|
||||
|
||||
if (artist_uuid != NULL) {
|
||||
KotoIndexedArtist *artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid); // Return any artist from cartographer
|
||||
return artist;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
GHashTable* koto_indexed_library_get_artists(KotoIndexedLibrary *self) {
|
||||
|
@ -162,10 +172,9 @@ void koto_indexed_library_set_path(KotoIndexedLibrary *self, gchar *path) {
|
|||
}
|
||||
|
||||
int process_artists(void *data, int num_columns, char **fields, char **column_names) {
|
||||
(void) num_columns; (void) column_names; // Don't need these
|
||||
|
||||
KotoIndexedLibrary *self = (KotoIndexedLibrary*) data;
|
||||
(void) num_columns; (void) column_names; // Don't need any of the params
|
||||
|
||||
KotoIndexedLibrary *library = (KotoIndexedLibrary*) data;
|
||||
gchar *artist_uuid = g_strdup(koto_utils_unquote_string(fields[0])); // First column is UUID
|
||||
gchar *artist_path = g_strdup(koto_utils_unquote_string(fields[1])); // Second column is path
|
||||
gchar *artist_name = g_strdup(koto_utils_unquote_string(fields[3])); // Fourth column is artist name
|
||||
|
@ -177,7 +186,8 @@ int process_artists(void *data, int num_columns, char **fields, char **column_na
|
|||
"name", artist_name, // Set name
|
||||
NULL);
|
||||
|
||||
koto_indexed_library_add_artist(self, artist); // Add the artist
|
||||
koto_cartographer_add_artist(koto_maps, artist); // Add the artist to our global cartographer
|
||||
koto_indexed_library_add_artist(library, artist);
|
||||
|
||||
int albums_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM albums WHERE artist_id=\"%s\"", artist_uuid), process_albums, artist, NULL); // Process our albums
|
||||
if (albums_rc != SQLITE_OK) { // Failed to get our albums
|
||||
|
@ -211,7 +221,8 @@ int process_albums(void *data, int num_columns, char **fields, char **column_nam
|
|||
"art-path", album_art, // Set art path if any
|
||||
NULL);
|
||||
|
||||
koto_indexed_artist_add_album(artist, album); // Add the album
|
||||
koto_cartographer_add_album(koto_maps, album); // Add the album to our global cartographer
|
||||
koto_indexed_artist_add_album(artist, album_uuid); // Add the album
|
||||
|
||||
int tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM tracks WHERE album_id=\"%s\"", album_uuid), process_tracks, album, NULL); // Process our tracks
|
||||
if (tracks_rc != SQLITE_OK) { // Failed to get our tracks
|
||||
|
@ -241,8 +252,8 @@ int process_tracks(void *data, int num_columns, char **fields, char **column_nam
|
|||
gchar *album_uuid = g_strdup(koto_utils_unquote_string(fields[4]));
|
||||
gchar *file_name = g_strdup(koto_utils_unquote_string(fields[5]));
|
||||
gchar *name = g_strdup(koto_utils_unquote_string(fields[6]));
|
||||
guint *disc_num = (guint*) g_ascii_strtoull(fields[6], NULL, 10);
|
||||
guint *position = (guint*) g_ascii_strtoull(fields[7], NULL, 10);
|
||||
guint *disc_num = (guint*) g_ascii_strtoull(fields[7], NULL, 10);
|
||||
guint *position = (guint*) g_ascii_strtoull(fields[8], NULL, 10);
|
||||
|
||||
KotoIndexedTrack *track = koto_indexed_track_new_with_uuid(track_uuid); // Create our file
|
||||
g_object_set(track,
|
||||
|
@ -255,7 +266,8 @@ int process_tracks(void *data, int num_columns, char **fields, char **column_nam
|
|||
"position", position,
|
||||
NULL);
|
||||
|
||||
koto_indexed_album_add_file(album, track); // Add the file
|
||||
koto_cartographer_add_track(koto_maps, track); // Add the track to cartographer
|
||||
koto_indexed_album_add_track(album, track); // Add the track
|
||||
|
||||
g_free(track_uuid);
|
||||
g_free(path);
|
||||
|
@ -341,21 +353,25 @@ void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth) {
|
|||
NULL
|
||||
);
|
||||
|
||||
koto_cartographer_add_artist(koto_maps, artist); // Add the artist to cartographer
|
||||
koto_indexed_library_add_artist(self, artist); // Add the artist
|
||||
index_folder(self, full_path, depth); // Index this directory
|
||||
g_free(artist_name);
|
||||
} else if (depth == 2) { // If we are following FOLDER/ARTIST/ALBUM then this would be album
|
||||
gchar *artist_name = g_path_get_basename(path); // Get the last entry from our path which is probably the artist
|
||||
|
||||
KotoIndexedArtist *artist = koto_indexed_library_get_artist(self, artist_name); // Get the artist
|
||||
|
||||
if (artist == NULL) {
|
||||
g_message("Failed to get artist by name of: %s", artist_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
KotoIndexedAlbum *album = koto_indexed_album_new(artist, full_path);
|
||||
koto_cartographer_add_album(koto_maps, album); // Add our album to the cartographer
|
||||
|
||||
koto_indexed_artist_add_album(artist, album); // Add the album
|
||||
gchar *album_uuid = NULL;
|
||||
g_object_get(album, "uuid", &album_uuid, NULL);
|
||||
koto_indexed_artist_add_album(artist, album_uuid); // Add the album
|
||||
g_free(artist_name);
|
||||
}
|
||||
}
|
||||
|
@ -367,9 +383,13 @@ void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth) {
|
|||
}
|
||||
|
||||
void output_artists(gpointer artist_key, gpointer artist_ptr, gpointer data) {
|
||||
(void)artist_key;
|
||||
(void)data;
|
||||
KotoIndexedArtist *artist = (KotoIndexedArtist*) artist_ptr;
|
||||
(void) artist_ptr; (void) data;
|
||||
KotoIndexedArtist *artist = koto_cartographer_get_artist_by_uuid(koto_maps, (gchar*) artist_key);
|
||||
|
||||
if (artist == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
gchar *artist_name;
|
||||
g_object_get(artist, "name", &artist_name, NULL);
|
||||
g_debug("Artist: %s", artist_name);
|
||||
|
@ -382,18 +402,33 @@ void output_artists(gpointer artist_key, gpointer artist_ptr, gpointer data) {
|
|||
|
||||
GList *a;
|
||||
for (a = albums; a != NULL; a = a->next) {
|
||||
KotoIndexedAlbum *album = (KotoIndexedAlbum*) a->data;
|
||||
gchar *album_uuid = a->data;
|
||||
KotoIndexedAlbum *album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid);
|
||||
|
||||
if (album == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
gchar *artwork = koto_indexed_album_get_album_art(album);
|
||||
gchar *album_name;
|
||||
g_object_get(album, "name", &album_name, NULL);
|
||||
g_debug("Album Art: %s", artwork);
|
||||
g_debug("Album Name: %s", album_name);
|
||||
|
||||
GList *tracks = koto_indexed_album_get_files(album); // Get the files for the album
|
||||
GList *t;
|
||||
g_list_foreach(koto_indexed_album_get_tracks(album), output_track, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void output_track(gpointer data, gpointer user_data) {
|
||||
(void) user_data;
|
||||
|
||||
g_message("Track UUID: %s", g_strdup(data));
|
||||
KotoIndexedTrack *track = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) data);
|
||||
|
||||
if (track == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (t = tracks; t != NULL; t = t->next) {
|
||||
KotoIndexedTrack *track = (KotoIndexedTrack*) t->data;
|
||||
gchar *filepath;
|
||||
gchar *parsed_name;
|
||||
guint *pos;
|
||||
|
@ -408,6 +443,4 @@ void output_artists(gpointer artist_key, gpointer artist_ptr, gpointer data) {
|
|||
g_debug("Position: %d", GPOINTER_TO_INT(pos));
|
||||
g_free(filepath);
|
||||
g_free(parsed_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ G_DECLARE_FINAL_TYPE (KotoIndexedAlbum, koto_indexed_album, KOTO, INDEXED_ALBUM,
|
|||
|
||||
#define KOTO_TYPE_INDEXED_TRACK koto_indexed_track_get_type()
|
||||
G_DECLARE_FINAL_TYPE(KotoIndexedTrack, koto_indexed_track, KOTO, INDEXED_TRACK, GObject);
|
||||
#define KOTO_IS_INDEXED_TRACK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_INDEXED_TRACK))
|
||||
|
||||
/**
|
||||
* Library Functions
|
||||
|
@ -54,6 +55,7 @@ int process_tracks(void *data, int num_columns, char **fields, char **column_nam
|
|||
void read_from_db(KotoIndexedLibrary *self);
|
||||
void start_indexing(KotoIndexedLibrary *self);
|
||||
void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth);
|
||||
void output_track(gpointer data, gpointer user_data);
|
||||
|
||||
/**
|
||||
* Artist Functions
|
||||
|
@ -62,11 +64,10 @@ void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth);
|
|||
KotoIndexedArtist* koto_indexed_artist_new(const gchar *path);
|
||||
KotoIndexedArtist* koto_indexed_artist_new_with_uuid(const gchar *uuid);
|
||||
|
||||
void koto_indexed_artist_add_album(KotoIndexedArtist *self, KotoIndexedAlbum *album);
|
||||
void koto_indexed_artist_add_album(KotoIndexedArtist *self, gchar *album_uuid);
|
||||
void koto_indexed_artist_commit(KotoIndexedArtist *self);
|
||||
guint koto_indexed_artist_find_album_with_name(gconstpointer *album_data, gconstpointer *album_name_data);
|
||||
GList* koto_indexed_artist_get_albums(KotoIndexedArtist *self);
|
||||
KotoIndexedAlbum* koto_indexed_artist_get_album(KotoIndexedArtist *self, gchar *album_uuid);
|
||||
void koto_indexed_artist_remove_album(KotoIndexedArtist *self, KotoIndexedAlbum *album);
|
||||
void koto_indexed_artist_remove_album_by_name(KotoIndexedArtist *self, gchar *album_name);
|
||||
void koto_indexed_artist_set_artist_name(KotoIndexedArtist *self, const gchar *artist_name);
|
||||
|
@ -80,18 +81,19 @@ void output_artists(gpointer artist_key, gpointer artist_ptr, gpointer data);
|
|||
KotoIndexedAlbum* koto_indexed_album_new(KotoIndexedArtist *artist, const gchar *path);
|
||||
KotoIndexedAlbum* koto_indexed_album_new_with_uuid(KotoIndexedArtist *artist, const gchar *uuid);
|
||||
|
||||
void koto_indexed_album_add_file(KotoIndexedAlbum *self, KotoIndexedTrack *track);
|
||||
void koto_indexed_album_add_track(KotoIndexedAlbum *self, KotoIndexedTrack *track);
|
||||
void koto_indexed_album_commit(KotoIndexedAlbum *self);
|
||||
void koto_indexed_album_find_album_art(KotoIndexedAlbum *self);
|
||||
void koto_indexed_album_find_tracks(KotoIndexedAlbum *self, magic_t magic_cookie, const gchar *path);
|
||||
gchar* koto_indexed_album_get_album_art(KotoIndexedAlbum *self);
|
||||
GList* koto_indexed_album_get_files(KotoIndexedAlbum *self);
|
||||
GList* koto_indexed_album_get_tracks(KotoIndexedAlbum *self);
|
||||
void koto_indexed_album_remove_file(KotoIndexedAlbum *self, KotoIndexedTrack *track);
|
||||
void koto_indexed_album_remove_file_by_name(KotoIndexedAlbum *self, const gchar *track_name);
|
||||
void koto_indexed_album_set_album_art(KotoIndexedAlbum *self, const gchar *album_art);
|
||||
void koto_indexed_album_set_album_name(KotoIndexedAlbum *self, const gchar *album_name);
|
||||
void koto_indexed_album_set_artist_uuid(KotoIndexedAlbum *self, const gchar *artist_uuid);
|
||||
void koto_indexed_album_set_as_current_playlist(KotoIndexedAlbum *self);
|
||||
void koto_indexed_album_update_path(KotoIndexedAlbum *self, const gchar *path);
|
||||
gint koto_indexed_album_sort_tracks(gconstpointer track1_uuid, gconstpointer track2_uuid, gpointer user_data);
|
||||
|
||||
/**
|
||||
* File / Track Functions
|
||||
|
@ -102,6 +104,7 @@ KotoIndexedTrack* koto_indexed_track_new_with_uuid(const gchar *uuid);
|
|||
|
||||
void koto_indexed_track_commit(KotoIndexedTrack *self);
|
||||
void koto_indexed_track_parse_name(KotoIndexedTrack *self);
|
||||
void koto_indexed_track_save_to_playlist(KotoIndexedTrack *self, gchar *playlist_uuid, guint position, gint current);
|
||||
void koto_indexed_track_set_file_name(KotoIndexedTrack *self, gchar *new_file_name);
|
||||
void koto_indexed_track_set_cd(KotoIndexedTrack *self, guint cd);
|
||||
void koto_indexed_track_set_parsed_name(KotoIndexedTrack *self, gchar *new_parsed_name);
|
||||
|
|
|
@ -18,9 +18,11 @@
|
|||
#include <glib-2.0/glib.h>
|
||||
#include <sqlite3.h>
|
||||
#include <taglib/tag_c.h>
|
||||
#include "../db/cartographer.h"
|
||||
#include "structs.h"
|
||||
#include "koto-utils.h"
|
||||
|
||||
extern KotoCartographer *koto_maps;
|
||||
extern sqlite3 *koto_db;
|
||||
|
||||
struct _KotoIndexedTrack {
|
||||
|
@ -32,10 +34,9 @@ struct _KotoIndexedTrack {
|
|||
|
||||
gchar *file_name;
|
||||
gchar *parsed_name;
|
||||
gchar *artist;
|
||||
gchar *album;
|
||||
guint *cd;
|
||||
guint *position;
|
||||
guint *playback_position;
|
||||
|
||||
gboolean acquired_metadata_from_id3;
|
||||
gboolean do_initial_index;
|
||||
|
@ -49,13 +50,12 @@ enum {
|
|||
PROP_ALBUM_UUID,
|
||||
PROP_UUID,
|
||||
PROP_DO_INITIAL_INDEX,
|
||||
PROP_ARTIST,
|
||||
PROP_ALBUM,
|
||||
PROP_PATH,
|
||||
PROP_FILE_NAME,
|
||||
PROP_PARSED_NAME,
|
||||
PROP_CD,
|
||||
PROP_POSITION,
|
||||
PROP_PLAYBACK_POSITION,
|
||||
N_PROPERTIES
|
||||
};
|
||||
|
||||
|
@ -110,22 +110,6 @@ static void koto_indexed_track_class_init(KotoIndexedTrackClass *c) {
|
|||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
props[PROP_ARTIST] = g_param_spec_string(
|
||||
"artist",
|
||||
"Name of Artist",
|
||||
"Name of Artist",
|
||||
NULL,
|
||||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
props[PROP_ALBUM] = g_param_spec_string(
|
||||
"album",
|
||||
"Name of Album",
|
||||
"Name of Album",
|
||||
NULL,
|
||||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
props[PROP_FILE_NAME] = g_param_spec_string(
|
||||
"file-name",
|
||||
"Name of File",
|
||||
|
@ -162,6 +146,16 @@ static void koto_indexed_track_class_init(KotoIndexedTrackClass *c) {
|
|||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
props[PROP_PLAYBACK_POSITION] = g_param_spec_uint(
|
||||
"playback-position",
|
||||
"Current playback position",
|
||||
"Current playback position",
|
||||
0,
|
||||
G_MAXUINT16,
|
||||
0,
|
||||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
|
||||
}
|
||||
|
||||
|
@ -182,12 +176,6 @@ static void koto_indexed_track_get_property(GObject *obj, guint prop_id, GValue
|
|||
case PROP_UUID:
|
||||
g_value_set_string(val, self->uuid);
|
||||
break;
|
||||
case PROP_ARTIST:
|
||||
g_value_set_string(val, self->artist);
|
||||
break;
|
||||
case PROP_ALBUM:
|
||||
g_value_set_string(val, self->album);
|
||||
break;
|
||||
case PROP_PATH:
|
||||
g_value_set_string(val, self->path);
|
||||
break;
|
||||
|
@ -203,6 +191,9 @@ static void koto_indexed_track_get_property(GObject *obj, guint prop_id, GValue
|
|||
case PROP_POSITION:
|
||||
g_value_set_uint(val, GPOINTER_TO_UINT(self->position));
|
||||
break;
|
||||
case PROP_PLAYBACK_POSITION:
|
||||
g_value_set_uint(val, GPOINTER_TO_UINT(self->playback_position));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
|
@ -228,12 +219,6 @@ static void koto_indexed_track_set_property(GObject *obj, guint prop_id, const G
|
|||
case PROP_DO_INITIAL_INDEX:
|
||||
self->do_initial_index = g_value_get_boolean(val);
|
||||
break;
|
||||
case PROP_ARTIST:
|
||||
self->artist = g_strdup(g_value_get_string(val));
|
||||
break;
|
||||
case PROP_ALBUM:
|
||||
self->album = g_strdup(g_value_get_string(val));
|
||||
break;
|
||||
case PROP_PATH:
|
||||
koto_indexed_track_update_path(self, g_value_get_string(val)); // Update the path
|
||||
break;
|
||||
|
@ -249,6 +234,9 @@ static void koto_indexed_track_set_property(GObject *obj, guint prop_id, const G
|
|||
case PROP_POSITION:
|
||||
koto_indexed_track_set_position(self, g_value_get_uint(val));
|
||||
break;
|
||||
case PROP_PLAYBACK_POSITION:
|
||||
self->playback_position = GUINT_TO_POINTER(g_value_get_uint(val));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
|
@ -293,15 +281,23 @@ void koto_indexed_track_commit(KotoIndexedTrack *self) {
|
|||
void koto_indexed_track_parse_name(KotoIndexedTrack *self) {
|
||||
gchar *copied_file_name = g_strdelimit(g_strdup(self->file_name), "_", ' '); // Replace _ with whitespace for starters
|
||||
|
||||
if (self->artist != NULL && strcmp(self->artist, "") != 0) { // If we have artist set
|
||||
gchar **split = g_strsplit(copied_file_name, self->artist, -1); // Split whenever we encounter the artist
|
||||
KotoIndexedArtist *artist = NULL;
|
||||
artist = koto_cartographer_get_artist_by_uuid(koto_maps, self->artist_uuid);
|
||||
|
||||
if (artist != NULL) { // If we have artist
|
||||
gchar *artist_name = NULL;
|
||||
g_object_get(artist, "name", &artist_name, NULL);
|
||||
|
||||
if (artist_name != NULL && (strcmp(artist_name, "") != 0)) {
|
||||
gchar **split = g_strsplit(copied_file_name, artist_name, -1); // Split whenever we encounter the artist
|
||||
copied_file_name = g_strjoinv("", split); // Remove the artist
|
||||
g_strfreev(split);
|
||||
|
||||
split = g_strsplit(copied_file_name, g_utf8_strdown(self->artist, -1), -1); // Lowercase album name and split by that
|
||||
split = g_strsplit(copied_file_name, g_utf8_strdown(artist_name, -1), -1); // Lowercase album name and split by that
|
||||
copied_file_name = g_strjoinv("", split); // Remove the artist
|
||||
g_strfreev(split);
|
||||
}
|
||||
}
|
||||
|
||||
gchar *file_without_ext = koto_utils_get_filename_without_extension(copied_file_name);
|
||||
g_free(copied_file_name);
|
||||
|
@ -339,6 +335,28 @@ void koto_indexed_track_parse_name(KotoIndexedTrack *self) {
|
|||
g_free(file_without_ext);
|
||||
}
|
||||
|
||||
void koto_indexed_track_save_to_playlist(KotoIndexedTrack *self, gchar *playlist_uuid, guint position, gint current) {
|
||||
gchar *commit_op = g_strdup_printf(
|
||||
"INSERT INTO playlist_tracks(playlist_id, track_id, position, current)"
|
||||
"VALUES('%s', '%s', quote(\"%d\"), quote(\"%d\")"
|
||||
"ON CONFLICT(playlist_id, track_id) DO UPDATE SET position=excluded.position, current=excluded.current;",
|
||||
playlist_uuid,
|
||||
self->uuid,
|
||||
position,
|
||||
current
|
||||
);
|
||||
|
||||
gchar *commit_op_errmsg = NULL;
|
||||
int rc = sqlite3_exec(koto_db, commit_op, 0, 0, &commit_op_errmsg);
|
||||
|
||||
if (rc != SQLITE_OK) {
|
||||
g_warning("Failed to save track to playlist: %s", commit_op_errmsg);
|
||||
}
|
||||
|
||||
g_free(commit_op);
|
||||
g_free(commit_op_errmsg);
|
||||
}
|
||||
|
||||
void koto_indexed_track_set_file_name(KotoIndexedTrack *self, gchar *new_file_name) {
|
||||
if (new_file_name == NULL) {
|
||||
return;
|
||||
|
@ -402,9 +420,8 @@ void koto_indexed_track_update_metadata(KotoIndexedTrack *self) {
|
|||
self->acquired_metadata_from_id3 = TRUE;
|
||||
TagLib_Tag *tag = taglib_file_tag(t_file); // Get our tag
|
||||
koto_indexed_track_set_parsed_name(self, taglib_tag_title(tag)); // Set the title of the file
|
||||
self->artist = g_strdup(taglib_tag_artist((tag))); // Set the artist
|
||||
self->album = g_strdup(taglib_tag_album(tag)); // Set the album
|
||||
koto_indexed_track_set_position(self, (uint) taglib_tag_track(tag)); // Get the track, convert to uint and cast as a pointer
|
||||
koto_indexed_track_set_file_name(self, g_path_get_basename(self->path)); // Update our file name
|
||||
} else {
|
||||
koto_indexed_track_set_file_name(self, g_path_get_basename(self->path)); // Update our file name
|
||||
}
|
||||
|
@ -432,29 +449,14 @@ void koto_indexed_track_update_path(KotoIndexedTrack *self, const gchar *new_pat
|
|||
}
|
||||
|
||||
KotoIndexedTrack* koto_indexed_track_new(KotoIndexedAlbum *album, const gchar *path, guint *cd) {
|
||||
KotoIndexedArtist *artist;
|
||||
|
||||
gchar *artist_uuid;
|
||||
gchar *artist_name;
|
||||
gchar *album_uuid;
|
||||
gchar *album_name;
|
||||
|
||||
g_object_get(album, "artist", &artist, NULL); // Get the artist from our Album
|
||||
g_object_get(artist,
|
||||
"name", &artist_name,
|
||||
"uuid", &artist_uuid, // Get the artist UUID from the artist
|
||||
NULL);
|
||||
|
||||
g_object_get(album,
|
||||
"name", &album_name,
|
||||
"uuid", &album_uuid, // Get the album UUID from the album
|
||||
NULL);
|
||||
g_object_get(album, "artist-uuid", &artist_uuid, "uuid", &album_uuid, NULL); // Get the artist and album uuids from our Album
|
||||
|
||||
KotoIndexedTrack *track = g_object_new(KOTO_TYPE_INDEXED_TRACK,
|
||||
"artist-uuid", artist_uuid,
|
||||
"album-uuid", album_uuid,
|
||||
"artist", artist_name,
|
||||
"album", album_name,
|
||||
"do-initial-index", TRUE,
|
||||
"uuid", g_uuid_string_random(),
|
||||
"path", path,
|
||||
|
|
|
@ -338,6 +338,10 @@ void koto_button_set_text(KotoButton *self, gchar *text) {
|
|||
}
|
||||
|
||||
void koto_button_show_image(KotoButton *self, gboolean use_alt) {
|
||||
if (!KOTO_IS_BUTTON(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (use_alt && ((self->alt_icon_name == NULL) || (strcmp(self->alt_icon_name, "") == 0))) { // Don't have an alt icon set
|
||||
return;
|
||||
} else if (!use_alt && ((self->icon_name == NULL) || (strcmp(self->icon_name, "") == 0))) { // Don't have icon set
|
||||
|
|
|
@ -36,8 +36,8 @@ typedef enum {
|
|||
#define NUM_BUILTIN_SIZES 7
|
||||
|
||||
#define KOTO_TYPE_BUTTON (koto_button_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (KotoButton, koto_button, KOTO, BUTTON, GtkBox)
|
||||
#define KOTO_IS_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_BUTTON))
|
||||
|
||||
guint koto_get_pixbuf_size(KotoButtonPixbufSize size);
|
||||
|
||||
|
|
|
@ -58,8 +58,7 @@ static void koto_expander_get_property(GObject *obj, guint prop_id, GValue *val,
|
|||
static void koto_expander_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec);
|
||||
|
||||
static void koto_expander_class_init(KotoExpanderClass *c) {
|
||||
GObjectClass *gobject_class;
|
||||
gobject_class = G_OBJECT_CLASS(c);
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS(c);
|
||||
gobject_class->set_property = koto_expander_set_property;
|
||||
gobject_class->get_property = koto_expander_get_property;
|
||||
|
||||
|
|
|
@ -15,14 +15,19 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <gstreamer-1.0/gst/gst.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "playback/engine.h"
|
||||
#include "koto-button.h"
|
||||
#include "koto-config.h"
|
||||
#include "koto-playerbar.h"
|
||||
|
||||
extern KotoPlaybackEngine *playback_engine;
|
||||
|
||||
struct _KotoPlayerBar {
|
||||
GObject parent_instance;
|
||||
GtkWidget *main;
|
||||
GtkWidget *controls;
|
||||
|
||||
/* Sections */
|
||||
GtkWidget *playback_section;
|
||||
|
@ -39,6 +44,8 @@ struct _KotoPlayerBar {
|
|||
KotoButton *eq_button;
|
||||
GtkWidget *volume_button;
|
||||
|
||||
GtkWidget *progress_bar;
|
||||
|
||||
/* Selected Playback Section */
|
||||
|
||||
GtkWidget *playback_details_section;
|
||||
|
@ -65,23 +72,38 @@ static void koto_playerbar_class_init(KotoPlayerBarClass *c) {
|
|||
|
||||
static void koto_playerbar_constructed(GObject *obj) {
|
||||
KotoPlayerBar *self = KOTO_PLAYERBAR(obj);
|
||||
self->main = gtk_center_box_new();
|
||||
gtk_center_box_set_baseline_position(GTK_CENTER_BOX(self->main), GTK_BASELINE_POSITION_CENTER);
|
||||
self->main = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
self->progress_bar = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, 0, 120, 1); // Default to 120 as random max
|
||||
gtk_scale_set_draw_value(GTK_SCALE(self->progress_bar), FALSE);
|
||||
gtk_scale_set_digits(GTK_SCALE(self->progress_bar), 0);
|
||||
gtk_range_set_increments(GTK_RANGE(self->progress_bar), 1, 1);
|
||||
|
||||
self->controls = gtk_center_box_new();
|
||||
gtk_center_box_set_baseline_position(GTK_CENTER_BOX(self->controls), GTK_BASELINE_POSITION_CENTER);
|
||||
gtk_widget_add_css_class(self->main, "player-bar");
|
||||
|
||||
self->playback_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
self->primary_controls_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
self->secondary_controls_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
|
||||
gtk_center_box_set_start_widget(GTK_CENTER_BOX(self->main), GTK_WIDGET(self->primary_controls_section));
|
||||
gtk_center_box_set_center_widget(GTK_CENTER_BOX(self->main), GTK_WIDGET(self->playback_section));
|
||||
gtk_center_box_set_end_widget(GTK_CENTER_BOX(self->main), GTK_WIDGET(self->secondary_controls_section));
|
||||
gtk_center_box_set_start_widget(GTK_CENTER_BOX(self->controls), GTK_WIDGET(self->primary_controls_section));
|
||||
gtk_center_box_set_center_widget(GTK_CENTER_BOX(self->controls), GTK_WIDGET(self->playback_section));
|
||||
gtk_center_box_set_end_widget(GTK_CENTER_BOX(self->controls), GTK_WIDGET(self->secondary_controls_section));
|
||||
|
||||
koto_playerbar_create_playback_details(self);
|
||||
koto_playerbar_create_primary_controls(self);
|
||||
koto_playerbar_create_secondary_controls(self);
|
||||
|
||||
gtk_box_prepend(GTK_BOX(self->main), self->progress_bar);
|
||||
gtk_box_append(GTK_BOX(self->main), self->controls);
|
||||
|
||||
gtk_widget_set_hexpand(GTK_WIDGET(self->main), TRUE);
|
||||
|
||||
// Set up the bindings
|
||||
|
||||
g_signal_connect(playback_engine, "duration-changed", G_CALLBACK(koto_playerbar_handle_duration_change), self);
|
||||
g_signal_connect(playback_engine, "play-state-changed", G_CALLBACK(koto_playerbar_handle_engine_state_change), self);
|
||||
g_signal_connect(playback_engine, "progress-changed", G_CALLBACK(koto_playerbar_handle_progress_change), self);
|
||||
}
|
||||
|
||||
static void koto_playerbar_init(KotoPlayerBar *self) {
|
||||
|
@ -139,6 +161,19 @@ void koto_playerbar_create_primary_controls(KotoPlayerBar* bar) {
|
|||
if (bar->forward_button != NULL) {
|
||||
gtk_box_append(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->forward_button));
|
||||
}
|
||||
|
||||
|
||||
GtkGesture *back_controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
|
||||
g_signal_connect(back_controller, "pressed", G_CALLBACK(koto_playerbar_go_backwards), NULL);
|
||||
gtk_widget_add_controller(GTK_WIDGET(bar->back_button), GTK_EVENT_CONTROLLER(back_controller));
|
||||
|
||||
GtkGesture *controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
|
||||
g_signal_connect(controller, "pressed", G_CALLBACK(koto_playerbar_toggle_play_pause), NULL);
|
||||
gtk_widget_add_controller(GTK_WIDGET(bar->play_pause_button), GTK_EVENT_CONTROLLER(controller));
|
||||
|
||||
GtkGesture *forwards_controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
|
||||
g_signal_connect(forwards_controller, "pressed", G_CALLBACK(koto_playerbar_go_forwards), NULL);
|
||||
gtk_widget_add_controller(GTK_WIDGET(bar->forward_button), GTK_EVENT_CONTROLLER(forwards_controller));
|
||||
}
|
||||
|
||||
void koto_playerbar_create_secondary_controls(KotoPlayerBar* bar) {
|
||||
|
@ -170,6 +205,83 @@ void koto_playerbar_create_secondary_controls(KotoPlayerBar* bar) {
|
|||
}
|
||||
}
|
||||
|
||||
void koto_playerbar_go_backwards(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data) {
|
||||
(void) gesture; (void) n_press; (void) x; (void) y; (void) data;
|
||||
|
||||
koto_playback_engine_backwards(playback_engine);
|
||||
}
|
||||
|
||||
void koto_playerbar_go_forwards(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data) {
|
||||
(void) gesture; (void) n_press; (void) x; (void) y; (void) data;
|
||||
|
||||
koto_playback_engine_forwards(playback_engine);
|
||||
}
|
||||
|
||||
void koto_playerbar_handle_duration_change(KotoPlaybackEngine *engine, gpointer user_data) {
|
||||
if (!KOTO_IS_PLAYBACK_ENGINE(engine)) {
|
||||
return;
|
||||
}
|
||||
|
||||
KotoPlayerBar *bar = user_data;
|
||||
koto_playerbar_set_progressbar_duration(bar, koto_playback_engine_get_duration(engine));
|
||||
}
|
||||
|
||||
void koto_playerbar_handle_engine_state_change(KotoPlaybackEngine *engine, gpointer user_data) {
|
||||
if (!KOTO_IS_PLAYBACK_ENGINE(engine)) {
|
||||
return;
|
||||
}
|
||||
|
||||
KotoPlayerBar *bar = user_data;
|
||||
|
||||
if (!KOTO_IS_PLAYERBAR(bar)) {
|
||||
return;
|
||||
}
|
||||
|
||||
GstState new_state = koto_playback_engine_get_state(playback_engine);
|
||||
|
||||
if (new_state == GST_STATE_PLAYING) { // Now playing
|
||||
koto_button_show_image(bar->play_pause_button, TRUE); // Set to TRUE to show pause as the next action
|
||||
} else if (new_state == GST_STATE_PAUSED) { // Now paused
|
||||
koto_button_show_image(bar->play_pause_button, FALSE); // Set to FALSE to show play as the next action
|
||||
}
|
||||
}
|
||||
|
||||
void koto_playerbar_handle_progress_change(KotoPlaybackEngine *engine, gpointer user_data) {
|
||||
if (!KOTO_IS_PLAYBACK_ENGINE(engine)) {
|
||||
return;
|
||||
}
|
||||
|
||||
KotoPlayerBar *bar = user_data;
|
||||
koto_playerbar_set_progressbar_value(bar, koto_playback_engine_get_progress(engine));
|
||||
}
|
||||
|
||||
void koto_playerbar_reset_progressbar(KotoPlayerBar* bar) {
|
||||
gtk_range_set_range(GTK_RANGE(bar->progress_bar), 0, 0); // Reset range
|
||||
gtk_range_set_value(GTK_RANGE(bar->progress_bar), 0); // Set value to 0
|
||||
}
|
||||
|
||||
void koto_playerbar_set_progressbar_duration(KotoPlayerBar* bar, gint64 duration) {
|
||||
if (duration < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_range_set_range(GTK_RANGE(bar->progress_bar), 0, duration);
|
||||
}
|
||||
|
||||
void koto_playerbar_set_progressbar_value(KotoPlayerBar* bar, gint64 progress) {
|
||||
if (progress < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_range_set_value(GTK_RANGE(bar->progress_bar), progress);
|
||||
}
|
||||
|
||||
void koto_playerbar_toggle_play_pause(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data) {
|
||||
(void) gesture; (void) n_press; (void) x; (void) y; (void) data;
|
||||
|
||||
koto_playback_engine_toggle(playback_engine);
|
||||
}
|
||||
|
||||
GtkWidget* koto_playerbar_get_main(KotoPlayerBar* bar) {
|
||||
return bar->main;
|
||||
}
|
||||
|
|
|
@ -16,18 +16,29 @@
|
|||
*/
|
||||
|
||||
#pragma once
|
||||
#include <gstreamer-1.0/gst/gst.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "playback/engine.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define KOTO_TYPE_PLAYERBAR (koto_playerbar_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (KotoPlayerBar, koto_playerbar, KOTO, PLAYERBAR, GObject)
|
||||
#define KOTO_IS_PLAYERBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_PLAYERBAR))
|
||||
|
||||
KotoPlayerBar* koto_playerbar_new (void);
|
||||
GtkWidget* koto_playerbar_get_main(KotoPlayerBar* bar);
|
||||
void koto_playerbar_create_playback_details(KotoPlayerBar* bar);
|
||||
void koto_playerbar_create_primary_controls(KotoPlayerBar* bar);
|
||||
void koto_playerbar_create_secondary_controls(KotoPlayerBar* bar);
|
||||
void koto_playerbar_go_backwards(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data);
|
||||
void koto_playerbar_go_forwards(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data);
|
||||
void koto_playerbar_handle_duration_change(KotoPlaybackEngine *engine, gpointer user_data);
|
||||
void koto_playerbar_handle_engine_state_change(KotoPlaybackEngine *engine, gpointer user_data);
|
||||
void koto_playerbar_handle_progress_change(KotoPlaybackEngine *engine, gpointer user_data);
|
||||
void koto_playerbar_reset_progressbar(KotoPlayerBar* bar);
|
||||
void koto_playerbar_set_progressbar_duration(KotoPlayerBar* bar, gint64 duration);
|
||||
void koto_playerbar_set_progressbar_value(KotoPlayerBar* bar, gint64 progress);
|
||||
void koto_playerbar_toggle_play_pause(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data);
|
||||
|
||||
G_END_DECLS
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <gtk-4.0/gdk/x11/gdkx.h>
|
||||
#include "indexer/structs.h"
|
||||
#include "pages/music/music-local.h"
|
||||
#include "playback/engine.h"
|
||||
#include "playlist/current.h"
|
||||
#include "koto-config.h"
|
||||
#include "koto-nav.h"
|
||||
|
@ -25,6 +26,7 @@
|
|||
#include "koto-window.h"
|
||||
|
||||
extern KotoCurrentPlaylist *current_playlist;
|
||||
extern KotoPlaybackEngine *playback_engine;
|
||||
|
||||
struct _KotoWindow {
|
||||
GtkApplicationWindow parent_instance;
|
||||
|
@ -51,6 +53,7 @@ static void koto_window_class_init (KotoWindowClass *klass) {
|
|||
|
||||
static void koto_window_init (KotoWindow *self) {
|
||||
current_playlist = koto_current_playlist_new();
|
||||
playback_engine = koto_playback_engine_new();
|
||||
|
||||
GtkCssProvider* provider = gtk_css_provider_new();
|
||||
gtk_css_provider_load_from_resource(provider, "/com/github/joshstrobl/koto/style.css");
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
#include <gstreamer-1.0/gst/gst.h>
|
||||
#include "db/cartographer.h"
|
||||
#include "db/db.h"
|
||||
|
||||
|
@ -53,6 +54,8 @@ int main (int argc, char *argv[]) {
|
|||
textdomain (GETTEXT_PACKAGE);
|
||||
|
||||
gtk_init();
|
||||
gst_init(&argc, &argv);
|
||||
|
||||
koto_maps = koto_cartographer_new(); // Create our new cartographer and their collection of maps
|
||||
open_db(); // Open our database
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ koto_sources = [
|
|||
'pages/music/artist-view.c',
|
||||
'pages/music/disc-view.c',
|
||||
'pages/music/music-local.c',
|
||||
'playback/engine.c',
|
||||
'playlist/current.c',
|
||||
'playlist/playlist.c',
|
||||
'main.c',
|
||||
|
@ -26,6 +27,8 @@ koto_sources = [
|
|||
koto_deps = [
|
||||
dependency('glib-2.0', version: '>= 2.66'),
|
||||
dependency('gio-2.0', version: '>= 2.66'),
|
||||
dependency('gstreamer-1.0', version: '>= 1.18'),
|
||||
dependency('gstreamer-player-1.0', version: '>= 1.18'),
|
||||
dependency('gtk4', version: '>= 4.0'),
|
||||
dependency('libmagic', version: '>=5.39'),
|
||||
dependency('sqlite3', version: '>=3.34'),
|
||||
|
|
|
@ -17,13 +17,16 @@
|
|||
|
||||
#include <glib-2.0/glib.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "../../koto-button.h"
|
||||
#include "../../db/cartographer.h"
|
||||
#include "../../indexer/structs.h"
|
||||
#include "../../koto-button.h"
|
||||
#include "album-view.h"
|
||||
#include "disc-view.h"
|
||||
#include "koto-config.h"
|
||||
#include "koto-utils.h"
|
||||
|
||||
extern KotoCartographer *koto_maps;
|
||||
|
||||
struct _KotoAlbumView {
|
||||
GObject parent_instance;
|
||||
KotoIndexedAlbum *album;
|
||||
|
@ -176,10 +179,15 @@ void koto_album_view_set_album(KotoAlbumView *self, KotoIndexedAlbum *album) {
|
|||
gtk_box_prepend(GTK_BOX(self->album_tracks_box), self->album_label); // Prepend our new label to the album + tracks box
|
||||
|
||||
GHashTable *discs = g_hash_table_new(g_str_hash, g_str_equal);
|
||||
GList *tracks = koto_indexed_album_get_files(album); // Get the tracks for this album
|
||||
GList *tracks = koto_indexed_album_get_tracks(album); // Get the tracks for this album
|
||||
|
||||
for (guint i = 0; i < g_list_length(tracks); i++) {
|
||||
KotoIndexedTrack *track = (KotoIndexedTrack*) g_list_nth_data(tracks, i); // Get the
|
||||
KotoIndexedTrack *track = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) g_list_nth_data(tracks, i)); // Get the track by its UUID
|
||||
|
||||
if (track == NULL) { // Track doesn't exist
|
||||
continue;
|
||||
}
|
||||
|
||||
guint *disc_number;
|
||||
g_object_get(track, "cd", &disc_number, NULL);
|
||||
gchar *disc_num_as_str = g_strdup_printf("%u", GPOINTER_TO_UINT(disc_number));
|
||||
|
|
|
@ -17,12 +17,15 @@
|
|||
|
||||
#include <glib-2.0/glib.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "../../db/cartographer.h"
|
||||
#include "../../indexer/structs.h"
|
||||
#include "album-view.h"
|
||||
#include "artist-view.h"
|
||||
#include "koto-config.h"
|
||||
#include "koto-utils.h"
|
||||
|
||||
extern KotoCartographer *koto_maps;
|
||||
|
||||
struct _KotoArtistView {
|
||||
GObject parent_instance;
|
||||
KotoIndexedArtist *artist;
|
||||
|
@ -160,9 +163,12 @@ void koto_artist_view_add_artist(KotoArtistView *self, KotoIndexedArtist *artist
|
|||
|
||||
GList *a;
|
||||
for (a = albums; a != NULL; a = a->next) {
|
||||
KotoIndexedAlbum *album = (KotoIndexedAlbum*) a->data;
|
||||
KotoIndexedAlbum *album = koto_cartographer_get_album_by_uuid(koto_maps, (gchar*) a->data);
|
||||
|
||||
if (album != NULL) {
|
||||
koto_artist_view_add_album(self, album); // Add the album
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GtkWidget* koto_artist_view_get_main(KotoArtistView *self) {
|
||||
|
|
|
@ -16,10 +16,13 @@
|
|||
*/
|
||||
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "../../db/cartographer.h"
|
||||
#include "../../indexer/structs.h"
|
||||
#include "../../koto-track-item.h"
|
||||
#include "disc-view.h"
|
||||
|
||||
extern KotoCartographer *koto_maps;
|
||||
|
||||
struct _KotoDiscView {
|
||||
GtkBox parent_instance;
|
||||
KotoIndexedAlbum *album;
|
||||
|
@ -138,25 +141,26 @@ void koto_disc_view_set_album(KotoDiscView *self, KotoIndexedAlbum *album) {
|
|||
|
||||
self->list = gtk_list_box_new(); // Create our list of our tracks
|
||||
gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->list), GTK_SELECTION_MULTIPLE);
|
||||
gtk_list_box_set_sort_func(GTK_LIST_BOX(self->list), koto_album_view_sort_tracks, NULL, NULL); // Ensure we can sort our tracks
|
||||
gtk_widget_add_css_class(self->list, "track-list");
|
||||
gtk_widget_set_size_request(self->list, 600, -1);
|
||||
gtk_box_append(GTK_BOX(self), self->list);
|
||||
|
||||
GList *t;
|
||||
for (t = koto_indexed_album_get_files(self->album); t != NULL; t = t->next) { // For each file / track
|
||||
KotoIndexedTrack *track = (KotoIndexedTrack*) t->data;
|
||||
g_list_foreach(koto_indexed_album_get_tracks(self->album), koto_disc_view_list_tracks, self);
|
||||
}
|
||||
|
||||
void koto_disc_view_list_tracks(gpointer data, gpointer selfptr) {
|
||||
KotoDiscView *self = (KotoDiscView*) selfptr;
|
||||
KotoIndexedTrack *track = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) data); // Get the track by its UUID
|
||||
|
||||
guint *disc_number;
|
||||
g_object_get(track, "cd", &disc_number, NULL); // get the disc number
|
||||
|
||||
if (GPOINTER_TO_UINT(self->disc_number) != GPOINTER_TO_UINT(disc_number)) { // Track does not belong to this CD
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
KotoTrackItem *track_item = koto_track_item_new(track); // Create our new track item
|
||||
gtk_list_box_prepend(GTK_LIST_BOX(self->list), GTK_WIDGET(track_item)); // Add to our tracks list box
|
||||
}
|
||||
gtk_list_box_append(GTK_LIST_BOX(self->list), GTK_WIDGET(track_item)); // Add to our tracks list box
|
||||
}
|
||||
|
||||
void koto_disc_view_set_disc_number(KotoDiscView *self, guint disc_number) {
|
||||
|
@ -176,38 +180,6 @@ void koto_disc_view_set_disc_label_visible(KotoDiscView *self, gboolean visible)
|
|||
(visible) ? gtk_widget_show(self->header) : gtk_widget_hide(self->header);
|
||||
}
|
||||
|
||||
int koto_album_view_sort_tracks(GtkListBoxRow *track1, GtkListBoxRow *track2, gpointer user_data) {
|
||||
(void) user_data;
|
||||
KotoTrackItem *track1_item = KOTO_TRACK_ITEM(gtk_list_box_row_get_child(track1));
|
||||
KotoTrackItem *track2_item = KOTO_TRACK_ITEM(gtk_list_box_row_get_child(track2));
|
||||
|
||||
KotoIndexedTrack *track1_file;
|
||||
KotoIndexedTrack *track2_file;
|
||||
|
||||
g_object_get(track1_item, "track", &track1_file, NULL);
|
||||
g_object_get(track2_item, "track", &track2_file, NULL);
|
||||
|
||||
guint16 *track1_pos;
|
||||
guint16 *track2_pos;
|
||||
|
||||
g_object_get(track1_file, "position", &track1_pos, NULL);
|
||||
g_object_get(track2_file, "position", &track2_pos, NULL);
|
||||
|
||||
if (track1_pos == track2_pos) { // Identical positions (like reported as 0)
|
||||
gchar *track1_name;
|
||||
gchar *track2_name;
|
||||
|
||||
g_object_get(track1_file, "parsed-name", &track1_name, NULL);
|
||||
g_object_get(track2_file, "parsed-name", &track2_name, NULL);
|
||||
|
||||
return g_utf8_collate(track1_name, track2_name);
|
||||
} else if (track1_pos < track2_pos) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
KotoDiscView* koto_disc_view_new(KotoIndexedAlbum *album, guint *disc_number) {
|
||||
return g_object_new(KOTO_TYPE_DISC_VIEW,
|
||||
"disc", disc_number,
|
||||
|
|
|
@ -28,9 +28,9 @@ G_BEGIN_DECLS
|
|||
G_DECLARE_FINAL_TYPE(KotoDiscView, koto_disc_view, KOTO, DISC_VIEW, GtkBox)
|
||||
|
||||
KotoDiscView* koto_disc_view_new(KotoIndexedAlbum *album, guint *disc);
|
||||
void koto_disc_view_list_tracks(gpointer data, gpointer selfptr);
|
||||
void koto_disc_view_set_album(KotoDiscView *self, KotoIndexedAlbum *album);
|
||||
void koto_disc_view_set_disc_label_visible(KotoDiscView *self, gboolean visible);
|
||||
void koto_disc_view_set_disc_number(KotoDiscView *self, guint disc_number);
|
||||
int koto_album_view_sort_tracks(GtkListBoxRow *track1, GtkListBoxRow *track2, gpointer user_data);
|
||||
|
||||
G_END_DECLS
|
||||
|
|
|
@ -17,11 +17,14 @@
|
|||
|
||||
#include <glib-2.0/glib.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "../../db/cartographer.h"
|
||||
#include "../../indexer/structs.h"
|
||||
#include "koto-button.h"
|
||||
#include "koto-config.h"
|
||||
#include "music-local.h"
|
||||
|
||||
extern KotoCartographer *koto_maps;
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_LIB,
|
||||
|
@ -175,9 +178,12 @@ void koto_page_music_local_set_library(KotoPageMusicLocal *self, KotoIndexedLibr
|
|||
|
||||
g_hash_table_iter_init(&artist_list_iter, artists);
|
||||
while (g_hash_table_iter_next(&artist_list_iter, &artist_key, &artist_data)) { // For each of the music artists
|
||||
KotoIndexedArtist *artist = (KotoIndexedArtist*) artist_data; // Cast our data as a KotoIndexedArtist
|
||||
KotoIndexedArtist *artist = koto_cartographer_get_artist_by_uuid(koto_maps, (gchar*) artist_data); // Cast our data as a KotoIndexedArtist
|
||||
|
||||
if (artist != NULL) {
|
||||
koto_page_music_local_add_artist(self, artist);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int koto_page_music_local_sort_artists(GtkListBoxRow *artist1, GtkListBoxRow *artist2, gpointer user_data) {
|
||||
|
|
314
src/playback/engine.c
Normal file
314
src/playback/engine.c
Normal file
|
@ -0,0 +1,314 @@
|
|||
/* engine.c
|
||||
*
|
||||
* Copyright 2021 Joshua Strobl
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <glib-2.0/glib.h>
|
||||
#include <gstreamer-1.0/gst/gst.h>
|
||||
#include <gstreamer-1.0/gst/player/player.h>
|
||||
#include "../db/cartographer.h"
|
||||
#include "../playlist/current.h"
|
||||
#include "../indexer/structs.h"
|
||||
#include "engine.h"
|
||||
|
||||
enum {
|
||||
SIGNAL_DURATION_CHANGE,
|
||||
SIGNAL_PLAY_STATE_CHANGE,
|
||||
SIGNAL_PROGRESS_CHANGE,
|
||||
N_SIGNALS
|
||||
};
|
||||
|
||||
static glong NS = 1000000000;
|
||||
static guint playback_engine_signals[N_SIGNALS] = { 0 };
|
||||
|
||||
extern KotoCartographer *koto_maps;
|
||||
extern KotoCurrentPlaylist *current_playlist;
|
||||
|
||||
KotoPlaybackEngine *playback_engine;
|
||||
|
||||
struct _KotoPlaybackEngine {
|
||||
GObject parent_class;
|
||||
GstElement *player;
|
||||
GstElement *playbin;
|
||||
GstElement *suppress_video;
|
||||
|
||||
GstBus *monitor;
|
||||
|
||||
gboolean is_paused;
|
||||
gboolean is_playing;
|
||||
gboolean is_muted;
|
||||
gboolean is_repeat_enabled;
|
||||
gboolean is_shuffle_enabled;
|
||||
gboolean requested_playing;
|
||||
guint playback_position;
|
||||
gdouble volume;
|
||||
};
|
||||
|
||||
struct _KotoPlaybackEngineClass {
|
||||
GObjectClass parent_class;
|
||||
|
||||
void (* duration_changed) (KotoPlaybackEngine *engine, gint64 duration);
|
||||
void (* play_state_changed) (KotoPlaybackEngine *engine);
|
||||
void (* progress_changed) (KotoPlaybackEngine *engine, gint64 progress);
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(KotoPlaybackEngine, koto_playback_engine, G_TYPE_OBJECT);
|
||||
|
||||
static void koto_playback_engine_class_init(KotoPlaybackEngineClass *c) {
|
||||
c->play_state_changed = NULL;
|
||||
|
||||
GObjectClass *gobject_class;
|
||||
gobject_class = G_OBJECT_CLASS(c);
|
||||
|
||||
playback_engine_signals[SIGNAL_DURATION_CHANGE] = g_signal_new(
|
||||
"duration-changed",
|
||||
G_TYPE_FROM_CLASS(gobject_class),
|
||||
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
||||
G_STRUCT_OFFSET(KotoPlaybackEngineClass, duration_changed),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
0
|
||||
);
|
||||
|
||||
playback_engine_signals[SIGNAL_PLAY_STATE_CHANGE] = g_signal_new(
|
||||
"play-state-changed",
|
||||
G_TYPE_FROM_CLASS(gobject_class),
|
||||
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
||||
G_STRUCT_OFFSET(KotoPlaybackEngineClass, play_state_changed),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
0
|
||||
);
|
||||
|
||||
playback_engine_signals[SIGNAL_PROGRESS_CHANGE] = g_signal_new(
|
||||
"progress-changed",
|
||||
G_TYPE_FROM_CLASS(gobject_class),
|
||||
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
||||
G_STRUCT_OFFSET(KotoPlaybackEngineClass, progress_changed),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
static void koto_playback_engine_init(KotoPlaybackEngine *self) {
|
||||
self->player = gst_pipeline_new("player");
|
||||
self->playbin = gst_element_factory_make("playbin", NULL);
|
||||
self->suppress_video = gst_element_factory_make("fakesink", "suppress-video");
|
||||
|
||||
g_object_set(self->playbin, "video-sink", self->suppress_video, NULL);
|
||||
g_object_set(self->playbin, "volume", 0.5, NULL);
|
||||
|
||||
gst_bin_add(GST_BIN(self->player), self->playbin);
|
||||
self->monitor = gst_bus_new(); // Get the bus for the playbin
|
||||
|
||||
if (GST_IS_BUS(self->monitor)) {
|
||||
gst_bus_add_watch(self->monitor, koto_playback_engine_monitor_changed, self);
|
||||
gst_element_set_bus(self->playbin, self->monitor); // Set our bus to monitor changes
|
||||
}
|
||||
|
||||
self->is_muted = FALSE;
|
||||
self->is_playing = FALSE;
|
||||
self->is_paused = FALSE;
|
||||
self->is_repeat_enabled = FALSE;
|
||||
self->is_shuffle_enabled = FALSE;
|
||||
self->requested_playing = FALSE;
|
||||
|
||||
if (KOTO_IS_CURRENT_PLAYLIST(current_playlist)) {
|
||||
g_signal_connect(current_playlist, "notify::current-playlist", G_CALLBACK(koto_playback_engine_current_playlist_changed), NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void koto_playback_engine_backwards(KotoPlaybackEngine *self) {
|
||||
KotoPlaylist *playlist = koto_current_playlist_get_playlist(current_playlist); // Get the current playlist
|
||||
|
||||
if (!KOTO_IS_PLAYLIST(playlist)) { // If we do not have a playlist currently
|
||||
return;
|
||||
}
|
||||
|
||||
koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_previous(playlist));
|
||||
}
|
||||
|
||||
void koto_playback_engine_current_playlist_changed() {
|
||||
if (!KOTO_IS_PLAYBACK_ENGINE(playback_engine)) {
|
||||
return;
|
||||
}
|
||||
|
||||
KotoPlaylist *playlist = koto_current_playlist_get_playlist(current_playlist); // Get the current playlist
|
||||
|
||||
if (!KOTO_IS_PLAYLIST(playlist)) { // If we do not have a playlist currently
|
||||
return;
|
||||
}
|
||||
|
||||
koto_playback_engine_set_track_by_uuid(playback_engine, koto_playlist_get_current_uuid(playlist)); // Get the current UUID
|
||||
}
|
||||
|
||||
void koto_playback_engine_forwards(KotoPlaybackEngine *self) {
|
||||
KotoPlaylist *playlist = koto_current_playlist_get_playlist(current_playlist); // Get the current playlist
|
||||
|
||||
if (!KOTO_IS_PLAYLIST(playlist)) { // If we do not have a playlist currently
|
||||
return;
|
||||
}
|
||||
|
||||
koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_next(playlist));
|
||||
}
|
||||
|
||||
gint64 koto_playback_engine_get_duration(KotoPlaybackEngine *self) {
|
||||
gint64 duration = 0;
|
||||
if (gst_element_query_duration(self->playbin, GST_FORMAT_TIME, &duration)) {
|
||||
duration = duration / NS; // Divide by NS to get seconds
|
||||
}
|
||||
|
||||
return duration;
|
||||
}
|
||||
|
||||
GstState koto_playback_engine_get_state(KotoPlaybackEngine *self) {
|
||||
GstState current_state;
|
||||
GstStateChangeReturn ret = gst_element_get_state(self->playbin, ¤t_state, NULL, GST_SECOND); // Get the current state, allowing up to a second to get it
|
||||
|
||||
if (ret != GST_STATE_CHANGE_SUCCESS) { // Got the data we need
|
||||
return GST_STATE_NULL;
|
||||
}
|
||||
|
||||
return current_state;
|
||||
}
|
||||
|
||||
gint64 koto_playback_engine_get_progress(KotoPlaybackEngine *self) {
|
||||
gint64 progress = 0;
|
||||
if (gst_element_query_position(self->playbin, GST_FORMAT_TIME, &progress)) {
|
||||
progress = progress / NS; // Divide by NS to get seconds
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
gboolean koto_playback_engine_monitor_changed(GstBus *bus, GstMessage *msg, gpointer user_data) {
|
||||
(void) bus;
|
||||
KotoPlaybackEngine *self = user_data;
|
||||
|
||||
switch (GST_MESSAGE_TYPE(msg)) {
|
||||
case GST_MESSAGE_ASYNC_DONE: { // Finished loading
|
||||
if (self->requested_playing) {
|
||||
self->is_playing = TRUE;
|
||||
self->is_paused = FALSE;
|
||||
g_timeout_add(50, koto_playback_engine_update_progress, self);
|
||||
gst_element_set_state(self->playbin, GST_STATE_PLAYING); // Make double sure the state is playing
|
||||
g_signal_emit(self, playback_engine_signals[SIGNAL_DURATION_CHANGE], 0); // Emit our duration signal
|
||||
g_signal_emit(self, playback_engine_signals[SIGNAL_PLAY_STATE_CHANGE], 0); // Emit our playing signal
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GST_MESSAGE_STATE_CHANGED: { // State changed
|
||||
GstState old_state;
|
||||
GstState requested_state;
|
||||
GstState new_state;
|
||||
gst_message_parse_state_changed(msg, &old_state, &requested_state, &new_state);
|
||||
|
||||
if ((old_state == GST_STATE_PLAYING) && (requested_state == GST_STATE_PAUSED)) {
|
||||
self->is_playing = FALSE;
|
||||
self->is_paused = TRUE;
|
||||
|
||||
g_signal_emit(self, playback_engine_signals[SIGNAL_PLAY_STATE_CHANGE], 0); // Emit our paused signal
|
||||
} else if (requested_state == GST_STATE_PLAYING && !self->is_playing) {
|
||||
self->is_playing = TRUE;
|
||||
self->is_paused = FALSE;
|
||||
g_signal_emit(self, playback_engine_signals[SIGNAL_PLAY_STATE_CHANGE], 0); // Emit our paused signal
|
||||
} else if (((old_state == GST_STATE_PLAYING) || (old_state == GST_STATE_PAUSED)) && (new_state == GST_STATE_NULL)) { // If we're freeing resources
|
||||
gst_bus_post(self->monitor, gst_message_new_reset_time(GST_OBJECT(self->playbin), 0));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case GST_MESSAGE_EOS: { // Reached end of stream
|
||||
koto_playback_engine_forwards(self); // Go to the next track
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void koto_playback_engine_play(KotoPlaybackEngine *self) {
|
||||
self->requested_playing = TRUE;
|
||||
gst_element_set_state(self->playbin, GST_STATE_PLAYING); // Set our state to play
|
||||
}
|
||||
|
||||
void koto_playback_engine_pause(KotoPlaybackEngine *self) {
|
||||
self->requested_playing = FALSE;
|
||||
gst_element_set_state(self->playbin, GST_STATE_PAUSED); // Set our state to paused
|
||||
}
|
||||
|
||||
void koto_playback_engine_set_track_by_uuid(KotoPlaybackEngine *self, gchar *track_uuid) {
|
||||
if (track_uuid == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
KotoIndexedTrack *track = koto_cartographer_get_track_by_uuid(koto_maps, track_uuid); // Get the track from cartographer
|
||||
|
||||
if (!KOTO_IS_INDEXED_TRACK(track)) { // Not a track
|
||||
return;
|
||||
}
|
||||
|
||||
gchar *track_file_path = NULL;
|
||||
g_object_get(track, "path", &track_file_path, NULL); // Get the path to the track
|
||||
|
||||
koto_playback_engine_stop(self); // Stop current track
|
||||
|
||||
gchar *gst_filename = gst_filename_to_uri(track_file_path, NULL); // Get the gst supported file naem
|
||||
|
||||
g_object_set(self->playbin, "uri", gst_filename, NULL);
|
||||
g_free(gst_filename); // Free the filename
|
||||
|
||||
koto_playback_engine_play(self); // Play the new track
|
||||
}
|
||||
|
||||
void koto_playback_engine_stop(KotoPlaybackEngine *self) {
|
||||
gst_element_set_state(self->playbin, GST_STATE_NULL);
|
||||
GstPad *pad = gst_element_get_static_pad(self->playbin, "audio-sink"); // Get the static pad of the audio element
|
||||
|
||||
if (!GST_IS_PAD(pad)) {
|
||||
return;
|
||||
}
|
||||
|
||||
gst_pad_set_offset(pad, 0); // Change offset
|
||||
}
|
||||
|
||||
void koto_playback_engine_toggle(KotoPlaybackEngine *self) {
|
||||
if (koto_playback_engine_get_state(self) == GST_STATE_PLAYING) { // Currently playing
|
||||
koto_playback_engine_pause(self); // Pause
|
||||
} else {
|
||||
koto_playback_engine_play(self); // Play
|
||||
}
|
||||
}
|
||||
|
||||
gboolean koto_playback_engine_update_progress(gpointer user_data) {
|
||||
KotoPlaybackEngine *self = user_data;
|
||||
if (self->is_playing) { // Is playing
|
||||
g_signal_emit(self, playback_engine_signals[SIGNAL_PROGRESS_CHANGE], 0); // Emit our progress change signal
|
||||
}
|
||||
|
||||
return self->is_playing;
|
||||
}
|
||||
|
||||
KotoPlaybackEngine* koto_playback_engine_new() {
|
||||
return g_object_new(KOTO_TYPE_PLAYBACK_ENGINE, NULL);
|
||||
}
|
63
src/playback/engine.h
Normal file
63
src/playback/engine.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
/* engine.h
|
||||
*
|
||||
* Copyright 2021 Joshua Strobl
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <glib-2.0/glib-object.h>
|
||||
#include <gstreamer-1.0/gst/gst.h>
|
||||
#include <gstreamer-1.0/gst/player/player.h>
|
||||
#include "../playlist/current.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* Type Definition
|
||||
**/
|
||||
|
||||
#define KOTO_TYPE_PLAYBACK_ENGINE (koto_playback_engine_get_type())
|
||||
#define KOTO_PLAYBACK_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), KOTO_TYPE_PLAYBACK_ENGINE, KotoPlaybackEngine))
|
||||
#define KOTO_IS_PLAYBACK_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_PLAYBACK_ENGINE))
|
||||
|
||||
typedef struct _KotoPlaybackEngine KotoPlaybackEngine;
|
||||
typedef struct _KotoPlaybackEngineClass KotoPlaybackEngineClass;
|
||||
|
||||
GLIB_AVAILABLE_IN_ALL
|
||||
GType koto_playback_engine_get_type(void) G_GNUC_CONST;
|
||||
|
||||
/**
|
||||
* Playback Engine Functions
|
||||
**/
|
||||
|
||||
KotoPlaybackEngine* koto_playback_engine_new();
|
||||
void koto_playback_engine_backwards(KotoPlaybackEngine *self);
|
||||
void koto_playback_engine_current_playlist_changed();
|
||||
void koto_playback_engine_forwards(KotoPlaybackEngine *self);
|
||||
gint64 koto_playback_engine_get_duration(KotoPlaybackEngine *self);
|
||||
GstState koto_playback_engine_get_state(KotoPlaybackEngine *self);
|
||||
gint64 koto_playback_engine_get_progress(KotoPlaybackEngine *self);
|
||||
void koto_playback_engine_mute(KotoPlaybackEngine *self);
|
||||
gboolean koto_playback_engine_monitor_changed(GstBus *bus, GstMessage *msg, gpointer user_data);
|
||||
void koto_playback_engine_pause(KotoPlaybackEngine *self);
|
||||
void koto_playback_engine_play(KotoPlaybackEngine *self);
|
||||
void koto_playback_engine_toggle(KotoPlaybackEngine *self);
|
||||
void koto_playback_engine_set_position(KotoPlaybackEngine *self, guint position);
|
||||
void koto_playback_engine_set_repeat(KotoPlaybackEngine *self, gboolean enable);
|
||||
void koto_playback_engine_set_shuffle(KotoPlaybackEngine *self, gboolean enable);
|
||||
void koto_playback_engine_set_track_by_uuid(KotoPlaybackEngine *self, gchar *track_uuid);
|
||||
void koto_playback_engine_set_volume(KotoPlaybackEngine *self, gdouble volume);
|
||||
void koto_playback_engine_stop(KotoPlaybackEngine *self);
|
||||
void koto_playback_engine_update_duration(KotoPlaybackEngine *self);
|
||||
gboolean koto_playback_engine_update_progress(gpointer user_data);
|
|
@ -18,6 +18,14 @@
|
|||
#include <glib-2.0/glib-object.h>
|
||||
#include "current.h"
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_CURRENT_PLAYLIST,
|
||||
N_PROPERTIES
|
||||
};
|
||||
|
||||
static GParamSpec *props[N_PROPERTIES] = { NULL, };
|
||||
|
||||
KotoCurrentPlaylist *current_playlist = NULL;
|
||||
|
||||
struct _KotoCurrentPlaylist {
|
||||
|
@ -27,14 +35,56 @@ struct _KotoCurrentPlaylist {
|
|||
|
||||
G_DEFINE_TYPE(KotoCurrentPlaylist, koto_current_playlist, G_TYPE_OBJECT);
|
||||
|
||||
static void koto_current_playlist_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec);
|
||||
static void koto_current_playlist_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec);
|
||||
|
||||
static void koto_current_playlist_class_init(KotoCurrentPlaylistClass *c) {
|
||||
(void) c;
|
||||
GObjectClass *gobject_class;
|
||||
gobject_class = G_OBJECT_CLASS(c);
|
||||
gobject_class->set_property = koto_current_playlist_set_property;
|
||||
gobject_class->get_property = koto_current_playlist_get_property;
|
||||
|
||||
props[PROP_CURRENT_PLAYLIST] = g_param_spec_object(
|
||||
"current-playlist",
|
||||
"Current Playlist",
|
||||
"Current Playlist",
|
||||
KOTO_TYPE_PLAYLIST,
|
||||
G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
|
||||
}
|
||||
|
||||
static void koto_current_playlist_init(KotoCurrentPlaylist *self) {
|
||||
self->current_playlist = NULL;
|
||||
}
|
||||
|
||||
void koto_current_playlist_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) {
|
||||
KotoCurrentPlaylist *self = KOTO_CURRENT_PLAYLIST(obj);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_CURRENT_PLAYLIST:
|
||||
g_value_set_object(val, self->current_playlist);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void koto_current_playlist_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec) {
|
||||
KotoCurrentPlaylist *self = KOTO_CURRENT_PLAYLIST(obj);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_CURRENT_PLAYLIST:
|
||||
koto_current_playlist_set_playlist(self, (KotoPlaylist*) g_value_get_object(val));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
KotoPlaylist* koto_current_playlist_get_playlist(KotoCurrentPlaylist *self) {
|
||||
return self->current_playlist;
|
||||
}
|
||||
|
@ -44,15 +94,26 @@ void koto_current_playlist_set_playlist(KotoCurrentPlaylist *self, KotoPlaylist
|
|||
return;
|
||||
}
|
||||
|
||||
if (KOTO_IS_PLAYLIST(self->current_playlist)) {
|
||||
// TODO: Save current playlist state if needed
|
||||
if (!KOTO_IS_PLAYLIST(playlist)) { // Not a playlist
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->current_playlist != NULL && KOTO_IS_PLAYLIST(self->current_playlist)) {
|
||||
gboolean *is_temp = FALSE;
|
||||
g_object_get(self->current_playlist, "ephemeral", &is_temp, NULL); // Get the current ephemeral value
|
||||
|
||||
if (is_temp) { // Is a temporary playlist
|
||||
koto_playlist_unmap(self->current_playlist); // Unmap the playlist if needed
|
||||
} else { // Not a temporary playlist
|
||||
koto_playlist_commit(self->current_playlist); // Save the current playlist
|
||||
}
|
||||
|
||||
g_object_unref(self->current_playlist); // Unreference
|
||||
}
|
||||
|
||||
self->current_playlist = playlist;
|
||||
g_object_ref(playlist); // Increment the reference
|
||||
g_object_notify(G_OBJECT(self), "current-playlist-changed");
|
||||
koto_playlist_debug(self->current_playlist);
|
||||
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_CURRENT_PLAYLIST]);
|
||||
}
|
||||
|
||||
KotoCurrentPlaylist* koto_current_playlist_new() {
|
||||
|
|
|
@ -18,8 +18,10 @@
|
|||
#include <glib-2.0/glib.h>
|
||||
#include <magic.h>
|
||||
#include <sqlite3.h>
|
||||
#include "../db/cartographer.h"
|
||||
#include "playlist.h"
|
||||
|
||||
extern KotoCartographer *koto_maps;
|
||||
extern sqlite3 *koto_db;
|
||||
|
||||
struct _KotoPlaylist {
|
||||
|
@ -28,8 +30,11 @@ struct _KotoPlaylist {
|
|||
gchar *name;
|
||||
gchar *art_path;
|
||||
guint current_position;
|
||||
gboolean ephemeral;
|
||||
gboolean is_shuffle_enabled;
|
||||
|
||||
GQueue *tracks;
|
||||
GQueue *played_tracks;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(KotoPlaylist, koto_playlist, G_TYPE_OBJECT);
|
||||
|
@ -39,6 +44,7 @@ enum {
|
|||
PROP_UUID,
|
||||
PROP_NAME,
|
||||
PROP_ART_PATH,
|
||||
PROP_EPHEMERAL,
|
||||
N_PROPERTIES,
|
||||
};
|
||||
|
||||
|
@ -76,6 +82,14 @@ static void koto_playlist_class_init(KotoPlaylistClass *c) {
|
|||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
props[PROP_EPHEMERAL] = g_param_spec_boolean(
|
||||
"ephemeral",
|
||||
"Is the playlist ephemeral (temporary)",
|
||||
"Is the playlist ephemeral (temporary)",
|
||||
FALSE,
|
||||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
|
||||
}
|
||||
|
||||
|
@ -92,6 +106,9 @@ static void koto_playlist_get_property(GObject *obj, guint prop_id, GValue *val,
|
|||
case PROP_ART_PATH:
|
||||
g_value_set_string(val, self->art_path);
|
||||
break;
|
||||
case PROP_EPHEMERAL:
|
||||
g_value_set_boolean(val, self->ephemeral);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
|
@ -111,6 +128,9 @@ static void koto_playlist_set_property(GObject *obj, guint prop_id, const GValue
|
|||
case PROP_ART_PATH:
|
||||
koto_playlist_set_artwork(self, g_value_get_string(val));
|
||||
break;
|
||||
case PROP_EPHEMERAL:
|
||||
self->ephemeral = g_value_get_boolean(val);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
|
@ -119,6 +139,7 @@ static void koto_playlist_set_property(GObject *obj, guint prop_id, const GValue
|
|||
|
||||
static void koto_playlist_init(KotoPlaylist *self) {
|
||||
self->current_position = 0; // Default to 0
|
||||
self->played_tracks = g_queue_new(); // Set as an empty GQueue
|
||||
self->tracks = g_queue_new(); // Set as an empty GQueue
|
||||
}
|
||||
|
||||
|
@ -135,14 +156,45 @@ void koto_playlist_add_track_by_uuid(KotoPlaylist *self, const gchar *uuid) {
|
|||
// TODO: Add to table
|
||||
}
|
||||
|
||||
void koto_playlist_debug(KotoPlaylist *self) {
|
||||
g_queue_foreach(self->tracks, koto_playlist_debug_foreach, NULL);
|
||||
void koto_playlist_commit(KotoPlaylist *self) {
|
||||
if (self->ephemeral) { // Temporary playlist
|
||||
return;
|
||||
}
|
||||
|
||||
gchar *commit_op = g_strdup_printf(
|
||||
"INSERT INTO playlist_meta(id, name, art_path)"
|
||||
"VALUES('%s', quote(\"%s\"), quote(\"%s\")"
|
||||
"ON CONFLICT(id) DO UPDATE SET name=excluded.name, art_path=excluded.art_path;",
|
||||
self->uuid,
|
||||
self->name,
|
||||
self->art_path
|
||||
);
|
||||
|
||||
gchar *commit_op_errmsg = NULL;
|
||||
int rc = sqlite3_exec(koto_db, commit_op, 0, 0, &commit_op_errmsg);
|
||||
|
||||
if (rc != SQLITE_OK) {
|
||||
g_warning("Failed to save playlist: %s", commit_op_errmsg);
|
||||
} else { // Successfully saved our playlist
|
||||
g_queue_foreach(self->tracks, koto_playlist_commit_tracks, self); // Iterate over each track to save it
|
||||
}
|
||||
|
||||
g_free(commit_op);
|
||||
g_free(commit_op_errmsg);
|
||||
}
|
||||
|
||||
void koto_playlist_debug_foreach(gpointer data, gpointer user_data) {
|
||||
(void) user_data;
|
||||
gchar *uuid = data;
|
||||
g_message("UUID in Playlist: %s", uuid);
|
||||
void koto_playlist_commit_tracks(gpointer data, gpointer user_data) {
|
||||
KotoIndexedTrack *track = koto_cartographer_get_track_by_uuid(koto_maps, data); // Get the track
|
||||
|
||||
if (track == NULL) { // Not a track
|
||||
KotoPlaylist *self = user_data;
|
||||
gchar *playlist_uuid = self->uuid; // Get the playlist UUID
|
||||
|
||||
gchar *current_track = g_queue_peek_nth(self->tracks, self->current_position); // Get the UUID of the current track
|
||||
koto_indexed_track_save_to_playlist(track, playlist_uuid, g_queue_index(self->tracks, data), (data == current_track) ? 1 : 0); // Call to save the playlist to the track
|
||||
g_free(playlist_uuid);
|
||||
g_free(current_track);
|
||||
}
|
||||
}
|
||||
|
||||
gchar* koto_playlist_get_artwork(KotoPlaylist *self) {
|
||||
|
@ -165,6 +217,38 @@ gchar* koto_playlist_get_name(KotoPlaylist *self) {
|
|||
return (self->name == NULL) ? NULL : g_strdup(self->name);
|
||||
}
|
||||
|
||||
gchar* koto_playlist_get_random_track(KotoPlaylist *self) {
|
||||
gchar *track_uuid = NULL;
|
||||
guint tracks_len = g_queue_get_length(self->tracks);
|
||||
|
||||
if (tracks_len == g_queue_get_length(self->played_tracks)) { // Played all tracks
|
||||
track_uuid = g_list_nth_data(self->tracks->head, 0); // Get the first
|
||||
g_queue_clear(self->played_tracks); // Clear our played tracks
|
||||
} else { // Have not played all tracks
|
||||
GRand* rando_calrissian = g_rand_new(); // Create a new RNG
|
||||
guint attempt = 0;
|
||||
|
||||
while (track_uuid != NULL) { // Haven't selected a track yet
|
||||
attempt++;
|
||||
gint32 *selected_item = g_rand_int_range(rando_calrissian, 0, (gint32) tracks_len);
|
||||
gchar *selected_track = g_queue_peek_nth(self->tracks, (guint) selected_item); // Get the UUID of the selected item
|
||||
|
||||
if (g_queue_index(self->played_tracks, selected_track) == -1) { // Haven't played the track
|
||||
track_uuid = selected_track;
|
||||
break;
|
||||
} else { // Failed to get the track
|
||||
if (attempt > tracks_len / 2) {
|
||||
break; // Give up
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_rand_free(rando_calrissian); // Free rando
|
||||
}
|
||||
|
||||
return track_uuid;
|
||||
}
|
||||
|
||||
GQueue* koto_playlist_get_tracks(KotoPlaylist *self) {
|
||||
return self->tracks;
|
||||
}
|
||||
|
@ -174,6 +258,10 @@ gchar* koto_playlist_get_uuid(KotoPlaylist *self) {
|
|||
}
|
||||
|
||||
gchar* koto_playlist_go_to_next(KotoPlaylist *self) {
|
||||
if (self->is_shuffle_enabled) { // Shuffling enabled
|
||||
return koto_playlist_get_random_track(self); // Get a random track
|
||||
}
|
||||
|
||||
gchar *current_uuid = koto_playlist_get_current_uuid(self); // Get the current UUID
|
||||
|
||||
if (current_uuid == self->tracks->tail->data) { // Current UUID matches the last item in the playlist
|
||||
|
@ -185,6 +273,10 @@ gchar* koto_playlist_go_to_next(KotoPlaylist *self) {
|
|||
}
|
||||
|
||||
gchar* koto_playlist_go_to_previous(KotoPlaylist *self) {
|
||||
if (self->is_shuffle_enabled) { // Shuffling enabled
|
||||
return koto_playlist_get_random_track(self); // Get a random track
|
||||
}
|
||||
|
||||
gchar *current_uuid = koto_playlist_get_current_uuid(self); // Get the current UUID
|
||||
|
||||
if (current_uuid == self->tracks->head->data) { // Current UUID matches the first item in the playlist
|
||||
|
@ -280,6 +372,10 @@ void koto_playlist_set_uuid(KotoPlaylist *self, const gchar *uuid) {
|
|||
return;
|
||||
}
|
||||
|
||||
void koto_playlist_unmap(KotoPlaylist *self) {
|
||||
koto_cartographer_remove_playlist_by_uuid(koto_maps, self->uuid); // Remove from our cartographer
|
||||
}
|
||||
|
||||
KotoPlaylist* koto_playlist_new() {
|
||||
return g_object_new(KOTO_TYPE_PLAYLIST,
|
||||
"uuid", g_uuid_string_random(),
|
||||
|
|
|
@ -37,8 +37,8 @@ KotoPlaylist* koto_playlist_new();
|
|||
KotoPlaylist* koto_playlist_new_with_uuid(const gchar *uuid);
|
||||
void koto_playlist_add_track(KotoPlaylist *self, KotoIndexedTrack *track);
|
||||
void koto_playlist_add_track_by_uuid(KotoPlaylist *self, const gchar *uuid);
|
||||
void koto_playlist_debug(KotoPlaylist *self);
|
||||
void koto_playlist_debug_foreach(gpointer data, gpointer user_data);
|
||||
void koto_playlist_commit(KotoPlaylist *self);
|
||||
void koto_playlist_commit_tracks(gpointer data, gpointer user_data);
|
||||
gchar* koto_playlist_get_artwork(KotoPlaylist *self);
|
||||
guint koto_playlist_get_current_position(KotoPlaylist *self);
|
||||
gchar* koto_playlist_get_current_uuid(KotoPlaylist *self);
|
||||
|
@ -55,5 +55,6 @@ void koto_playlist_save_state(KotoPlaylist *self);
|
|||
void koto_playlist_set_name(KotoPlaylist *self, const gchar *name);
|
||||
void koto_playlist_set_position(KotoPlaylist *self, guint pos);
|
||||
void koto_playlist_set_uuid(KotoPlaylist *self, const gchar *uuid);
|
||||
void koto_playlist_unmap(KotoPlaylist *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue