From 5f77b35d14df2bee8d4a06fb714eba4fcc80f40d Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sat, 28 Mar 2015 16:15:49 +0000 Subject: [PATCH] Initial patch for playlist history support. This allows to store a history of played songs along the source object and report it as part of the status XML. Additional work needs to be done to make this configurable. Also format_mp3.c needs work to support this. A generic song changed handler should be implemented to handle this in a nice way. That one should also be the point to call logging_playlist(). See: #766 --- src/Makefile.am | 4 +- src/format_ogg.c | 3 + src/playlist.c | 179 +++++++++++++++++++++++++++++++++++++++++++++++ src/playlist.h | 38 ++++++++++ src/source.c | 4 ++ src/source.h | 3 + src/stats.c | 7 +- 7 files changed, 234 insertions(+), 4 deletions(-) create mode 100644 src/playlist.c create mode 100644 src/playlist.h diff --git a/src/Makefile.am b/src/Makefile.am index 977d9328..00fd6730 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -9,7 +9,7 @@ bin_PROGRAMS = icecast INCLUDES = -I./common/ noinst_HEADERS = admin.h cfgfile.h logging.h sighandler.h connection.h \ - global.h util.h slave.h source.h stats.h refbuf.h client.h \ + global.h util.h slave.h source.h stats.h refbuf.h client.h playlist.h \ compat.h fserve.h xslt.h yp.h md5.h \ event.h event_log.h event_exec.h event_url.h \ acl.h auth.h \ @@ -17,7 +17,7 @@ noinst_HEADERS = admin.h cfgfile.h logging.h sighandler.h connection.h \ format_vorbis.h format_theora.h format_flac.h format_speex.h format_midi.h \ format_kate.h format_skeleton.h format_opus.h icecast_SOURCES = cfgfile.c main.c logging.c sighandler.c connection.c global.c \ - util.c slave.c source.c stats.c refbuf.c client.c \ + util.c slave.c source.c stats.c refbuf.c client.c playlist.c \ xslt.c fserve.c admin.c md5.c \ format.c format_ogg.c format_mp3.c format_midi.c format_flac.c format_ebml.c \ format_kate.c format_skeleton.c format_opus.c \ diff --git a/src/format_ogg.c b/src/format_ogg.c index 09de64d7..d905bfce 100644 --- a/src/format_ogg.c +++ b/src/format_ogg.c @@ -31,6 +31,7 @@ #include "client.h" #include "stats.h" +#include "playlist.h" #include "format.h" #include "format_ogg.h" #include "format_vorbis.h" @@ -318,6 +319,8 @@ static void update_comments(source_t *source) stats_event (source->mount, "artist", artist); stats_event (source->mount, "title", title); + playlist_push_track(source->history, &source->format->vc); + codec = ogg_info->codecs; while (codec) { diff --git a/src/playlist.c b/src/playlist.c new file mode 100644 index 00000000..a9fd0242 --- /dev/null +++ b/src/playlist.c @@ -0,0 +1,179 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2015, Philipp "ph3-der-loewe" Schafft , + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include "playlist.h" + +/* for XMLSTR() */ +#include "cfgfile.h" + +#include "logging.h" +#define CATMODULE "playlist" + +typedef struct playlist_track_tag playlist_track_t; + +struct playlist_tag { + size_t refc; + ssize_t max_tracks; + playlist_track_t *first; +}; + +struct playlist_track_tag { + char *title; + char *creator; + char *album; + char *trackNum; + playlist_track_t *next; +}; + +static void __free_track(playlist_track_t *track) +{ + if (track->title) + free(track->title); + if (track->creator) + free(track->creator); + if (track->album) + free(track->album); + if (track->trackNum) + free(track->trackNum); + free(track); +} + +static char * __query_vc(vorbis_comment *vc, const char *key) +{ + char *value = vorbis_comment_query(vc, key, 0); + if (!value) + return NULL; + return strdup(value); +} + +playlist_t * playlist_new(ssize_t max_tracks) +{ + playlist_t *playlist = calloc(1, sizeof(playlist_t)); + + if (!playlist) + return NULL; + + playlist->refc = 1; + playlist->max_tracks = max_tracks; + + return playlist; +} + +int playlist_ref(playlist_t *playlist) +{ + if (!playlist) + return -1; + playlist->refc++; + return 0; +} + +int playlist_release(playlist_t *playlist) +{ + playlist_track_t *track; + + if (!playlist) + return -1; + playlist->refc--; + if (playlist->refc) + return 0; + + while ((track = playlist->first)) { + playlist->first = track->next; + __free_track(track); + } + + free(playlist); + return 0; +} + +int playlist_set_max_tracks(playlist_t *playlist, ssize_t max_tracks) +{ + if (!playlist) + return -1; + playlist->max_tracks = max_tracks; + return 0; +} + + +int playlist_push_track(playlist_t *playlist, vorbis_comment *vc) +{ + playlist_track_t *track, **cur; + ssize_t num = 0; + + if (!playlist) + return -1; + + track = calloc(1, sizeof(playlist_track_t)); + if (!track) + return -1; + + cur = &playlist->first; + while (*cur) { + cur = &(*cur)->next; + num++; + } + *cur = track; + + while (playlist->max_tracks > 0 && num > playlist->max_tracks) { + playlist_track_t *to_free = playlist->first; + playlist->first = to_free->next; + __free_track(to_free); + num--; + } + + if (vc) { + track->title = __query_vc(vc, "TITLE"); + track->creator = __query_vc(vc, "ARTIST"); + track->album = __query_vc(vc, "ALBUM"); + track->trackNum = __query_vc(vc, "TRACKNUMBER"); + } + + return 0; +} + +xmlNodePtr playlist_render_xspf(playlist_t *playlist) +{ + xmlNodePtr rootnode, tracklist, tracknode; + playlist_track_t *track; + + if (!playlist) + return NULL; + + rootnode = xmlNewNode(NULL, XMLSTR("playlist")); + xmlSetProp(rootnode, XMLSTR("version"), XMLSTR("1")); + xmlSetProp(rootnode, XMLSTR("xmlns"), XMLSTR("http://xspf.org/ns/0/")); + + tracklist = xmlNewNode(NULL, XMLSTR("trackList")); + xmlAddChild(rootnode, tracklist); + + track = playlist->first; + while (track) { + tracknode = xmlNewNode(NULL, XMLSTR("track")); + xmlAddChild(tracklist, tracknode); + /* TODO: Handle meta data */ + if (track->title) + xmlNewTextChild(tracknode, NULL, XMLSTR("title"), XMLSTR(track->title)); + if (track->creator) + xmlNewTextChild(tracknode, NULL, XMLSTR("creator"), XMLSTR(track->creator)); + if (track->album) + xmlNewTextChild(tracknode, NULL, XMLSTR("album"), XMLSTR(track->album)); + if (track->trackNum) + xmlNewTextChild(tracknode, NULL, XMLSTR("trackNum"), XMLSTR(track->trackNum)); + track = track->next; + } + + return rootnode; +} diff --git a/src/playlist.h b/src/playlist.h new file mode 100644 index 00000000..75b9fade --- /dev/null +++ b/src/playlist.h @@ -0,0 +1,38 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2015, Philipp "ph3-der-loewe" Schafft , + */ + +#ifndef __PLAYLIST_H__ +#define __PLAYLIST_H__ + +#include + +typedef struct playlist_tag playlist_t; + +playlist_t * playlist_new(ssize_t max_tracks); +int playlist_ref(playlist_t *playlist); +int playlist_release(playlist_t *playlist); + +/* set maximum size of playlist. + * Will not reduce number of tracks if there are more in the list + * than the new limit. + */ +int playlist_set_max_tracks(playlist_t *playlist, ssize_t max_tracks); + +/* push a new track at the end of the playlist. + * If the playlist is already at maximum size the oldest track + * is automatically removed. + */ +int playlist_push_track(playlist_t *playlist, vorbis_comment *vc); + +/* this function returns the root node of the playlist. + * If you want to use this for file output you need to generate + * your own xmlDocPtr and attach it as root node. + */ +xmlNodePtr playlist_render_xspf(playlist_t *playlist); + +#endif diff --git a/src/source.c b/src/source.c index 1be88a9c..003dd240 100644 --- a/src/source.c +++ b/src/source.c @@ -100,6 +100,7 @@ source_t *source_reserve (const char *mount) src->client_tree = avl_tree_new(_compare_clients, NULL); src->pending_tree = avl_tree_new(_compare_clients, NULL); + src->history = playlist_new(-1); /* make duplicates for strings or similar */ src->mount = strdup(mount); @@ -283,6 +284,9 @@ void source_clear_source (source_t *source) free(source->dumpfilename); source->dumpfilename = NULL; + playlist_release(source->history); + source->history = NULL; + if (source->intro_file) { fclose (source->intro_file); diff --git a/src/source.h b/src/source.h index 7cefdcca..af46e536 100644 --- a/src/source.h +++ b/src/source.h @@ -18,6 +18,7 @@ #include "yp.h" #include "util.h" #include "format.h" +#include "playlist.h" #include "common/thread/thread.h" #include @@ -79,6 +80,8 @@ typedef struct source_tag refbuf_t *stream_data; refbuf_t *stream_data_tail; + playlist_t *history; + } source_t; source_t *source_reserve (const char *mount); diff --git a/src/stats.c b/src/stats.c index 42394ad4..62f47e19 100644 --- a/src/stats.c +++ b/src/stats.c @@ -855,7 +855,7 @@ static xmlNodePtr _dump_stats_to_doc (xmlNodePtr root, const char *show_mount, i if (source->hidden <= hidden && (show_mount == NULL || strcmp (show_mount, source->source) == 0)) { - xmlNodePtr metadata; + xmlNodePtr metadata, history; source_t *source_real; mount_proxy *mountproxy; int i; @@ -874,9 +874,12 @@ static xmlNodePtr _dump_stats_to_doc (xmlNodePtr root, const char *show_mount, i } - metadata = xmlNewTextChild(xmlnode, NULL, XMLSTR("metadata"), NULL); avl_tree_rlock(global.source_tree); source_real = source_find_mount_raw(source->source); + history = playlist_render_xspf(source_real->history); + if (history) + xmlAddChild(xmlnode, history); + metadata = xmlNewTextChild(xmlnode, NULL, XMLSTR("metadata"), NULL); if (source_real->format) { for (i = 0; i < source_real->format->vc.comments; i++) __add_metadata(metadata, source_real->format->vc.user_comments[i]);