mirror of
https://gitlab.xiph.org/xiph/icecast-server.git
synced 2025-05-18 00:58:26 -04:00
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
This commit is contained in:
parent
8d513db405
commit
5f77b35d14
@ -9,7 +9,7 @@ bin_PROGRAMS = icecast
|
|||||||
INCLUDES = -I./common/
|
INCLUDES = -I./common/
|
||||||
|
|
||||||
noinst_HEADERS = admin.h cfgfile.h logging.h sighandler.h connection.h \
|
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 \
|
compat.h fserve.h xslt.h yp.h md5.h \
|
||||||
event.h event_log.h event_exec.h event_url.h \
|
event.h event_log.h event_exec.h event_url.h \
|
||||||
acl.h auth.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_vorbis.h format_theora.h format_flac.h format_speex.h format_midi.h \
|
||||||
format_kate.h format_skeleton.h format_opus.h
|
format_kate.h format_skeleton.h format_opus.h
|
||||||
icecast_SOURCES = cfgfile.c main.c logging.c sighandler.c connection.c global.c \
|
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 \
|
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.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 \
|
format_kate.c format_skeleton.c format_opus.c \
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
#include "client.h"
|
#include "client.h"
|
||||||
|
|
||||||
#include "stats.h"
|
#include "stats.h"
|
||||||
|
#include "playlist.h"
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
#include "format_ogg.h"
|
#include "format_ogg.h"
|
||||||
#include "format_vorbis.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, "artist", artist);
|
||||||
stats_event (source->mount, "title", title);
|
stats_event (source->mount, "title", title);
|
||||||
|
|
||||||
|
playlist_push_track(source->history, &source->format->vc);
|
||||||
|
|
||||||
codec = ogg_info->codecs;
|
codec = ogg_info->codecs;
|
||||||
while (codec)
|
while (codec)
|
||||||
{
|
{
|
||||||
|
179
src/playlist.c
Normal file
179
src/playlist.c
Normal file
@ -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 <lion@lion.leolix.org>,
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <libxml/xmlmemory.h>
|
||||||
|
#include <libxml/parser.h>
|
||||||
|
#include <libxml/tree.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
38
src/playlist.h
Normal file
38
src/playlist.h
Normal file
@ -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 <lion@lion.leolix.org>,
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __PLAYLIST_H__
|
||||||
|
#define __PLAYLIST_H__
|
||||||
|
|
||||||
|
#include <vorbis/codec.h>
|
||||||
|
|
||||||
|
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
|
@ -100,6 +100,7 @@ source_t *source_reserve (const char *mount)
|
|||||||
|
|
||||||
src->client_tree = avl_tree_new(_compare_clients, NULL);
|
src->client_tree = avl_tree_new(_compare_clients, NULL);
|
||||||
src->pending_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 */
|
/* make duplicates for strings or similar */
|
||||||
src->mount = strdup(mount);
|
src->mount = strdup(mount);
|
||||||
@ -283,6 +284,9 @@ void source_clear_source (source_t *source)
|
|||||||
free(source->dumpfilename);
|
free(source->dumpfilename);
|
||||||
source->dumpfilename = NULL;
|
source->dumpfilename = NULL;
|
||||||
|
|
||||||
|
playlist_release(source->history);
|
||||||
|
source->history = NULL;
|
||||||
|
|
||||||
if (source->intro_file)
|
if (source->intro_file)
|
||||||
{
|
{
|
||||||
fclose (source->intro_file);
|
fclose (source->intro_file);
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include "yp.h"
|
#include "yp.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
#include "playlist.h"
|
||||||
#include "common/thread/thread.h"
|
#include "common/thread/thread.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@ -79,6 +80,8 @@ typedef struct source_tag
|
|||||||
refbuf_t *stream_data;
|
refbuf_t *stream_data;
|
||||||
refbuf_t *stream_data_tail;
|
refbuf_t *stream_data_tail;
|
||||||
|
|
||||||
|
playlist_t *history;
|
||||||
|
|
||||||
} source_t;
|
} source_t;
|
||||||
|
|
||||||
source_t *source_reserve (const char *mount);
|
source_t *source_reserve (const char *mount);
|
||||||
|
@ -855,7 +855,7 @@ static xmlNodePtr _dump_stats_to_doc (xmlNodePtr root, const char *show_mount, i
|
|||||||
if (source->hidden <= hidden &&
|
if (source->hidden <= hidden &&
|
||||||
(show_mount == NULL || strcmp (show_mount, source->source) == 0))
|
(show_mount == NULL || strcmp (show_mount, source->source) == 0))
|
||||||
{
|
{
|
||||||
xmlNodePtr metadata;
|
xmlNodePtr metadata, history;
|
||||||
source_t *source_real;
|
source_t *source_real;
|
||||||
mount_proxy *mountproxy;
|
mount_proxy *mountproxy;
|
||||||
int i;
|
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);
|
avl_tree_rlock(global.source_tree);
|
||||||
source_real = source_find_mount_raw(source->source);
|
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) {
|
if (source_real->format) {
|
||||||
for (i = 0; i < source_real->format->vc.comments; i++)
|
for (i = 0; i < source_real->format->vc.comments; i++)
|
||||||
__add_metadata(metadata, source_real->format->vc.user_comments[i]);
|
__add_metadata(metadata, source_real->format->vc.user_comments[i]);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user