summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Alexey Yakovenko <wakeroid@gmail.com>2009-11-08 15:10:02 +0100
committerGravatar Alexey Yakovenko <wakeroid@gmail.com>2009-11-08 15:10:02 +0100
commite84871a4c4d8828239d8501273481cdcd05769c2 (patch)
treeae8bb4e5a0627127c4aac1a00f0ea971bfc0e1e4
parent2ea97385dba1a30d50e034ef8793b9a8baefaa19 (diff)
parentaf6c7af6784b1e1b720077f7814cb07b0127b4bb (diff)
Merge branch 'vfs'
Conflicts: configure.ac main.c palsa.c playlist.c plugins/flac/flac.c streamer.c
-rw-r--r--Makefile.am14
-rw-r--r--README3
-rw-r--r--about.txt60
-rw-r--r--callbacks.c718
-rw-r--r--callbacks.h155
-rw-r--r--cdumb.c47
-rw-r--r--cgme.c7
-rw-r--r--conf.c203
-rw-r--r--conf.h50
-rw-r--r--configure.ac86
-rw-r--r--csid.cpp13
-rw-r--r--deadbeef.glade1317
-rw-r--r--deadbeef.h120
-rw-r--r--gtkplaylist.c809
-rw-r--r--gtkplaylist.h59
-rw-r--r--gtksession.c17
-rw-r--r--interface.c747
-rw-r--r--interface.h4
-rw-r--r--junklib.c48
-rw-r--r--junklib.h10
-rw-r--r--main.c149
-rw-r--r--messages.h41
-rw-r--r--moduleconf.h1
-rw-r--r--palsa.c293
-rw-r--r--palsa.h9
-rw-r--r--pixmaps/Makefile.am5
-rw-r--r--pixmaps/buffering_16.pngbin0 -> 228 bytes
-rw-r--r--playlist.c616
-rw-r--r--playlist.h25
-rw-r--r--plugins.c111
-rw-r--r--plugins.h16
-rw-r--r--plugins/cdda/Makefile.am11
-rw-r--r--plugins/cdda/cdda.c455
-rw-r--r--plugins/faad2/Makefile.am9
-rw-r--r--plugins/faad2/faad2.c270
-rw-r--r--plugins/ffap/ffap.c59
-rw-r--r--plugins/flac/Makefile.am2
-rw-r--r--plugins/flac/flac.c163
-rw-r--r--plugins/hotkeys/hotkeys.c93
-rw-r--r--plugins/lastfm/Makefile.am7
-rw-r--r--plugins/lastfm/lastfm.c46
-rw-r--r--plugins/mpgmad/Makefile.am2
-rw-r--r--plugins/mpgmad/mpgmad.c553
-rw-r--r--plugins/sndfile/Makefile.am2
-rw-r--r--plugins/sndfile/sndfile.c7
-rw-r--r--plugins/vfs_curl/Makefile.am9
-rw-r--r--plugins/vfs_curl/vfs_curl.c711
-rw-r--r--plugins/vorbis/Makefile.am4
-rw-r--r--plugins/vorbis/vorbis.c260
-rw-r--r--plugins/wavpack/Makefile.am2
-rw-r--r--plugins/wavpack/wavpack.c97
-rw-r--r--search.c3
-rw-r--r--session.c201
-rw-r--r--session.h38
-rw-r--r--streamer.c264
-rw-r--r--streamer.h12
-rw-r--r--timeline.c122
-rw-r--r--timeline.h52
-rw-r--r--vfs.c108
-rw-r--r--vfs.h37
-rw-r--r--vfs_stdio.c123
-rw-r--r--volume.c6
62 files changed, 7888 insertions, 1593 deletions
diff --git a/Makefile.am b/Makefile.am
index b79c89d7..b8d1be0a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,5 +1,5 @@
## Process this file with automake to produce Makefile.in
-ACLOCAL_AMFLAGS = -I m4
+#ACLOCAL_AMFLAGS = -I m4
SUBDIRS = gme/Game_Music_Emu-0.5.2\
gme/Game_Music_Emu-0.5.2/gme\
@@ -14,7 +14,9 @@ SUBDIRS = gme/Game_Music_Emu-0.5.2\
${VORBIS_DIR}\
${FLAC_DIR}\
${WAVPACK_DIR}\
- ${SNDFILE_DIR}
+ ${SNDFILE_DIR}\
+ ${VFS_CURL_DIR}\
+ ${CDDA_DIR}
dumbpath=@top_srcdir@/dumb
sidpath=@top_srcdir@/sid/sidplay-libs-2.1.0
@@ -30,7 +32,7 @@ deadbeef_SOURCES =\
streamer.c streamer.h\
progress.c progress.h\
codec.c codec.h\
- messagepump.c messagepump.h messages.h\
+ messagepump.c messagepump.h\
conf.c conf.h\
search.c search.h\
cgme.c cdumb.c csid.cpp\
@@ -41,7 +43,9 @@ deadbeef_SOURCES =\
drawing.h gdkdrawing.c\
session.h session.c gtksession.c\
junklib.h junklib.c utf8.c utf8.h\
- optmath.h
+ optmath.h\
+ vfs.c vfs.h vfs_stdio.c\
+ timeline.c timeline.h
sdkdir = $(pkgincludedir)
sdk_HEADERS = deadbeef.h
@@ -53,7 +57,7 @@ AM_CPPFLAGS = $(DEPS_CFLAGS) -I$(sidpath)/libsidplay/include -I$(sidpath)/builde
docsdir = $(datadir)/doc/$(PACKAGE)
-docs_DATA = README COPYING config-example help.txt
+docs_DATA = README COPYING config-example help.txt about.txt
desktopdir = $(datadir)/applications
desktop_DATA = deadbeef.desktop
diff --git a/README b/README
index c1814670..8489fea4 100644
--- a/README
+++ b/README
@@ -5,11 +5,12 @@ dependencies for player core - includes support for sid, tracker music and chipt
optional dependencies
libvorbis: for ogg vorbis plugin
- libcurl: for last.fm plugin
+ libcurl >= 7.10: for last.fm plugin
libmad: for mpgmad plugin (mpeg1,2 layers1,2,3)
libFLAC: for flac plugin
wavpack: for wavpack plugin
libsndfile: for sndfile plugin
+ libcdio: for cd audio plugin
if you want to build from git - install autotools, and run ./autogen.sh before compiling
diff --git a/about.txt b/about.txt
new file mode 100644
index 00000000..4148272d
--- /dev/null
+++ b/about.txt
@@ -0,0 +1,60 @@
+DeaDBeeF - ultimate music player for GNU/Linux systems with X11
+Copyright © 2009 Alexey Yakovenko <waker@users.sourceforge.net>
+http://deadbeef.sf.net
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+
+Note that other included libraries and plugins may have separate licensing terms.
+
+Global hotkeys plugin
+Copyright © Viktor Semykin <thesame.ml@gmail.com>
+
+
+Buttons artwork
+Copyright © Stas Akimushkin <uncle.lag@gmail.com>
+
+
+Debian packages, initial deadbeef.desktop file by
+Alexey A. Smirnov <alexey.smirnov@gmx.com>
+
+
+DUMB - Dynamic Universal Music Bibliotheque, Version 0.9.3
+Copyright © 2001-2005 Ben Davis, Robert J Ohannessian and Julien Cugniere.
+
+
+Game_Music_Emu Version 0.5.2
+Copyright © 2003-2006 Shay Green.
+
+
+Functions to compute MD5 message digest of files or memory blocks
+Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995.
+Modified by Gray Watson <http://256.com/gray/>, 1997.
+
+
+libsidplay2 - commodore 64 SID emulation library
+Copyright © Simon White and other authors.
+
+
+ffap - monkey's audio decoder based on apedec code from ffmpeg
+Copyright © 2007 Benjamin Zores <ben@geexbox.org>
+
+
+wavpack - Hybrid Lossless Wavefile Compressor
+Copyright © 1998 - 2006 Conifer Software
+
+
+libsndfile - PCM read/write/conversion library
+Copyright © 1999-2009 Erik de Castro Lopo <erikd@mega-nerd.com>
diff --git a/callbacks.c b/callbacks.c
index f03c36c4..8d6d9bbf 100644
--- a/callbacks.c
+++ b/callbacks.c
@@ -26,6 +26,7 @@
#include <unistd.h>
#include <assert.h>
#include <ctype.h>
+#include <gdk/gdkkeysyms.h>
#include "callbacks.h"
#include "interface.h"
@@ -36,7 +37,6 @@
#include "playlist.h"
#include "gtkplaylist.h"
#include "messagepump.h"
-#include "messages.h"
#include "codec.h"
#include "playback.h"
#include "search.h"
@@ -48,21 +48,26 @@
#include "plugins.h"
-//#include "cmp3.h"
-//#include "cvorbis.h"
-//#include "cdumb.h"
-//#include "cgme.h"
-//#include "cflac.h"
-//#include "csid.h"
-
extern GtkWidget *mainwin;
extern gtkplaylist_t main_playlist;
extern gtkplaylist_t search_playlist;
+gboolean
+playlist_tooltip_handler (GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer unused)
+{
+ playItem_t *item = gtkpl_get_for_idx (&main_playlist, main_playlist.scrollpos + y / rowheight);
+ if (item && item->fname) {
+ gtk_tooltip_set_text (tooltip, item->fname);
+ return TRUE;
+ }
+ return FALSE;
+}
+
void
main_playlist_init (GtkWidget *widget) {
// init playlist control structure, and put it into widget user-data
memset (&main_playlist, 0, sizeof (main_playlist));
+ main_playlist.title = "playlist";
main_playlist.playlist = widget;
main_playlist.header = lookup_widget (mainwin, "header");
main_playlist.scrollbar = lookup_widget (mainwin, "playscroll");
@@ -76,14 +81,40 @@ main_playlist_init (GtkWidget *widget) {
main_playlist.row = -1;
main_playlist.clicktime = -1;
main_playlist.nvisiblerows = 0;
- //main_playlist.fmtcache = NULL;
-// int colwidths[pl_ncolumns] = { 50, 150, 50, 150, 50 };
-// memcpy (main_playlist.colwidths, colwidths, sizeof (colwidths));
- main_playlist.colwidths = session_get_main_colwidths_ptr ();
+
+// FIXME: on 1st run, copy colwidths to new columns
+// main_playlist.colwidths = session_get_main_colwidths_ptr ();
+
+ DB_conf_item_t *col = conf_find ("playlist.column.", NULL);
+ if (!col) {
+ // create default set of columns
+ gtkpl_column_append (&main_playlist, gtkpl_column_alloc ("Playing", 50, DB_COLUMN_PLAYING, NULL, 0));
+ gtkpl_column_append (&main_playlist, gtkpl_column_alloc ("Artist / Album", 150, DB_COLUMN_ARTIST_ALBUM, NULL, 0));
+ gtkpl_column_append (&main_playlist, gtkpl_column_alloc ("Track №", 50, DB_COLUMN_TRACK, NULL, 1));
+ gtkpl_column_append (&main_playlist, gtkpl_column_alloc ("Title / Track Artist", 150, DB_COLUMN_TITLE, NULL, 0));
+ gtkpl_column_append (&main_playlist, gtkpl_column_alloc ("Duration", 50, DB_COLUMN_DURATION, NULL, 0));
+ }
+ else {
+ while (col) {
+ gtkpl_append_column_from_textdef (&main_playlist, col->value);
+ col = conf_find ("playlist.column.", col);
+ }
+ }
+
gtk_object_set_data (GTK_OBJECT (main_playlist.playlist), "ps", &main_playlist);
gtk_object_set_data (GTK_OBJECT (main_playlist.header), "ps", &main_playlist);
gtk_object_set_data (GTK_OBJECT (main_playlist.scrollbar), "ps", &main_playlist);
gtk_object_set_data (GTK_OBJECT (main_playlist.hscrollbar), "ps", &main_playlist);
+
+ // FIXME: filepath should be in properties dialog, while tooltip should be
+ // used to show text that doesn't fit in column width
+ if (conf_get_int ("playlist.showpathtooltip", 0)) {
+ GValue value = {0, };
+ g_value_init (&value, G_TYPE_BOOLEAN);
+ g_value_set_boolean (&value, TRUE);
+ g_object_set_property (G_OBJECT (widget), "has-tooltip", &value);
+ g_signal_connect (G_OBJECT (widget), "query-tooltip", G_CALLBACK (playlist_tooltip_handler), NULL);
+ }
}
void
@@ -91,6 +122,7 @@ search_playlist_init (GtkWidget *widget) {
extern GtkWidget *searchwin;
// init playlist control structure, and put it into widget user-data
memset (&search_playlist, 0, sizeof (search_playlist));
+ search_playlist.title = "search";
search_playlist.playlist = widget;
search_playlist.header = lookup_widget (searchwin, "searchheader");
search_playlist.scrollbar = lookup_widget (searchwin, "searchscroll");
@@ -106,10 +138,23 @@ search_playlist_init (GtkWidget *widget) {
search_playlist.row = -1;
search_playlist.clicktime = -1;
search_playlist.nvisiblerows = 0;
- //search_playlist.fmtcache = NULL;
-// int colwidths[pl_ncolumns] = { 0, 150, 50, 150, 50 };
-// memcpy (search_playlist.colwidths, colwidths, sizeof (colwidths));
- search_playlist.colwidths = session_get_search_colwidths_ptr ();
+
+// FIXME: port to new columns
+// search_playlist.colwidths = session_get_search_colwidths_ptr ();
+ // create default set of columns
+ DB_conf_item_t *col = conf_find ("search.column.", NULL);
+ if (!col) {
+ gtkpl_column_append (&search_playlist, gtkpl_column_alloc ("Artist / Album", 150, DB_COLUMN_ARTIST_ALBUM, NULL, 0));
+ gtkpl_column_append (&search_playlist, gtkpl_column_alloc ("Track №", 50, DB_COLUMN_TRACK, NULL, 1));
+ gtkpl_column_append (&search_playlist, gtkpl_column_alloc ("Title / Track Artist", 150, DB_COLUMN_TITLE, NULL, 0));
+ gtkpl_column_append (&search_playlist, gtkpl_column_alloc ("Duration", 50, DB_COLUMN_DURATION, NULL, 0));
+ }
+ else {
+ while (col) {
+ gtkpl_append_column_from_textdef (&search_playlist, col->value);
+ col = conf_find ("search.column.", col);
+ }
+ }
gtk_object_set_data (GTK_OBJECT (search_playlist.playlist), "ps", &search_playlist);
gtk_object_set_data (GTK_OBJECT (search_playlist.header), "ps", &search_playlist);
gtk_object_set_data (GTK_OBJECT (search_playlist.scrollbar), "ps", &search_playlist);
@@ -174,19 +219,55 @@ on_playscroll_value_changed (GtkRange *widget,
gtkpl_scroll (ps, newscroll);
}
+static gboolean
+file_filter_func (const GtkFileFilterInfo *filter_info, gpointer data) {
+ // get ext
+ const char *p = filter_info->filename + strlen (filter_info->filename)-1;
+ while (p >= filter_info->filename) {
+ if (*p == '.') {
+ break;
+ }
+ p--;
+ }
+ if (*p != '.') {
+ return FALSE;
+ }
+ p++;
+ DB_decoder_t **codecs = plug_get_decoder_list ();
+ for (int i = 0; codecs[i]; i++) {
+ if (codecs[i]->exts && codecs[i]->insert) {
+ const char **exts = codecs[i]->exts;
+ if (exts) {
+ for (int e = 0; exts[e]; e++) {
+ if (!strcasecmp (exts[e], p)) {
+ return TRUE;
+ }
+ }
+ }
+ }
+ }
+ if (!strcasecmp (p, "pls")) {
+ return TRUE;
+ }
+ if (!strcasecmp (p, "m3u")) {
+ return TRUE;
+ }
+ return FALSE;
+}
-void
-on_open_activate (GtkMenuItem *menuitem,
- gpointer user_data)
-{
- GtkWidget *dlg = gtk_file_chooser_dialog_new ("Open file(s)...", GTK_WINDOW (mainwin), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_OK, NULL);
+static GtkFileFilter *
+set_file_filter (GtkWidget *dlg, const char *name) {
+ if (!name) {
+ name = "Supported sound formats";
+ }
GtkFileFilter* flt;
flt = gtk_file_filter_new ();
- gtk_file_filter_set_name (flt, "Supported music files");
+ gtk_file_filter_set_name (flt, name);
+ gtk_file_filter_add_custom (flt, GTK_FILE_FILTER_FILENAME, file_filter_func, NULL, NULL);
+#if 0
DB_decoder_t **codecs = plug_get_decoder_list ();
-
for (int i = 0; codecs[i]; i++) {
if (codecs[i]->exts && codecs[i]->insert) {
const char **exts = codecs[i]->exts;
@@ -204,6 +285,9 @@ on_open_activate (GtkMenuItem *menuitem,
}
}
}
+ gtk_file_filter_add_pattern (flt, "*.pls");
+ gtk_file_filter_add_pattern (flt, "*.m3u");
+#endif
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dlg), flt);
gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dlg), flt);
@@ -211,14 +295,24 @@ on_open_activate (GtkMenuItem *menuitem,
gtk_file_filter_set_name (flt, "Other files (*)");
gtk_file_filter_add_pattern (flt, "*");
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dlg), flt);
+}
+
+void
+on_open_activate (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+ GtkWidget *dlg = gtk_file_chooser_dialog_new ("Open file(s)...", GTK_WINDOW (mainwin), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_OK, NULL);
+
+ set_file_filter (dlg, NULL);
+
gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dlg), TRUE);
// restore folder
- gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dlg), session_get_directory ());
+ gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dlg), conf_get_str ("filechooser.lastdir", ""));
int response = gtk_dialog_run (GTK_DIALOG (dlg));
// store folder
gchar *folder = gtk_file_chooser_get_current_folder_uri (GTK_FILE_CHOOSER (dlg));
if (folder) {
- session_set_directory (folder);
+ conf_set_str ("filechooser.lastdir", folder);
g_free (folder);
}
if (response == GTK_RESPONSE_OK)
@@ -242,44 +336,17 @@ on_add_files_activate (GtkMenuItem *menuitem,
{
GtkWidget *dlg = gtk_file_chooser_dialog_new ("Add file(s) to playlist...", GTK_WINDOW (mainwin), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_OK, NULL);
- GtkFileFilter* flt;
- flt = gtk_file_filter_new ();
- gtk_file_filter_set_name (flt, "Supported music files");
-
- DB_decoder_t **codecs = plug_get_decoder_list ();
- for (int i = 0; codecs[i]; i++) {
- if (codecs[i]->exts && codecs[i]->insert) {
- const char **exts = codecs[i]->exts;
- if (exts) {
- for (int e = 0; exts[e]; e++) {
- char filter[20];
- snprintf (filter, 20, "*.%s", exts[e]);
- gtk_file_filter_add_pattern (flt, filter);
- char *p;
- for (p = filter; *p; p++) {
- *p = toupper (*p);
- }
- gtk_file_filter_add_pattern (flt, filter);
- }
- }
- }
- }
+ set_file_filter (dlg, NULL);
- gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dlg), flt);
- gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dlg), flt);
- flt = gtk_file_filter_new ();
- gtk_file_filter_set_name (flt, "Other files (*)");
- gtk_file_filter_add_pattern (flt, "*");
- gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dlg), flt);
gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dlg), TRUE);
// restore folder
- gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dlg), session_get_directory ());
+ gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dlg), conf_get_str ("filechooser.lastdir", ""));
int response = gtk_dialog_run (GTK_DIALOG (dlg));
// store folder
gchar *folder = gtk_file_chooser_get_current_folder_uri (GTK_FILE_CHOOSER (dlg));
if (folder) {
- session_set_directory (folder);
+ conf_set_str ("filechooser.lastdir", folder);
g_free (folder);
}
if (response == GTK_RESPONSE_OK)
@@ -301,43 +368,16 @@ on_add_folders_activate (GtkMenuItem *menuitem,
{
GtkWidget *dlg = gtk_file_chooser_dialog_new ("Add folder(s) to playlist...", GTK_WINDOW (mainwin), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_OK, NULL);
- GtkFileFilter* flt;
- flt = gtk_file_filter_new ();
- gtk_file_filter_set_name (flt, "Supported music files");
+ set_file_filter (dlg, NULL);
- DB_decoder_t **codecs = plug_get_decoder_list ();
- for (int i = 0; codecs[i]; i++) {
- if (codecs[i]->exts && codecs[i]->insert) {
- const char **exts = codecs[i]->exts;
- if (exts) {
- for (int e = 0; exts[e]; e++) {
- char filter[20];
- snprintf (filter, 20, "*.%s", exts[e]);
- gtk_file_filter_add_pattern (flt, filter);
- char *p;
- for (p = filter; *p; p++) {
- *p = toupper (*p);
- }
- gtk_file_filter_add_pattern (flt, filter);
- }
- }
- }
- }
-
- gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dlg), flt);
- gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dlg), flt);
- flt = gtk_file_filter_new ();
- gtk_file_filter_set_name (flt, "Other files (*)");
- gtk_file_filter_add_pattern (flt, "*");
- gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dlg), flt);
gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dlg), TRUE);
// restore folder
- gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dlg), session_get_directory ());
+ gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dlg), conf_get_str ("filechooser.lastdir", ""));
int response = gtk_dialog_run (GTK_DIALOG (dlg));
// store folder
gchar *folder = gtk_file_chooser_get_current_folder_uri (GTK_FILE_CHOOSER (dlg));
if (folder) {
- session_set_directory (folder);
+ conf_set_str ("filechooser.lastdir", folder);
g_free (folder);
}
if (response == GTK_RESPONSE_OK)
@@ -405,6 +445,12 @@ on_remove1_activate (GtkMenuItem *menuitem,
gtkplaylist_t *ps = &main_playlist;
GtkWidget *widget = ps->playlist;
ps->row = pl_delete_selected ();
+ if (ps->row != -1) {
+ playItem_t *it = pl_get_for_idx (ps->row);
+ if (it) {
+ it->selected = 1;
+ }
+ }
gtkpl_setup_scrollbar (ps);
gtkpl_draw_playlist (ps, 0, 0, widget->allocation.width, widget->allocation.height);
gtkpl_expose (ps, 0, 0, widget->allocation.width, widget->allocation.height);
@@ -426,15 +472,6 @@ on_crop1_activate (GtkMenuItem *menuitem,
}
-void
-on_about1_activate (GtkMenuItem *menuitem,
- gpointer user_data)
-{
- GtkWidget *d = create_aboutdialog ();
- gtk_dialog_run (GTK_DIALOG (d));
- gtk_widget_destroy (d);
-}
-
gboolean
on_playlist_scroll_event (GtkWidget *widget,
@@ -597,9 +634,10 @@ on_playlist_drag_data_received (GtkWidget *widget,
GTKPL_PROLOGUE;
gchar *ptr=(char*)data->data;
if (target_type == 0) { // uris
- if (!strncmp(ptr,"file:///",8)) {
+ fprintf (stderr, "calling gtkpl_handle_fm_drag_drop\n");
+// if (!strncmp(ptr,"file:///",8)) {
gtkpl_handle_fm_drag_drop (ps, y, ptr, data->length);
- }
+// }
}
else if (target_type == 1) {
uint32_t *d= (uint32_t *)ptr;
@@ -700,8 +738,7 @@ void
on_order_linear_activate (GtkMenuItem *menuitem,
gpointer user_data)
{
- session_set_playlist_order (0);
- pl_set_order (0);
+ conf_set_int ("playback.order", 0);
}
@@ -709,8 +746,7 @@ void
on_order_shuffle_activate (GtkMenuItem *menuitem,
gpointer user_data)
{
- session_set_playlist_order (1);
- pl_set_order (1);
+ conf_set_int ("playback.order", 1);
}
@@ -718,8 +754,7 @@ void
on_order_random_activate (GtkMenuItem *menuitem,
gpointer user_data)
{
- session_set_playlist_order (2);
- pl_set_order (2);
+ conf_set_int ("playback.order", 2);
}
@@ -727,8 +762,7 @@ void
on_loop_all_activate (GtkMenuItem *menuitem,
gpointer user_data)
{
- session_set_playlist_looping (0);
- pl_set_loop_mode (0);
+ conf_set_int ("playback.loop", 0);
}
@@ -736,8 +770,7 @@ void
on_loop_single_activate (GtkMenuItem *menuitem,
gpointer user_data)
{
- session_set_playlist_looping (2);
- pl_set_loop_mode (2);
+ conf_set_int ("playback.loop", 2);
}
@@ -745,8 +778,7 @@ void
on_loop_disable_activate (GtkMenuItem *menuitem,
gpointer user_data)
{
- session_set_playlist_looping (1);
- pl_set_loop_mode (1);
+ conf_set_int ("playback.loop", 1);
}
void
@@ -924,6 +956,12 @@ seekbar_draw (GtkWidget *widget) {
if (!cr) {
return;
}
+ if (!str_playing_song.decoder || str_playing_song._duration < 0) {
+ clearlooks_rounded_rectangle (cr, 2, widget->allocation.height/2-4, widget->allocation.width-4, 8, 4, 0xff);
+ theme_set_cairo_source_rgb (cr, COLO_SEEKBAR_FRONT);
+ cairo_stroke (cr);
+ return;
+ }
float pos = 0;
if (seekbar_moving) {
int x = seekbar_move_x;
@@ -936,8 +974,8 @@ seekbar_draw (GtkWidget *widget) {
pos = x;
}
else {
- if (str_playing_song.decoder && str_playing_song.duration > 0) {
- pos = streamer_get_playpos () / str_playing_song.duration;
+ if (str_playing_song.decoder && str_playing_song._duration > 0) {
+ pos = streamer_get_playpos () / str_playing_song._duration;
pos *= widget->allocation.width;
}
}
@@ -1027,7 +1065,7 @@ on_seekbar_button_release_event (GtkWidget *widget,
seekbar_moving = 0;
seekbar_draw (widget);
seekbar_expose (widget, 0, 0, widget->allocation.width, widget->allocation.height);
- float time = event->x * str_playing_song.duration / (widget->allocation.width);
+ float time = event->x * str_playing_song._duration / (widget->allocation.width);
if (time < 0) {
time = 0;
}
@@ -1160,6 +1198,7 @@ on_mainwin_delete_event (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
+ int conf_close_send_to_tray = conf_get_int ("close_send_to_tray", 0);
if (conf_close_send_to_tray) {
gtk_widget_hide (widget);
}
@@ -1214,7 +1253,7 @@ void
on_scroll_follows_playback_activate (GtkMenuItem *menuitem,
gpointer user_data)
{
- session_set_scroll_follows_playback (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menuitem)));
+ conf_set_int ("playlist.scroll.followplayback", gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menuitem)));
}
@@ -1225,20 +1264,15 @@ on_find_activate (GtkMenuItem *menuitem,
search_start ();
}
-
-
-
-
void
-on_help1_activate (GtkMenuItem *menuitem,
- gpointer user_data)
-{
+show_info_window (const char *fname, const char *title) {
GtkWidget *widget = create_helpwindow ();
+ gtk_window_set_title (GTK_WINDOW (widget), title);
gtk_window_set_transient_for (GTK_WINDOW (widget), GTK_WINDOW (mainwin));
GtkWidget *txt = lookup_widget (widget, "helptext");
GtkTextBuffer *buffer = gtk_text_buffer_new (NULL);
- FILE *fp = fopen (PREFIX "/share/doc/deadbeef/help.txt", "rb");
+ FILE *fp = fopen (fname, "rb");
if (fp) {
fseek (fp, 0, SEEK_END);
size_t s = ftell (fp);
@@ -1260,9 +1294,24 @@ on_help1_activate (GtkMenuItem *menuitem,
gtk_text_buffer_set_text (buffer, error, strlen (error));
}
gtk_text_view_set_buffer (GTK_TEXT_VIEW (txt), buffer);
+ g_object_unref (buffer);
gtk_widget_show (widget);
}
+void
+on_help1_activate (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+ show_info_window (PREFIX "/share/doc/deadbeef/help.txt", "Help");
+}
+
+void
+on_about1_activate (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+ show_info_window (PREFIX "/share/doc/deadbeef/about.txt", "About DeaDBeeF " VERSION);
+}
+
void
on_playhscroll_value_changed (GtkRange *widget,
@@ -1283,3 +1332,432 @@ on_searchhscroll_value_changed (GtkRange *widget,
gtkpl_hscroll (ps, newscroll);
}
+
+gboolean
+on_helpwindow_key_press_event (GtkWidget *widget,
+ GdkEventKey *event,
+ gpointer user_data)
+{
+ if (event->keyval == GDK_Escape) {
+ gtk_widget_hide (widget);
+ gtk_widget_destroy (widget);
+ }
+ return FALSE;
+}
+
+
+void
+on_add_audio_cd_activate (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+ pl_add_file ("all.cda", NULL, NULL);
+ playlist_refresh ();
+}
+
+static GtkWidget *prefwin;
+
+static char alsa_device_names[100][64];
+static int num_alsa_devices;
+
+static void
+gtk_enum_sound_callback (const char *name, const char *desc, void *userdata) {
+ if (num_alsa_devices >= 100) {
+ fprintf (stderr, "wtf!! more than 100 alsa devices??\n");
+ return;
+ }
+ GtkComboBox *combobox = GTK_COMBO_BOX (userdata);
+ gtk_combo_box_append_text (combobox, desc);
+
+ if (!strcmp (conf_get_str ("alsa_soundcard", "default"), name)) {
+ gtk_combo_box_set_active (combobox, num_alsa_devices);
+ }
+
+ strncpy (alsa_device_names[num_alsa_devices], name, 63);
+ alsa_device_names[num_alsa_devices][63] = 0;
+ num_alsa_devices++;
+}
+
+void
+on_preferences_activate (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+ GtkWidget *w = prefwin = create_prefwin ();
+ gtk_window_set_transient_for (GTK_WINDOW (w), GTK_WINDOW (mainwin));
+
+ // alsa_soundcard
+
+ const char *s = conf_get_str ("alsa_soundcard", "default");
+ GtkComboBox *combobox = GTK_COMBO_BOX (lookup_widget (w, "pref_soundcard"));
+ gtk_combo_box_append_text (combobox, "Default Audio Device");
+ if (!strcmp (s, "default")) {
+ gtk_combo_box_set_active (combobox, 0);
+ }
+ num_alsa_devices = 1;
+ strcpy (alsa_device_names[0], "default");
+ palsa_enum_soundcards (gtk_enum_sound_callback, combobox);
+
+ // alsa resampling
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "pref_alsa_resampling")), conf_get_int ("alsa.resample", 0));
+
+ // alsa freeonstop
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "pref_alsa_freewhenstopped")), conf_get_int ("alsa.freeonstop", 0));
+
+ // src_quality
+ combobox = GTK_COMBO_BOX (lookup_widget (w, "pref_src_quality"));
+ gtk_combo_box_set_active (combobox, conf_get_int ("src_quality", 2));
+
+ // replaygain_mode
+ combobox = GTK_COMBO_BOX (lookup_widget (w, "pref_replaygain_mode"));
+ gtk_combo_box_set_active (combobox, conf_get_int ("replaygain_mode", 0));
+
+ // replaygain_scale
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "pref_replaygain_scale")), conf_get_int ("replaygain_scale", 1));
+
+ // close_send_to_tray
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "pref_close_send_to_tray")), conf_get_int ("close_send_to_tray", 0));
+
+ // network
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "pref_network_enableproxy")), conf_get_int ("network.proxy", 0));
+ gtk_entry_set_text (GTK_ENTRY (lookup_widget (w, "pref_network_proxyaddress")), conf_get_str ("network.proxy.address", ""));
+ gtk_entry_set_text (GTK_ENTRY (lookup_widget (w, "pref_network_proxyport")), conf_get_str ("network.proxy.port", "8080"));
+ combobox = GTK_COMBO_BOX (lookup_widget (w, "pref_network_proxytype"));
+ const char *type = conf_get_str ("network.proxy.type", "HTTP");
+ if (!strcasecmp (type, "HTTP")) {
+ gtk_combo_box_set_active (combobox, 0);
+ }
+ else if (!strcasecmp (type, "HTTP_1_0")) {
+ gtk_combo_box_set_active (combobox, 1);
+ }
+ else if (!strcasecmp (type, "SOCKS4")) {
+ gtk_combo_box_set_active (combobox, 2);
+ }
+ else if (!strcasecmp (type, "SOCKS5")) {
+ gtk_combo_box_set_active (combobox, 3);
+ }
+ else if (!strcasecmp (type, "SOCKS4A")) {
+ gtk_combo_box_set_active (combobox, 4);
+ }
+ else if (!strcasecmp (type, "SOCKS5_HOSTNAME")) {
+ gtk_combo_box_set_active (combobox, 5);
+ }
+
+ // list of plugins
+ GtkTreeView *tree = GTK_TREE_VIEW (lookup_widget (w, "pref_pluginlist"));
+ GtkListStore *store = gtk_list_store_new (1, G_TYPE_STRING);//GTK_LIST_STORE (gtk_tree_view_get_model (tree));
+ GtkCellRenderer *rend = gtk_cell_renderer_text_new ();
+ GtkTreeViewColumn *col = gtk_tree_view_column_new_with_attributes ("Title", rend, "text", 0, NULL);
+ gtk_tree_view_append_column (tree, col);
+ DB_plugin_t **plugins = plug_get_list ();
+ int i;
+ for (i = 0; plugins[i]; i++) {
+ GtkTreeIter it;
+ gtk_list_store_append (store, &it);
+ gtk_list_store_set (store, &it, 0, plugins[i]->name, -1);
+ }
+ gtk_tree_view_set_model (tree, GTK_TREE_MODEL (store));
+
+ gtk_widget_show (w);
+}
+
+
+void
+on_pref_soundcard_changed (GtkComboBox *combobox,
+ gpointer user_data)
+{
+ int active = gtk_combo_box_get_active (combobox);
+ if (active >= 0 && active < num_alsa_devices) {
+ conf_set_str ("alsa_soundcard", alsa_device_names[active]);
+ messagepump_push (M_CONFIGCHANGED, 0, 0, 0);
+ }
+}
+
+void
+on_pref_alsa_resampling_clicked (GtkButton *button,
+ gpointer user_data)
+{
+ int active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+ conf_set_int ("alsa.resample", active);
+ messagepump_push (M_CONFIGCHANGED, 0, 0, 0);
+}
+
+
+void
+on_pref_src_quality_changed (GtkComboBox *combobox,
+ gpointer user_data)
+{
+ int active = gtk_combo_box_get_active (combobox);
+ conf_set_int ("src_quality", active == -1 ? 2 : active);
+ messagepump_push (M_CONFIGCHANGED, 0, 0, 0);
+}
+
+
+void
+on_pref_replaygain_mode_changed (GtkComboBox *combobox,
+ gpointer user_data)
+{
+ int active = gtk_combo_box_get_active (combobox);
+ conf_set_int ("replaygain_mode", active == -1 ? 0 : active);
+ messagepump_push (M_CONFIGCHANGED, 0, 0, 0);
+}
+
+void
+on_pref_replaygain_scale_clicked (GtkButton *button,
+ gpointer user_data)
+{
+ int active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+ conf_set_int ("replaygain_scale", active);
+ messagepump_push (M_CONFIGCHANGED, 0, 0, 0);
+}
+
+
+void
+on_pref_close_send_to_tray_clicked (GtkButton *button,
+ gpointer user_data)
+{
+ int active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+ conf_set_int ("close_send_to_tray", active);
+ messagepump_push (M_CONFIGCHANGED, 0, 0, 0);
+}
+
+
+void
+on_pref_plugin_configure_activate (GtkButton *button,
+ gpointer user_data)
+{
+}
+
+void
+on_pref_pluginlist_cursor_changed (GtkTreeView *treeview,
+ gpointer user_data)
+{
+ GtkTreePath *path;
+ GtkTreeViewColumn *col;
+ gtk_tree_view_get_cursor (treeview, &path, &col);
+ if (!path || !col) {
+ // reset
+ return;
+ }
+ int *indices = gtk_tree_path_get_indices (path);
+ DB_plugin_t **plugins = plug_get_list ();
+ DB_plugin_t *p = plugins[*indices];
+ assert (p);
+ GtkWidget *w = prefwin;//GTK_WIDGET (gtk_widget_get_parent_window (GTK_WIDGET (treeview)));
+ assert (w);
+ GtkEntry *e = GTK_ENTRY (lookup_widget (w, "pref_plugin_descr"));
+ gtk_entry_set_text (e, p->descr ? p->descr : "");
+ e = GTK_ENTRY (lookup_widget (w, "pref_plugin_author"));
+ gtk_entry_set_text (e, p->author ? p->author : "");
+ e = GTK_ENTRY (lookup_widget (w, "pref_plugin_email"));
+ gtk_entry_set_text (e, p->email ? p->email : "");
+ e = GTK_ENTRY (lookup_widget (w, "pref_plugin_website"));
+ gtk_entry_set_text (e, p->website ? p->website : "");
+}
+
+
+
+void
+on_artist_activate (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+
+}
+
+
+void
+on_album_activate (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+
+}
+
+
+void
+on_tracknum_activate (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+
+}
+
+
+void
+on_duration_activate (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+
+}
+
+
+void
+on_playing_activate (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+
+}
+
+
+void
+on_title_activate (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+
+}
+
+
+void
+on_custom_activate (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+
+}
+
+
+void
+on_remove_column_activate (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+
+}
+
+
+void
+on_pref_alsa_freewhenstopped_clicked (GtkButton *button,
+ gpointer user_data)
+{
+ int active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+ conf_set_int ("alsa.freeonstop", active);
+}
+
+void
+on_pref_network_proxyaddress_changed (GtkEditable *editable,
+ gpointer user_data)
+{
+ conf_set_str ("network.proxy.address", gtk_entry_get_text (GTK_ENTRY (editable)));
+}
+
+
+void
+on_pref_network_enableproxy_clicked (GtkButton *button,
+ gpointer user_data)
+{
+ conf_set_int ("network.proxy", gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)));
+}
+
+
+void
+on_pref_network_proxyport_changed (GtkEditable *editable,
+ gpointer user_data)
+{
+ conf_set_int ("network.proxy.port", atoi (gtk_entry_get_text (GTK_ENTRY (editable))));
+}
+
+
+void
+on_pref_network_proxytype_changed (GtkComboBox *combobox,
+ gpointer user_data)
+{
+
+ int active = gtk_combo_box_get_active (combobox);
+ switch (active) {
+ case 0:
+ conf_set_str ("network.proxy.type", "HTTP");
+ break;
+ case 1:
+ conf_set_str ("network.proxy.type", "HTTP_1_0");
+ break;
+ case 2:
+ conf_set_str ("network.proxy.type", "SOCKS4");
+ break;
+ case 3:
+ conf_set_str ("network.proxy.type", "SOCKS5");
+ break;
+ case 4:
+ conf_set_str ("network.proxy.type", "SOCKS4A");
+ break;
+ case 5:
+ conf_set_str ("network.proxy.type", "SOCKS5_HOSTNAME");
+ break;
+ default:
+ conf_set_str ("network.proxy.type", "HTTP");
+ break;
+ }
+}
+
+
+gboolean
+on_prefwin_key_press_event (GtkWidget *widget,
+ GdkEventKey *event,
+ gpointer user_data)
+{
+ if (event->keyval == GDK_Escape) {
+ gtk_widget_hide (widget);
+ gtk_widget_destroy (widget);
+ }
+ return FALSE;
+}
+
+
+static GtkWidget *addlocation_window;
+
+void
+on_add_location_activate (GtkMenuItem *menuitem,
+ gpointer user_data)
+{
+ GtkWidget *widget = addlocation_window = create_addlocation ();
+ gtk_window_set_transient_for (GTK_WINDOW (widget), GTK_WINDOW (mainwin));
+ gtk_widget_show (widget);
+}
+
+static void
+add_location_destroy (void) {
+ if (addlocation_window) {
+ gtk_widget_hide (addlocation_window);
+ gtk_widget_destroy (addlocation_window);
+ addlocation_window = NULL;
+ }
+}
+
+void
+on_addlocation_entry_activate (GtkEntry *entry,
+ gpointer user_data)
+{
+ const char *text = gtk_entry_get_text (entry);
+ if (text) {
+ pl_add_file (text, NULL, NULL);
+ playlist_refresh ();
+ }
+ add_location_destroy ();
+}
+
+void
+on_addlocation_ok_clicked (GtkButton *button,
+ gpointer user_data)
+{
+ if (addlocation_window) {
+ GtkEntry *entry = GTK_ENTRY (lookup_widget (addlocation_window, "addlocation_entry"));
+ if (entry) {
+ const char *text = gtk_entry_get_text (entry);
+ if (text) {
+ pl_add_file (text, NULL, NULL);
+ playlist_refresh ();
+ }
+ }
+ }
+ add_location_destroy ();
+}
+
+gboolean
+on_addlocation_key_press_event (GtkWidget *widget,
+ GdkEventKey *event,
+ gpointer user_data)
+{
+ if (event->keyval == GDK_Escape) {
+ add_location_destroy ();
+ }
+ return FALSE;
+}
+
+
+
+
+
+
diff --git a/callbacks.h b/callbacks.h
index 120e7488..d2e3f61a 100644
--- a/callbacks.h
+++ b/callbacks.h
@@ -537,3 +537,158 @@ on_playhscroll_value_changed (GtkRange *range,
void
on_searchhscroll_value_changed (GtkRange *range,
gpointer user_data);
+
+gboolean
+on_helpwindow_key_press_event (GtkWidget *widget,
+ GdkEventKey *event,
+ gpointer user_data);
+
+void
+on_add_audio_cd_activate (GtkMenuItem *menuitem,
+ gpointer user_data);
+
+void
+on_preferences_activate (GtkMenuItem *menuitem,
+ gpointer user_data);
+
+void
+on_pref_soundcard_changed (GtkComboBox *combobox,
+ gpointer user_data);
+
+void
+on_pref_samplerate_changed (GtkEditable *editable,
+ gpointer user_data);
+
+void
+on_pref_src_quality_changed (GtkComboBox *combobox,
+ gpointer user_data);
+
+void
+on_pref_replaygain_clicked (GtkButton *button,
+ gpointer user_data);
+
+void
+on_pref_replaygain_scale_clicked (GtkButton *button,
+ gpointer user_data);
+
+void
+on_pref_close_send_to_tray_clicked (GtkButton *button,
+ gpointer user_data);
+
+void
+on_pref_plugin_configure_activate (GtkButton *button,
+ gpointer user_data);
+
+void
+on_pref_src_quality_changed (GtkComboBox *combobox,
+ gpointer user_data);
+
+void
+on_conf_replaygain_mode_changed (GtkComboBox *combobox,
+ gpointer user_data);
+
+void
+on_pref_replaygain_mode_changed (GtkComboBox *combobox,
+ gpointer user_data);
+
+void
+on_pref_pluginlist_cursor_changed (GtkTreeView *treeview,
+ gpointer user_data);
+
+gboolean
+on_header_popup_menu (GtkWidget *widget,
+ gpointer user_data);
+
+void
+on_artist_activate (GtkMenuItem *menuitem,
+ gpointer user_data);
+
+void
+on_album_activate (GtkMenuItem *menuitem,
+ gpointer user_data);
+
+void
+on_tracknum_activate (GtkMenuItem *menuitem,
+ gpointer user_data);
+
+void
+on_duration_activate (GtkMenuItem *menuitem,
+ gpointer user_data);
+
+void
+on_playing_activate (GtkMenuItem *menuitem,
+ gpointer user_data);
+
+void
+on_title_activate (GtkMenuItem *menuitem,
+ gpointer user_data);
+
+void
+on_custom_activate (GtkMenuItem *menuitem,
+ gpointer user_data);
+
+void
+on_remove_column_activate (GtkMenuItem *menuitem,
+ gpointer user_data);
+
+void
+on_pref_alsa_resampling_clicked (GtkButton *button,
+ gpointer user_data);
+
+void
+on_pref_alsa_freewhenstopped_clicked (GtkButton *button,
+ gpointer user_data);
+
+void
+on_pref_soundcard_editing_done (GtkCellEditable *celleditable,
+ gpointer user_data);
+
+void
+on_pref_soundcard_changed (GtkComboBox *combobox,
+ gpointer user_data);
+
+void
+on_pref_network_proxyaddress_changed (GtkEditable *editable,
+ gpointer user_data);
+
+void
+on_pref_network_enableproxy_clicked (GtkButton *button,
+ gpointer user_data);
+
+void
+on_pref_network_proxyport_changed (GtkEditable *editable,
+ gpointer user_data);
+
+void
+on_pref_network_proxytype_changed (GtkComboBox *combobox,
+ gpointer user_data);
+
+gboolean
+on_prefwin_key_press_event (GtkWidget *widget,
+ GdkEventKey *event,
+ gpointer user_data);
+
+void
+on_addlocation_ok_activate (GtkButton *button,
+ gpointer user_data);
+
+gboolean
+on_addlocation_key_press_event (GtkWidget *widget,
+ GdkEventKey *event,
+ gpointer user_data);
+
+void
+on_add_location_activate (GtkMenuItem *menuitem,
+ gpointer user_data);
+
+void
+on_location_activate (GtkEntry *entry,
+ gpointer user_data);
+
+void
+on_addlocation_ok_clicked (GtkButton *button,
+ gpointer user_data);
+
+void
+on_addlocation_entry_activate (GtkEntry *entry,
+ gpointer user_data);
diff --git a/cdumb.c b/cdumb.c
index 2dae5754..0aaaec71 100644
--- a/cdumb.c
+++ b/cdumb.c
@@ -45,12 +45,46 @@ cdumb_startrenderer (void);
static DUH*
open_module(const char *fname, const char *ext, int *start_order, int *is_it, int *is_dos, const char **filetype);
+static DUMBFILE_SYSTEM dumb_vfs;
+
+static int
+dumb_vfs_skip (void *f, long n) {
+ return deadbeef->fseek (f, n, SEEK_CUR);
+}
+
+static int
+dumb_vfs_getc (void *f) {
+ uint8_t c;
+ deadbeef->fread (&c, 1, 1, f);
+ return (int)c;
+}
+
+static long
+dumb_vfs_getnc (char *ptr, long n, void *f) {
+ return deadbeef->fread (ptr, 1, n, f);
+}
+
+static void
+dumb_vfs_close (void *f) {
+ deadbeef->fclose (f);
+}
+
+static void
+dumb_register_db_vfs (void) {
+ dumb_vfs.open = (void *(*)(const char *))deadbeef->fopen;
+ dumb_vfs.skip = dumb_vfs_skip;
+ dumb_vfs.getc = dumb_vfs_getc;
+ dumb_vfs.getnc = dumb_vfs_getnc;
+ dumb_vfs.close = dumb_vfs_close;
+ register_dumbfile_system (&dumb_vfs);
+}
+
static int
cdumb_init (DB_playItem_t *it) {
if (!dumb_initialized) {
atexit (&dumb_exit);
}
- dumb_register_stdfiles ();
+ dumb_register_db_vfs ();
int start_order = 0;
int is_dos, is_it;
@@ -160,12 +194,12 @@ static DUH * open_module(const char *fname, const char *ext, int *start_order, i
*is_dos = 1;
char ptr[2000];
- FILE *fp = fopen (fname, "rb");
+ DB_FILE *fp = deadbeef->fopen (fname);
if (!fp) {
return NULL;
}
- int size = fread (ptr, 1, 2000, fp);
- fclose (fp);
+ int size = deadbeef->fread (ptr, 1, 2000, fp);
+ deadbeef->fclose (fp);
DUMBFILE * f = dumbfile_open (fname);
if (!f) {
@@ -730,7 +764,7 @@ cdumb_insert (DB_playItem_t *after, const char *fname) {
int start_order = 0;
int is_it;
int is_dos;
- dumb_register_stdfiles ();
+ dumb_register_db_vfs ();
const char *ftype;
DUH* duh = open_module(fname, ext, &start_order, &is_it, &is_dos, &ftype);
if (!duh) {
@@ -747,7 +781,7 @@ cdumb_insert (DB_playItem_t *after, const char *fname) {
deadbeef->pl_add_meta (it, "title", NULL);
}
dumb_it_do_initial_runthrough (duh);
- it->duration = duh_get_length (duh)/65536.0f;
+ deadbeef->pl_set_item_duration (it, duh_get_length (duh)/65536.0f);
it->filetype = ftype;
// printf ("duration: %f\n", plugin.info.duration);
after = deadbeef->pl_insert_item (after, it);
@@ -765,6 +799,7 @@ static DB_decoder_t plugin = {
.plugin.version_minor = 1,
.plugin.type = DB_PLUGIN_DECODER,
.plugin.name = "DUMB module player",
+ .plugin.descr = "module player based on DUMB library",
.plugin.author = "Alexey Yakovenko",
.plugin.email = "waker@users.sourceforge.net",
.plugin.website = "http://deadbeef.sf.net",
diff --git a/cgme.c b/cgme.c
index ba47f8f0..21aeb292 100644
--- a/cgme.c
+++ b/cgme.c
@@ -42,7 +42,7 @@ cgme_init (DB_playItem_t *it) {
plugin.info.bps = 16;
plugin.info.channels = 2;
plugin.info.samplerate = deadbeef->playback_get_samplerate ();
- duration = it->duration;
+ duration = deadbeef->pl_get_item_duration (it);
reallength = inf.length;
nzerosamples = 0;
plugin.info.readpos = 0;
@@ -136,10 +136,10 @@ cgme_insert (DB_playItem_t *after, const char *fname) {
snprintf (trk, 10, "%d", i+1);
deadbeef->pl_add_meta (it, "track", trk);
if (inf.length == -1) {
- it->duration = 300;
+ deadbeef->pl_set_item_duration (it, 300);
}
else {
- it->duration = (float)inf.length/1000.f;
+ deadbeef->pl_set_item_duration (it, (float)inf.length/1000.f);
}
const char *ext = fname + strlen (fname) - 1;
while (ext >= fname && *ext != '.') {
@@ -199,6 +199,7 @@ static DB_decoder_t plugin = {
.plugin.version_minor = 1,
.plugin.type = DB_PLUGIN_DECODER,
.plugin.name = "Game_Music_Emu decoder",
+ .plugin.descr = "chiptune music player based on GME",
.plugin.author = "Alexey Yakovenko",
.plugin.email = "waker@users.sourceforge.net",
.plugin.website = "http://deadbeef.sf.net",
diff --git a/conf.c b/conf.c
index 017da7d0..378309b3 100644
--- a/conf.c
+++ b/conf.c
@@ -19,16 +19,10 @@
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
+#include "conf.h"
-char conf_alsa_soundcard[1024] = "default"; // name of soundcard for alsa player
-int conf_samplerate = 48000;
-int conf_src_quality = 1;
-char conf_hvsc_path[1024] = "";
-int conf_hvsc_enable = 0;
-char conf_blacklist_plugins[1024]; // plugins listed in this option will not be loaded
-int conf_close_send_to_tray = 0;
-int conf_replaygain_mode = 0;
-int conf_replaygain_scale = 1;
+static DB_conf_item_t *conf_items;
+static int changed = 0;
int
conf_load (void) {
@@ -41,6 +35,7 @@ conf_load (void) {
return -1;
}
int line = 0;
+ DB_conf_item_t *tail = NULL;
while (fgets (str, 1024, fp) != NULL) {
line++;
if (str[0] == '#' || str[0] <= 0x20) {
@@ -70,48 +65,164 @@ conf_load (void) {
p++;
}
*p = 0;
- if (!strcasecmp (str, "samplerate")) {
- conf_samplerate = atoi (value);
- }
- else if (!strcasecmp (str, "alsa_soundcard")) {
- strncpy (conf_alsa_soundcard, value, sizeof (conf_alsa_soundcard));
- conf_alsa_soundcard[sizeof (conf_alsa_soundcard) - 1] = 0;
- }
- else if (!strcasecmp (str, "src_quality")) {
- conf_src_quality = atoi (value);
- }
- else if (!strcasecmp (str, "hvsc_path")) {
- strncpy (conf_hvsc_path, value, sizeof (conf_hvsc_path));
- conf_hvsc_path[sizeof (conf_hvsc_path)-1] = 0;
- }
- else if (!strcasecmp (str, "hvsc_enable")) {
- conf_hvsc_enable = atoi (value);
+ // new items are appended, to preserve order
+ conf_set_str (str, value);
+ }
+ fclose (fp);
+ changed = 0;
+ return 0;
+}
+
+int
+conf_save (void) {
+ extern char dbconfdir[1024]; // $HOME/.config/deadbeef
+ char str[1024];
+ snprintf (str, 1024, "%s/config", dbconfdir);
+ FILE *fp = fopen (str, "w+t");
+ if (!fp) {
+ fprintf (stderr, "failed to open config file for writing\n");
+ return -1;
+ }
+ for (DB_conf_item_t *it = conf_items; it; it = it->next) {
+ fprintf (fp, "%s %s\n", it->key, it->value);
+ }
+ fclose (fp);
+ return 0;
+}
+
+void
+conf_free (void) {
+ DB_conf_item_t *next = NULL;
+ for (DB_conf_item_t *it = conf_items; it; it = next) {
+ next = it->next;
+ conf_item_free (it);
+ }
+}
+
+void
+conf_item_free (DB_conf_item_t *it) {
+ if (it) {
+ if (it->key) {
+ free (it->key);
}
- else if (!strcasecmp (str, "blacklist_plugins")) {
- fprintf (stderr, "blacklisted plugins: %s\n", value);
- strncpy (conf_blacklist_plugins, value, sizeof (conf_blacklist_plugins));
- conf_blacklist_plugins[sizeof (conf_blacklist_plugins)-1] = 0;
+ if (it->value) {
+ free (it->value);
}
- else if (!strcasecmp (str, "close_send_to_tray")) {
- conf_close_send_to_tray = atoi (value);
+ free (it);
+ }
+}
+
+const char *
+conf_get_str (const char *key, const char *def) {
+ for (DB_conf_item_t *it = conf_items; it; it = it->next) {
+ if (!strcasecmp (key, it->key)) {
+ return it->value;
}
- else if (!strcasecmp (str, "replaygain_mode")) {
- int rg = atoi (value);
- if (rg >= 0 && rg <= 2) {
- conf_replaygain_mode = atoi (value);
- }
- else {
- fprintf (stderr, "config warning: replaygain_mode must be one of 0, 1 or 2\n");
- }
+ }
+ return def;
+}
+
+float
+conf_get_float (const char *key, float def) {
+ const char *v = conf_get_str (key, NULL);
+ return v ? atof (v) : def;
+}
+
+int
+conf_get_int (const char *key, int def) {
+ const char *v = conf_get_str (key, NULL);
+ return v ? atoi (v) : def;
+}
+
+DB_conf_item_t *
+conf_find (const char *group, DB_conf_item_t *prev) {
+ int l = strlen (group);
+ for (DB_conf_item_t *it = prev ? prev->next : conf_items; it; it = it->next) {
+ if (!strncasecmp (group, it->key, l)) {
+ return it;
}
- else if (!strcasecmp (str, "replaygain_scale")) {
- conf_replaygain_scale = atoi (value);
+ }
+ return NULL;
+}
+
+void
+conf_set_str (const char *key, const char *val) {
+ changed = 1;
+ DB_conf_item_t *prev = NULL;
+ for (DB_conf_item_t *it = conf_items; it; it = it->next) {
+ int cmp = strcasecmp (key, it->key);
+ if (!cmp) {
+ free (it->value);
+ it->value = strdup (val);
+ return;
+ }
+ else if (cmp < 0) {
+ break;
+ }
+ prev = it;
+ }
+ DB_conf_item_t *it = malloc (sizeof (DB_conf_item_t));
+ memset (it, 0, sizeof (DB_conf_item_t));
+ it->key = strdup (key);
+ it->value = strdup (val);
+ if (prev) {
+ DB_conf_item_t *next = prev->next;
+ prev->next = it;
+ it->next = next;
+ }
+ else {
+ it->next = conf_items;
+ conf_items = it;
+ }
+}
+
+void
+conf_set_int (const char *key, int val) {
+ char s[10];
+ snprintf (s, sizeof (s), "%d", val);
+ conf_set_str (key, s);
+}
+
+void
+conf_set_float (const char *key, float val) {
+ char s[10];
+ snprintf (s, sizeof (s), "%0.7f", val);
+ conf_set_str (key, s);
+}
+
+int
+conf_ischanged (void) {
+ return changed;
+}
+
+void
+conf_setchanged (int c) {
+ changed = c;
+}
+
+void
+conf_remove_items (const char *key) {
+ int l = strlen (key);
+ DB_conf_item_t *prev = NULL;
+ DB_conf_item_t *it;
+ for (it = conf_items; it; prev = it, it = it->next) {
+ if (!strncasecmp (key, it->key, l)) {
+ break;
}
- else {
- fprintf (stderr, "error in config file line %d\n", line);
+ }
+ DB_conf_item_t *next = NULL;
+ while (it) {
+ next = it->next;
+ conf_item_free (it);
+ it = next;
+ if (!it || strncasecmp (key, it->key, l)) {
+ break;
}
}
- fclose (fp);
- return 0;
+ if (prev) {
+ prev->next = next;
+ }
+ else {
+ conf_items = next;
+ }
}
-
diff --git a/conf.h b/conf.h
index 683db68b..0a76aaa2 100644
--- a/conf.h
+++ b/conf.h
@@ -18,17 +18,49 @@
#ifndef __CONF_H
#define __CONF_H
-extern char conf_alsa_soundcard[1024];
-extern int conf_samplerate;
-extern int conf_src_quality;
-extern char conf_hvsc_path[1024];
-extern int conf_hvsc_enable;
-extern char conf_blacklist_plugins[1024];
-extern int conf_close_send_to_tray;
-extern int conf_replaygain_mode;
-extern int conf_replaygain_scale;
+#include "deadbeef.h"
int
conf_load (void);
+int
+conf_save (void);
+
+void
+conf_free (void);
+
+int
+conf_ischanged (void);
+
+void
+conf_setchanged (int c);
+
+const char *
+conf_get_str (const char *key, const char *def);
+
+float
+conf_get_float (const char *key, float def);
+
+int
+conf_get_int (const char *key, int def);
+
+void
+conf_set_str (const char *key, const char *val);
+
+void
+conf_set_int (const char *key, int val);
+
+void
+conf_set_float (const char *key, float val);
+
+DB_conf_item_t *
+conf_find (const char *group, DB_conf_item_t *prev);
+
+// remove all items starting with key
+void
+conf_remove_items (const char *key);
+
+void
+conf_item_free (DB_conf_item_t *it);
+
#endif // __CONF_H
diff --git a/configure.ac b/configure.ac
index 6cba4710..3855f9aa 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3,7 +3,7 @@ AC_INIT
AC_CONFIG_HEADER(config.h)
PACKAGE="deadbeef"
-VERSION="0.2.3.2"
+VERSION="0.3.0-pre1"
AM_INIT_AUTOMAKE($PACKAGE,$VERSION)
@@ -47,17 +47,23 @@ if test ${HAVE_SSE2}; then
fi
AC_SUBST(SIMD_FLAGS)
-dnl lastfm plugin
+dnl curl lib
AC_CHECK_LIB([curl], [main], [HAVE_CURL=1])
+AM_CONDITIONAL(HAVE_CURL, test $HAVE_CURL)
+if test ${HAVE_CURL}; then
+ CURL_LIBS="-lcurl"
+ AC_SUBST(CURL_LIBS)
+fi
+
+dnl lastfm plugin
if test ${HAVE_CURL}; then
- LFM_LIBS="-lcurl"
LFM_DIR="plugins/lastfm"
- AC_SUBST(LFM_LIBS)
AC_SUBST(LFM_DIR)
fi
dnl mpgmad plugin
AC_CHECK_LIB([mad], [main], [HAVE_LIBMAD=1])
+AM_CONDITIONAL(HAVE_LIBMAD, test $HAVE_LIBMAD)
if test ${HAVE_LIBMAD}; then
MAD_LIBS="-lmad"
MPGMAD_DIR="plugins/mpgmad"
@@ -68,7 +74,8 @@ fi
dnl vorbis plugin
AC_CHECK_LIB([vorbis], [main], [HAVE_VORBIS=1])
AC_CHECK_LIB([vorbisfile], [main], [HAVE_VORBISFILE=1])
-
+AM_CONDITIONAL(HAVE_VORBIS, test $HAVE_VORBIS)
+AM_CONDITIONAL(HAVE_VORBISFILE, test $HAVE_VORBISFILE)
if test ${HAVE_VORBIS} && test ${HAVE_VORBISFILE} ; then
VORBIS_LIBS="-lvorbis -lvorbisfile"
VORBIS_DIR="plugins/vorbis"
@@ -78,7 +85,7 @@ fi
dnl flac plugin
AC_CHECK_LIB([FLAC], [main], [HAVE_FLAC=1])
-
+AM_CONDITIONAL(HAVE_FLAC, test $HAVE_FLAC)
if test ${HAVE_FLAC} ; then
FLAC_LIBS="-lFLAC"
FLAC_DIR="plugins/flac"
@@ -88,7 +95,7 @@ fi
dnl wavpack plugin
AC_CHECK_LIB([wavpack], [main], [HAVE_WAVPACK=1])
-
+AM_CONDITIONAL(HAVE_WAVPACK, test $HAVE_WAVPACK)
if test ${HAVE_WAVPACK} ; then
WAVPACK_LIBS="-lwavpack"
WAVPACK_DIR="plugins/wavpack"
@@ -98,7 +105,7 @@ fi
dnl libsndfile plugin
AC_CHECK_LIB([sndfile], [main], [HAVE_SNDFILE=1])
-
+AM_CONDITIONAL(HAVE_SNDFILE, test $HAVE_SNDFILE)
if test ${HAVE_SNDFILE} ; then
SNDFILE_LIBS="-lsndfile"
SNDFILE_DIR="plugins/sndfile"
@@ -106,6 +113,67 @@ if test ${HAVE_SNDFILE} ; then
AC_SUBST(SNDFILE_DIR)
fi
+dnl vfs_curl plugin
+if test ${HAVE_CURL}; then
+ VFS_CURL_DIR="plugins/vfs_curl"
+ AC_SUBST(VFS_CURL_DIR)
+fi
+
+dnl faad2 plugin
+dnl AC_CHECK_LIB([faad], [main], [HAVE_FAAD=1])
+dnl AC_CHECK_LIB([mp4ff], [main], [HAVE_MP4FF=1])
+dnl AM_CONDITIONAL(HAVE_FAAD, test $HAVE_FAAD)
+dnl AM_CONDITIONAL(HAVE_MP4FF, test $HAVE_MP4FF)
+dnl if test ${HAVE_FAAD} && test ${HAVE_MP4FF} ; then
+dnl FAAD2_LIBS="-lfaad -lmp4ff -lmp4v2"
+dnl FAAD2_DIR="plugins/faad2"
+dnl AC_SUBST(FAAD2_LIBS)
+dnl AC_SUBST(FAAD2_DIR)
+dnl fi
+
+dnl cdda plugin
+AC_CHECK_LIB([cdio], [main], [HAVE_CDIO=1])
+AC_CHECK_LIB([cddb], [main], [HAVE_CDDB=1])
+AM_CONDITIONAL(HAVE_CDIO, test $HAVE_CDIO)
+AM_CONDITIONAL(HAVE_CDDB, test $HAVE_CDDB)
+if test ${HAVE_CDIO} && test ${HAVE_CDDB}; then
+ CDDA_LIBS="-lcdio -lcddb"
+ CDDA_DIR="plugins/cdda"
+ AC_SUBST(CDDA_LIBS)
+ AC_SUBST(CDDA_DIR)
+fi
+
+dnl print summary
+echo
+echo "plugin summary:"
+echo
+
+AC_DEFUN([PRINT_PLUGIN_INFO],
+ [
+ if $3 ; then
+ echo " $1: yes - $2"
+ else
+ echo " $1: no - $2"
+ fi
+ ]
+)
+
+PRINT_PLUGIN_INFO([stdio],[Standard IO plugin],[true])
+PRINT_PLUGIN_INFO([sid],[SID player based on libsidplay2],[true])
+PRINT_PLUGIN_INFO([gme],[chiptune music player based on GME],[true])
+PRINT_PLUGIN_INFO([dumb],[module player based on DUMB library],[true])
+PRINT_PLUGIN_INFO([ffap],[Monkey's audio (APE) decoder],[true])
+PRINT_PLUGIN_INFO([lastfm],[last.fm scrobbler],[test $HAVE_CURL])
+PRINT_PLUGIN_INFO([mpgmad],[mpeg player based on libmad],[test $HAVE_LIBMAD])
+PRINT_PLUGIN_INFO([vorbis],[ogg vorbis player],[test $HAVE_VORBISFILE && test $HAVE_VORBIS])
+PRINT_PLUGIN_INFO([flac],[flac player],[test $HAVE_FLAC])
+PRINT_PLUGIN_INFO([wavpack],[wavpack player],[test $HAVE_WAVPACK])
+PRINT_PLUGIN_INFO([sndfile],[PCM (wav,aiff,etc) player based on libsndfile],[test $HAVE_SNDFILE])
+PRINT_PLUGIN_INFO([vfs_curl],[http/ftp streaming support],[test $HAVE_CURL])
+dnl PRINT_PLUGIN_INFO([faad2],[aac/mp4 player],[test $HAVE_FAAD && test $HAVE_MP4FF])
+PRINT_PLUGIN_INFO([cdda],[cd audio player],[test $HAVE_CDIO && test $HAVE_CDDB])
+echo
+
AC_OUTPUT([
Makefile
pixmaps/Makefile
@@ -122,6 +190,8 @@ plugins/vorbis/Makefile
plugins/flac/Makefile
plugins/wavpack/Makefile
plugins/sndfile/Makefile
+plugins/vfs_curl/Makefile
+plugins/cdda/Makefile
deadbeef.desktop
])
diff --git a/csid.cpp b/csid.cpp
index 286ae6f1..73eaba44 100644
--- a/csid.cpp
+++ b/csid.cpp
@@ -28,10 +28,10 @@
// #include "sidplay/sidendian.h"
#include "deadbeef.h"
-#include "conf.h"
extern "C" {
#include "md5/md5.h"
+#include "conf.h"
}
// forward decls
@@ -60,7 +60,7 @@ static DB_decoder_t plugin = {
/* .plugin.version_minor = */1,
/* .inactive = */0,
/* .plugin.name = */"SID decoder",
- /* .plugin.descr = */"based on libsidplay2",
+ /* .plugin.descr = */"SID player based on libsidplay2",
/* .plugin.author = */"Alexey Yakovenko",
/* .plugin.email = */"waker@users.sourceforge.net",
/* .plugin.website = */"http://deadbeef.sf.net",
@@ -129,9 +129,14 @@ static sldb_t *sldb;
static void sldb_load()
{
fprintf (stderr, "sldb_load\n");
+ int conf_hvsc_enable = conf_get_int ("hvsc_enable", 0);
if (sldb_loaded || !conf_hvsc_enable) {
return;
}
+ const char *conf_hvsc_path = conf_get_str ("hvsc_path", NULL);
+ if (!conf_hvsc_path) {
+ return;
+ }
sldb_loaded = 1;
const char *fname = conf_hvsc_path;
FILE *fp = fopen (fname, "r");
@@ -314,7 +319,7 @@ csid_init (DB_playItem_t *it) {
// resid->create (1);
resid->filter (true);
resid->sampling (deadbeef->playback_get_samplerate ());
- duration = it->duration;
+ duration = deadbeef->pl_get_item_duration (it);
tune = new SidTune (it->fname);
tune->selectSong (it->tracknum+1);
@@ -549,7 +554,7 @@ csid_insert (DB_playItem_t *after, const char *fname) {
// printf ("\n");
// }
}
- it->duration = length;
+ deadbeef->pl_set_item_duration (it, length);
it->filetype = "SID";
after = deadbeef->pl_insert_item (after, it);
diff --git a/deadbeef.glade b/deadbeef.glade
index 7f734701..c71e3a65 100644
--- a/deadbeef.glade
+++ b/deadbeef.glade
@@ -56,7 +56,7 @@
<accelerator key="O" modifiers="GDK_CONTROL_MASK" signal="activate"/>
<child internal-child="image">
- <widget class="GtkImage" id="image46">
+ <widget class="GtkImage" id="image120">
<property name="visible">True</property>
<property name="stock">gtk-open</property>
<property name="icon_size">1</property>
@@ -83,7 +83,7 @@
<signal name="activate" handler="on_add_files_activate" last_modification_time="Sat, 04 Jul 2009 13:04:01 GMT"/>
<child internal-child="image">
- <widget class="GtkImage" id="image47">
+ <widget class="GtkImage" id="image121">
<property name="visible">True</property>
<property name="stock">gtk-add</property>
<property name="icon_size">1</property>
@@ -104,7 +104,7 @@
<signal name="activate" handler="on_add_folders_activate" last_modification_time="Sun, 06 Sep 2009 17:51:40 GMT"/>
<child internal-child="image">
- <widget class="GtkImage" id="image48">
+ <widget class="GtkImage" id="image122">
<property name="visible">True</property>
<property name="stock">gtk-add</property>
<property name="icon_size">1</property>
@@ -118,6 +118,36 @@
</child>
<child>
+ <widget class="GtkImageMenuItem" id="add_audio_cd">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Add Audio CD</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_add_audio_cd_activate" last_modification_time="Sat, 10 Oct 2009 15:29:22 GMT"/>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image123">
+ <property name="visible">True</property>
+ <property name="stock">gtk-add</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="add_location1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Add location</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_add_location_activate" last_modification_time="Sat, 07 Nov 2009 18:03:26 GMT"/>
+ </widget>
+ </child>
+
+ <child>
<widget class="GtkSeparatorMenuItem" id="separatormenuitem1">
<property name="visible">True</property>
</widget>
@@ -132,7 +162,7 @@
<accelerator key="Q" modifiers="GDK_CONTROL_MASK" signal="activate"/>
<child internal-child="image">
- <widget class="GtkImage" id="image49">
+ <widget class="GtkImage" id="image124">
<property name="visible">True</property>
<property name="stock">gtk-quit</property>
<property name="icon_size">1</property>
@@ -166,7 +196,7 @@
<signal name="activate" handler="on_clear1_activate" last_modification_time="Sun, 06 Sep 2009 18:30:03 GMT"/>
<child internal-child="image">
- <widget class="GtkImage" id="image50">
+ <widget class="GtkImage" id="image125">
<property name="visible">True</property>
<property name="stock">gtk-clear</property>
<property name="icon_size">1</property>
@@ -207,7 +237,7 @@
<accelerator key="Delete" modifiers="0" signal="activate"/>
<child internal-child="image">
- <widget class="GtkImage" id="image51">
+ <widget class="GtkImage" id="image126">
<property name="visible">True</property>
<property name="stock">gtk-remove</property>
<property name="icon_size">1</property>
@@ -242,6 +272,21 @@
<accelerator key="F" modifiers="GDK_CONTROL_MASK" signal="activate"/>
</widget>
</child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator5">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="preferences">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Preferences</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_preferences_activate" last_modification_time="Sat, 10 Oct 2009 15:50:29 GMT"/>
+ </widget>
+ </child>
</widget>
</child>
</widget>
@@ -413,7 +458,7 @@
<signal name="activate" handler="on_help1_activate" last_modification_time="Tue, 08 Sep 2009 17:32:06 GMT"/>
<child internal-child="image">
- <widget class="GtkImage" id="image52">
+ <widget class="GtkImage" id="image127">
<property name="visible">True</property>
<property name="stock">gtk-help</property>
<property name="icon_size">1</property>
@@ -684,7 +729,7 @@
<widget class="GtkDrawingArea" id="header">
<property name="height_request">24</property>
<property name="visible">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<signal name="expose_event" handler="on_header_expose_event" last_modification_time="Thu, 06 Aug 2009 14:54:29 GMT"/>
<signal name="configure_event" handler="on_header_configure_event" last_modification_time="Thu, 06 Aug 2009 14:54:33 GMT"/>
<signal name="realize" handler="on_header_realize" last_modification_time="Thu, 06 Aug 2009 14:54:41 GMT"/>
@@ -800,72 +845,6 @@
</child>
</widget>
-<widget class="GtkAboutDialog" id="aboutdialog">
- <property name="border_width">4</property>
- <property name="visible">True</property>
- <property name="destroy_with_parent">True</property>
- <property name="name" translatable="yes">DeaDBeeF</property>
- <property name="copyright" translatable="yes">Copyright © 2009 Alexey Yakovenko</property>
- <property name="comments" translatable="yes"></property>
- <property name="license" translatable="yes">DeaDBeeF - ultimate music player for GNU/Linux systems with X11
-Copyright (C) 2009 Alexey Yakovenko
-
-This program is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
-
-Note that other included libraries have separate licensing terms.
-
-
-DUMB - Dynamic Universal Music Bibliotheque, Version 0.9.3
-Copyright (C) 2001-2005 Ben Davis, Robert J Ohannessian and Julien Cugniere.
-
-
-Game_Music_Emu Version 0.5.2
-Copyright (C) 2003-2006 Shay Green.
-
-
-Functions to compute MD5 message digest of files or memory blocks
-Written by Ulrich Drepper &lt;drepper@gnu.ai.mit.edu&gt;, 1995.
-Modified by Gray Watson &lt;http://256.com/gray/&gt;, 1997.
-
-
-libsidplay2 - commodore 64 SID emulation library
-Copyright (C) Simon White and other authors.
-
-
-ffap - monkey's audio decoder based on apedec code from ffmpeg
-Copyright (c) 2007 Benjamin Zores &lt;ben@geexbox.org&gt;</property>
- <property name="wrap_license">False</property>
- <property name="website">http://deadbeef.sf.net</property>
- <property name="website_label" translatable="yes">website</property>
- <property name="authors">Core developer:
-Alexey Yakovenko
-&lt;waker@users.sourceforge.net&gt;
-
-Hotkeys plugin:
-Viktor Semykin
-&lt;thesame.ml@gmail.com&gt;
-
-Desktop file and Debian packages:
-Alexey A. Smirnov
-&lt;alexey.smirnov@gmx.com&gt;</property>
- <property name="artists">Button artwork:
-Stas &quot;uncle lag&quot; Akimushkin &lt;uncle.lag@gmail.com&gt;</property>
- <property name="translator_credits" translatable="yes" comments="TRANSLATORS: Replace this string with your names, one name per line.">translator-credits</property>
-</widget>
-
<widget class="GtkWindow" id="searchwin">
<property name="width_request">600</property>
<property name="height_request">150</property>
@@ -1243,7 +1222,28 @@ Stas &quot;uncle lag&quot; Akimushkin &lt;uncle.lag@gmail.com&gt;</property>
<property name="spacing">0</property>
<child>
- <placeholder/>
+ <widget class="GtkLabel" id="label22">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">False</property>
+ </packing>
</child>
<child>
@@ -1277,6 +1277,7 @@ Stas &quot;uncle lag&quot; Akimushkin &lt;uncle.lag@gmail.com&gt;</property>
<property name="width_request">600</property>
<property name="height_request">400</property>
<property name="visible">True</property>
+ <property name="events">GDK_KEY_PRESS_MASK</property>
<property name="title" translatable="yes">Help</property>
<property name="type">GTK_WINDOW_TOPLEVEL</property>
<property name="window_position">GTK_WIN_POS_NONE</property>
@@ -1290,6 +1291,7 @@ Stas &quot;uncle lag&quot; Akimushkin &lt;uncle.lag@gmail.com&gt;</property>
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
<property name="focus_on_map">True</property>
<property name="urgency_hint">False</property>
+ <signal name="key_press_event" handler="on_helpwindow_key_press_event" last_modification_time="Sun, 04 Oct 2009 19:06:07 GMT"/>
<child>
<widget class="GtkScrolledWindow" id="scrolledwindow1">
@@ -1323,4 +1325,1169 @@ Stas &quot;uncle lag&quot; Akimushkin &lt;uncle.lag@gmail.com&gt;</property>
</child>
</widget>
+<widget class="GtkWindow" id="prefwin">
+ <property name="width_request">642</property>
+ <property name="height_request">372</property>
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Preferences</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">True</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <signal name="key_press_event" handler="on_prefwin_key_press_event" last_modification_time="Sat, 07 Nov 2009 15:47:40 GMT"/>
+
+ <child>
+ <widget class="GtkNotebook" id="notebook2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="show_tabs">True</property>
+ <property name="show_border">True</property>
+ <property name="tab_pos">GTK_POS_TOP</property>
+ <property name="scrollable">False</property>
+ <property name="enable_popup">False</property>
+
+ <child>
+ <widget class="GtkTable" id="table3">
+ <property name="border_width">3</property>
+ <property name="visible">True</property>
+ <property name="n_rows">6</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">0</property>
+ <property name="column_spacing">3</property>
+
+ <child>
+ <widget class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Output device</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Software ALSA resampling</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">SRC quality (libsamplerate)</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Replaygain mode</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Replaygain peak scale</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label15">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Release ALSA while stopped</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="pref_alsa_resampling">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <signal name="clicked" handler="on_pref_alsa_resampling_clicked" last_modification_time="Sat, 24 Oct 2009 16:50:34 GMT"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="pref_replaygain_scale">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <signal name="clicked" handler="on_pref_replaygain_scale_clicked" last_modification_time="Sat, 10 Oct 2009 18:52:10 GMT"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="pref_alsa_freewhenstopped">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <signal name="clicked" handler="on_pref_alsa_freewhenstopped_clicked" last_modification_time="Sat, 07 Nov 2009 11:37:02 GMT"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkComboBox" id="pref_soundcard">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ <signal name="changed" handler="on_pref_soundcard_changed" last_modification_time="Sat, 07 Nov 2009 14:12:28 GMT"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkComboBox" id="pref_src_quality">
+ <property name="visible">True</property>
+ <property name="items" translatable="yes">sinc_best_quality
+sinc_medium_quality
+sinc_fastest
+sinc_zero_order_hold
+linear</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ <signal name="changed" handler="on_pref_src_quality_changed" last_modification_time="Sat, 10 Oct 2009 19:02:36 GMT"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkComboBox" id="pref_replaygain_mode">
+ <property name="visible">True</property>
+ <property name="items" translatable="yes">Disable
+Track
+Album</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ <signal name="changed" handler="on_pref_replaygain_mode_changed" last_modification_time="Sat, 10 Oct 2009 19:22:23 GMT"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="Sound">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Sound</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkTable" id="table4">
+ <property name="border_width">3</property>
+ <property name="visible">True</property>
+ <property name="n_rows">1</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">0</property>
+ <property name="column_spacing">3</property>
+
+ <child>
+ <widget class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Close minimizes to tray</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="pref_close_send_to_tray">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <signal name="clicked" handler="on_pref_close_send_to_tray_clicked" last_modification_time="Sat, 10 Oct 2009 18:52:15 GMT"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">GUI</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkTable" id="table6">
+ <property name="border_width">3</property>
+ <property name="visible">True</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">0</property>
+ <property name="column_spacing">3</property>
+
+ <child>
+ <widget class="GtkLabel" id="label17">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Enable proxy server</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label18">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Proxy server address</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="pref_network_proxyaddress">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">•</property>
+ <property name="activates_default">False</property>
+ <signal name="changed" handler="on_pref_network_proxyaddress_changed" last_modification_time="Sat, 07 Nov 2009 15:13:12 GMT"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="pref_network_enableproxy">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <signal name="clicked" handler="on_pref_network_enableproxy_clicked" last_modification_time="Sat, 07 Nov 2009 15:12:34 GMT"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label19">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Proxy server port</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="pref_network_proxyport">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">•</property>
+ <property name="activates_default">False</property>
+ <signal name="changed" handler="on_pref_network_proxyport_changed" last_modification_time="Sat, 07 Nov 2009 15:13:18 GMT"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label20">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Proxy type</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkComboBox" id="pref_network_proxytype">
+ <property name="visible">True</property>
+ <property name="items" translatable="yes">HTTP
+HTTP_1_0
+SOCKS4
+SOCKS5
+SOCKS4A
+SOCKS5_HOSTNAME</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ <signal name="changed" handler="on_pref_network_proxytype_changed" last_modification_time="Sat, 07 Nov 2009 15:36:03 GMT"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label16">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Network</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHPaned" id="hpaned1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="width_request">280</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTreeView" id="pref_pluginlist">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">True</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ <signal name="cursor_changed" handler="on_pref_pluginlist_cursor_changed" last_modification_time="Sat, 10 Oct 2009 19:54:17 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="shrink">True</property>
+ <property name="resize">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkTable" id="table5">
+ <property name="width_request">400</property>
+ <property name="visible">True</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">0</property>
+ <property name="column_spacing">0</property>
+
+ <child>
+ <widget class="GtkLabel" id="label11">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Description</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label12">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Author(s)</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label13">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Email</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label14">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Website</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="pref_plugin_descr">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">False</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">●</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="pref_plugin_author">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">False</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">●</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="pref_plugin_email">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">False</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">●</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="pref_plugin_website">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">False</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">●</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="shrink">True</property>
+ <property name="resize">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Plugins</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+<widget class="GtkMenu" id="headermenu">
+
+ <child>
+ <widget class="GtkMenuItem" id="add_column">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Add column</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="add_column_menu">
+
+ <child>
+ <widget class="GtkMenuItem" id="artist">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Artist</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_artist_activate" last_modification_time="Mon, 19 Oct 2009 19:30:04 GMT"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="album">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Album</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_album_activate" last_modification_time="Mon, 19 Oct 2009 19:30:04 GMT"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="tracknum">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Track number</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_tracknum_activate" last_modification_time="Mon, 19 Oct 2009 19:30:04 GMT"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="duration">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Duration</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_duration_activate" last_modification_time="Mon, 19 Oct 2009 19:30:04 GMT"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="playing">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Playing status</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_playing_activate" last_modification_time="Mon, 19 Oct 2009 19:30:04 GMT"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="title">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Title</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_title_activate" last_modification_time="Mon, 19 Oct 2009 19:30:04 GMT"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator7">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="custom">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Custom</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_custom_activate" last_modification_time="Mon, 19 Oct 2009 19:30:04 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="remove_column">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Remove this column</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_remove_column_activate" last_modification_time="Mon, 19 Oct 2009 19:30:04 GMT"/>
+ </widget>
+ </child>
+</widget>
+
+<widget class="GtkWindow" id="addlocation">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Add Location</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">True</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <signal name="key_press_event" handler="on_addlocation_key_press_event" last_modification_time="Sat, 07 Nov 2009 17:25:40 GMT"/>
+
+ <child>
+ <widget class="GtkVBox" id="vbox7">
+ <property name="border_width">4</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkEntry" id="addlocation_entry">
+ <property name="width_request">346</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">•</property>
+ <property name="activates_default">False</property>
+ <signal name="activate" handler="on_addlocation_entry_activate" last_modification_time="Sat, 07 Nov 2009 18:13:04 GMT"/>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox8">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkLabel" id="label21">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="addlocation_ok">
+ <property name="width_request">83</property>
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">OK</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal name="clicked" handler="on_addlocation_ok_clicked" last_modification_time="Sat, 07 Nov 2009 18:12:00 GMT"/>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">2</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
</glade-interface>
diff --git a/deadbeef.h b/deadbeef.h
index ddff5aed..b97de2e8 100644
--- a/deadbeef.h
+++ b/deadbeef.h
@@ -29,7 +29,6 @@
#include <stdint.h>
#include <time.h>
-#include <stdio.h>
#ifdef __cplusplus
extern "C" {
@@ -54,7 +53,7 @@ extern "C" {
// DON'T release plugins without DB_PLUGIN_SET_API_VERSION
#define DB_API_VERSION_MAJOR 0
-#define DB_API_VERSION_MINOR 2
+#define DB_API_VERSION_MINOR 3
#define DB_PLUGIN_SET_API_VERSION\
.plugin.api_vmajor = DB_API_VERSION_MAJOR,\
@@ -71,7 +70,6 @@ typedef struct {
int tracknum; // used for stuff like sid, nsf, cue (will be ignored by most codecs)
int startsample; // start sample of track, or -1 for auto
int endsample; // end sample of track, or -1 for auto
- float duration; // in seconds
int shufflerating; // sort order for shuffle mode
float playtime; // total playtime
time_t started_timestamp; // result of calling time(NULL)
@@ -87,7 +85,8 @@ enum {
DB_PLUGIN_DECODER = 1,
DB_PLUGIN_OUTPUT = 2,
DB_PLUGIN_DSP = 3,
- DB_PLUGIN_MISC = 4
+ DB_PLUGIN_MISC = 4,
+ DB_PLUGIN_VFS = 5,
};
typedef struct {
@@ -100,6 +99,12 @@ typedef struct {
DB_playItem_t *song;
} DB_event_song_t;
+typedef struct DB_conf_item_s {
+ char *key;
+ char *value;
+ struct DB_conf_item_s *next;
+} DB_conf_item_t;
+
// event callback type
typedef int (*DB_callback_t)(DB_event_t *, uintptr_t data);
@@ -109,15 +114,56 @@ enum {
DB_EV_SONGCHANGED = 1, // triggers when song was just changed
DB_EV_SONGSTARTED = 2, // triggers when song started playing (for scrobblers and such)
DB_EV_SONGFINISHED = 3, // triggers when song finished playing (for scrobblers and such)
+ DB_EV_TRACKDELETED = 4, // triggers when track is to be deleted from playlist
+ DB_EV_CONFIGCHANGED = 5, // configuration option changed
DB_EV_MAX
};
+// preset columns, working using IDs
+enum {
+ DB_COLUMN_PLAYING = 1,
+ DB_COLUMN_ARTIST_ALBUM = 2,
+ DB_COLUMN_ARTIST = 3,
+ DB_COLUMN_ALBUM = 4,
+ DB_COLUMN_TITLE = 5,
+ DB_COLUMN_DURATION = 6,
+ DB_COLUMN_TRACK = 7
+};
+
+// message ids for communicating with player
+enum {
+ M_SONGFINISHED,
+ M_NEXTSONG,
+ M_PREVSONG,
+ M_PLAYSONG,
+ M_PLAYSONGNUM,
+ M_STOPSONG,
+ M_PAUSESONG,
+ M_PLAYRANDOM,
+ M_SONGCHANGED, // p1=from, p2=to
+ M_ADDDIR, // ctx = pointer to string, which must be freed by g_free
+ M_ADDFILES, // ctx = GSList pointer, must be freed with g_slist_free
+ M_ADDDIRS, // ctx = GSList pointer, must be freed with g_slist_free
+ M_OPENFILES, // ctx = GSList pointer, must be freed with g_slist_free
+ M_FMDRAGDROP, // ctx = char* ptr, must be freed with standard free, p1 is length of data, p2 is drop_y
+ M_TERMINATE, // must be sent to player thread to terminate
+ M_PLAYLISTREFRESH,
+ M_REINIT_SOUND,
+ M_TRACKCHANGED, // p1=tracknumber
+ M_CONFIGCHANGED, // no arguments
+};
+
// typecasting macros
#define DB_PLUGIN(x) ((DB_plugin_t *)(x))
#define DB_CALLBACK(x) ((DB_callback_t)(x))
#define DB_EVENT(x) ((DB_event_t *)(x))
#define DB_PLAYITEM(x) ((DB_playItem_t *)(x))
+// FILE object wrapper for vfs access
+typedef struct {
+ struct DB_vfs_s *vfs;
+} DB_FILE;
+
// forward decl for plugin struct
struct DB_plugin_s;
@@ -142,6 +188,7 @@ typedef struct {
float (*playback_get_pos) (void); // [0..100]
void (*playback_set_pos) (float pos); // [0..100]
int (*playback_get_samplerate) (void); // output samplerate
+ void (*playback_update_bitrate) (float bitrate);
// process control
const char *(*get_config_dir) (void);
void (*quit) (void);
@@ -162,9 +209,13 @@ typedef struct {
void (*pl_item_free) (DB_playItem_t *it);
void (*pl_item_copy) (DB_playItem_t *out, DB_playItem_t *in);
DB_playItem_t *(*pl_insert_item) (DB_playItem_t *after, DB_playItem_t *it);
+ int (*pl_get_idx_of) (DB_playItem_t *it);
// metainfo
void (*pl_add_meta) (DB_playItem_t *it, const char *key, const char *value);
const char *(*pl_find_meta) (DB_playItem_t *song, const char *meta);
+ void (*pl_delete_all_meta) (DB_playItem_t *it);
+ void (*pl_set_item_duration) (DB_playItem_t *it, float duration);
+ float (*pl_get_item_duration) (DB_playItem_t *it);
// cuesheet support
DB_playItem_t *(*pl_insert_cue_from_buffer) (DB_playItem_t *after, const char *fname, const uint8_t *buffer, int buffersize, struct DB_decoder_s *decoder, const char *ftype, int numsamples, int samplerate);
DB_playItem_t * (*pl_insert_cue) (DB_playItem_t *after, const char *filename, struct DB_decoder_s *decoder, const char *ftype, int numsamples, int samplerate);
@@ -174,10 +225,42 @@ typedef struct {
void (*volume_set_amp) (float amp);
float (*volume_get_amp) (void);
// junk reading
- int (*junk_read_id3v1) (DB_playItem_t *it, FILE *fp);
- int (*junk_read_id3v2) (DB_playItem_t *it, FILE *fp);
- int (*junk_read_ape) (DB_playItem_t *it, FILE *fp);
- int (*junk_get_leading_size) (FILE *fp);
+ int (*junk_read_id3v1) (DB_playItem_t *it, DB_FILE *fp);
+ int (*junk_read_id3v2) (DB_playItem_t *it, DB_FILE *fp);
+ int (*junk_read_ape) (DB_playItem_t *it, DB_FILE *fp);
+ int (*junk_get_leading_size) (DB_FILE *fp);
+ // vfs
+ DB_FILE* (*fopen) (const char *fname);
+ void (*fclose) (DB_FILE *f);
+ size_t (*fread) (void *ptr, size_t size, size_t nmemb, DB_FILE *stream);
+ int (*fseek) (DB_FILE *stream, int64_t offset, int whence);
+ int64_t (*ftell) (DB_FILE *stream);
+ void (*rewind) (DB_FILE *stream);
+ int64_t (*fgetlength) (DB_FILE *stream);
+ const char *(*fget_content_type) (DB_FILE *stream);
+ const char *(*fget_content_name) (DB_FILE *stream);
+ const char *(*fget_content_genre) (DB_FILE *stream);
+ void (*fstop) (DB_FILE *stream);
+ // message passing
+ int (*sendmessage) (uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2);
+ // configuration access
+ const char * (*conf_get_str) (const char *key, const char *def);
+ float (*conf_get_float) (const char *key, float def);
+ int (*conf_get_int) (const char *key, int def);
+ void (*conf_set_str) (const char *key, const char *val);
+ DB_conf_item_t * (*conf_find) (const char *group, DB_conf_item_t *prev);
+ // gui locking
+ void (*gui_lock) (void);
+ void (*gui_unlock) (void);
+ // exporting plugin conf options for gui
+ // all exported options are grouped by plugin, and will be available to user
+ // from gui
+// void (*export_plugin_option_string) (DB_plugin_t *plugin, const char *key);
+// void (*export_plugin_option_path) (DB_plugin_t *plugin, const char *key);
+// void (*export_plugin_option_check) (DB_plugin_t *plugin, const char *key);
+// void (*export_plugin_option_radio) (DB_plugin_t *plugin, const char *key);
+// void (*export_plugin_option_combo) (DB_plugin_t *plugin, const char *key);
+// void (*export_plugin_option_comboentry) (DB_plugin_t *plugin, const char *key);
} DB_functions_t;
// base plugin interface
@@ -211,6 +294,7 @@ typedef struct DB_plugin_s {
} DB_plugin_t;
typedef struct {
+ DB_FILE *file;
int bps;
int channels;
int samplerate;
@@ -302,6 +386,26 @@ typedef struct {
DB_plugin_t plugin;
} DB_misc_t;
+// vfs plugin
+// provides means for reading, seeking, etc
+// api is based on stdio
+typedef struct DB_vfs_s {
+ DB_plugin_t plugin;
+ DB_FILE* (*open) (const char *fname);
+ void (*close) (DB_FILE *f);
+ size_t (*read) (void *ptr, size_t size, size_t nmemb, DB_FILE *stream);
+ int (*seek) (DB_FILE *stream, int64_t offset, int whence);
+ int64_t (*tell) (DB_FILE *stream);
+ void (*rewind) (DB_FILE *stream);
+ int64_t (*getlength)(DB_FILE *stream);
+ void (*stop)(DB_FILE *stream);
+ const char * (*get_content_type) (DB_FILE *stream);
+ const char * (*get_content_name) (DB_FILE *stream);
+ const char * (*get_content_genre) (DB_FILE *stream);
+ const char **scheme_names; // NULL-terminated list of supported schemes, e.g. {"http", "ftp", NULL}
+ unsigned streaming : 1;
+} DB_vfs_t;
+
#ifdef __cplusplus
}
#endif
diff --git a/gtkplaylist.c b/gtkplaylist.c
index 7307462a..244741fe 100644
--- a/gtkplaylist.c
+++ b/gtkplaylist.c
@@ -28,6 +28,8 @@
#include <assert.h>
#include <unistd.h>
#include <ctype.h>
+#include <assert.h>
+#include <sys/time.h>
#include "gtkplaylist.h"
#include "callbacks.h"
#include "interface.h"
@@ -37,16 +39,22 @@
#include "codec.h"
#include "common.h"
#include "messagepump.h"
-#include "messages.h"
#include "streamer.h"
#include "search.h"
#include "progress.h"
#include "drawing.h"
#include "session.h"
+#include "deadbeef.h"
+#include "conf.h"
+#include "timeline.h"
//#define trace(...) { fprintf(stderr, __VA_ARGS__); }
#define trace(fmt,...)
+extern GtkWidget *mainwin;
+extern GtkStatusIcon *trayicon;
+extern gtkplaylist_t main_playlist;
+
// orange on dark color scheme
float colo_dark_orange[COLO_COUNT][3] = {
{ 0x7f/255.f, 0x7f/255.f, 0x7f/255.f }, // cursor
@@ -78,22 +86,102 @@ float colo_white_blue[COLO_COUNT][3] = {
{ 0x09/255.f, 0x22/255.f, 0x3a/255.f }, // dragdrop marker
};
+#define MIN_COLUMN_WIDTH 16
+
// current color scheme
float colo_current[COLO_COUNT][3];
// playlist row height
int rowheight = -1;
-const char *colnames[pl_ncolumns] = {
- "Playing",
- "Artist / Album",
- "Track №",
- "Title / Track Artist",
- "Duration"
-};
+// playlist scrolling during dragging
+static int playlist_scroll_mode = 0; // 0=select, 1=dragndrop
+static int playlist_scroll_pointer_y = -1;
+static int playlist_scroll_direction = 0;
+static int playlist_scroll_active = 0;
+static struct timeval tm_prevscroll;
+static float scroll_sleep_time = 0;
static uintptr_t play16_pixbuf;
static uintptr_t pause16_pixbuf;
+static uintptr_t buffering16_pixbuf;
+
+static GdkCursor* cursor_sz;
+static GdkCursor* cursor_drag;
+static int header_dragging = -1;
+static int header_sizing = -1;
+static int header_dragpt[2];
+
+#define COLHDR_ANIM_TIME 0.2f
+
+typedef struct {
+ int c1;
+ int c2;
+ int x1, x2;
+ int dx1, dx2;
+ // animated values
+ int ax1, ax2;
+ timeline_t *timeline;
+ int anim_active;
+ gtkplaylist_t *pl;
+} colhdr_animator_t;
+
+static colhdr_animator_t colhdr_anim;
+
+static gboolean
+redraw_header (void *data) {
+ colhdr_animator_t *anim = (colhdr_animator_t *)data;
+ gtkpl_header_draw (anim->pl);
+ gtkpl_expose_header (anim->pl, 0, 0, anim->pl->header->allocation.width, anim->pl->header->allocation.height);
+ return FALSE;
+}
+
+static int
+colhdr_anim_cb (float _progress, int _last, void *_ctx) {
+ colhdr_animator_t *anim = (colhdr_animator_t *)_ctx;
+ anim->ax1 = anim->x1 + (float)(anim->dx1 - anim->x1) * _progress;
+ anim->ax2 = anim->x2 + (float)(anim->dx2 - anim->x2) * _progress;
+// printf ("%f %d %d\n", _progress, anim->ax1, anim->ax2);
+ g_idle_add (redraw_header, anim);
+ if (_last) {
+ anim->anim_active = 0;
+ }
+ return 0;
+}
+
+static void
+colhdr_anim_swap (gtkplaylist_t *pl, int c1, int c2, int x1, int x2) {
+ // interrupt previous anim
+ if (!colhdr_anim.timeline) {
+ colhdr_anim.timeline = timeline_create ();
+ }
+ colhdr_anim.pl = pl;
+
+ colhdr_anim.c1 = c1;
+ colhdr_anim.c2 = c2;
+
+ // find c1 and c2 in column list and setup coords
+ // note: columns are already swapped, so their coords must be reversed,
+ // as if before swap
+ gtkpl_column_t *c;
+ int idx = 0;
+ int x = 0;
+ for (c = pl->columns; c; c = c->next, idx++) {
+ if (idx == c1) {
+ colhdr_anim.x1 = x1;
+ colhdr_anim.dx2 = x;
+ }
+ else if (idx == c2) {
+ colhdr_anim.x2 = x2;
+ colhdr_anim.dx1 = x;
+ }
+ x += c->width;
+ }
+ colhdr_anim.anim_active = 1;
+ timeline_stop (colhdr_anim.timeline, 0);
+ timeline_init (colhdr_anim.timeline, COLHDR_ANIM_TIME, 100, colhdr_anim_cb, &colhdr_anim);
+ timeline_start (colhdr_anim.timeline);
+}
// that must be called before gtk_init
void
@@ -102,11 +190,25 @@ gtkpl_init (void) {
//memcpy (colo_current, colo_dark_orange, sizeof (colo_current));
play16_pixbuf = draw_load_pixbuf ("play_16.png");
pause16_pixbuf = draw_load_pixbuf ("pause_16.png");
+ buffering16_pixbuf = draw_load_pixbuf ("buffering_16.png");
rowheight = draw_get_font_size () + 12;
memcpy (colo_current, colo_white_blue, sizeof (colo_current));
}
void
+gtkpl_free (gtkplaylist_t *pl) {
+ if (colhdr_anim.timeline) {
+ timeline_free (colhdr_anim.timeline, 1);
+ colhdr_anim.timeline = 0;
+ }
+ while (pl->columns) {
+ gtkpl_column_t *next = pl->columns->next;
+ gtkpl_column_free (pl->columns);
+ pl->columns = next;
+ }
+}
+
+void
theme_set_cairo_source_rgb (cairo_t *cr, int col) {
cairo_set_source_rgb (cr, colo_current[col][0], colo_current[col][1], colo_current[col][2]);
}
@@ -154,8 +256,9 @@ gtkpl_setup_hscrollbar (gtkplaylist_t *ps) {
int w = playlist->allocation.width;
int size = 0;
int i;
- for (i = 0; i < pl_ncolumns; i++) {
- size += ps->colwidths[i];
+ gtkpl_column_t *c;
+ for (c = ps->columns; c; c = c->next) {
+ size += c->width;
}
if (w >= size) {
size = 0;
@@ -240,10 +343,6 @@ gtkpl_draw_pl_row (gtkplaylist_t *ps, int row, playItem_t *it) {
}
int width, height;
draw_get_canvas_size ((uintptr_t)ps->backbuf, &width, &height);
- if (it == playlist_current_ptr && ps->colwidths[0] > 0 && !p_isstopped ()) {
- uintptr_t pixbuf = p_ispaused () ? pause16_pixbuf : play16_pixbuf;
- draw_pixbuf ((uintptr_t)ps->backbuf, pixbuf, ps->colwidths[0]/2-8-ps->hscrollpos, (row - ps->scrollpos) * rowheight + rowheight/2 - 8, 0, 0, 16, 16);
- }
if (it && ((it->selected && ps->multisel) || (row == ps->row && !ps->multisel))) {
if (row % 2) {
theme_set_bg_color (COLO_PLAYLIST_SEL_EVEN);
@@ -263,16 +362,9 @@ gtkpl_draw_pl_row (gtkplaylist_t *ps, int row, playItem_t *it) {
theme_set_fg_color (COLO_PLAYLIST_TEXT);
}
// draw as columns
- char dur[10] = "-:--";
- if (it) {
- if (it->duration >= 0) {
- int min = (int)it->duration/60;
- int sec = (int)(it->duration-min*60);
- snprintf (dur, 10, "%d:%02d", min, sec);
- }
- }
+ char dur[50];
+ pl_format_title (it, dur, sizeof (dur), "%l");
- char artistalbum[1024];
const char *artist = pl_find_meta (it, "artist");
if (!artist) {
artist = "?";
@@ -289,7 +381,9 @@ gtkpl_draw_pl_row (gtkplaylist_t *ps, int row, playItem_t *it) {
if (!title) {
title = "?";
}
- snprintf (artistalbum, 1024, "%s - %s", artist, album);
+ char artistalbum[1024];
+ pl_format_title (it, artistalbum, sizeof (artistalbum), "%a - %b");
+#if 0
const char *columns[pl_ncolumns] = {
"",
artistalbum,
@@ -297,20 +391,64 @@ gtkpl_draw_pl_row (gtkplaylist_t *ps, int row, playItem_t *it) {
title,
dur
};
+#endif
int x = -ps->hscrollpos;
- for (int i = 0; i < pl_ncolumns; i++) {
- if (i > 0) {
-
- int dotpos;
- int cidx = ((row-ps->scrollpos) * pl_ncolumns + i) * 3;
- if (i == 2) {
- draw_text_with_colors (x+5, row * rowheight - ps->scrollpos * rowheight + rowheight/2 - draw_get_font_size ()/2 - 2, ps->colwidths[i]-10, 1, columns[i]);
+ gtkpl_column_t *c;
+ for (c = ps->columns; c; c = c->next) {
+ if (it == playlist_current_ptr && c->id == DB_COLUMN_PLAYING/* && !p_isstopped ()*/) {
+ int paused = p_ispaused ();
+ int buffering = !streamer_ok_to_read (-1);
+ uintptr_t pixbuf;
+ if (paused) {
+ pixbuf = pause16_pixbuf;
+ }
+ else if (!buffering) {
+ pixbuf = play16_pixbuf;
}
else {
- draw_text_with_colors (x + 5, row * rowheight - ps->scrollpos * rowheight + rowheight/2 - draw_get_font_size ()/2 - 2, ps->colwidths[i]-10, 0, columns[i]);
+ pixbuf = buffering16_pixbuf;
+ }
+ draw_pixbuf ((uintptr_t)ps->backbuf, pixbuf, x + c->width/2 - 8 - ps->hscrollpos, (row - ps->scrollpos) * rowheight + rowheight/2 - 8, 0, 0, 16, 16);
+ }
+ else {
+ char fmt_text[1024];
+ const char *text = NULL;
+ if (c->id != -1) {
+ switch (c->id) {
+ case DB_COLUMN_ARTIST_ALBUM:
+ text = artistalbum;
+ break;
+ case DB_COLUMN_ARTIST:
+ text = artist;
+ break;
+ case DB_COLUMN_ALBUM:
+ text = album;
+ break;
+ case DB_COLUMN_TITLE:
+ text = title;
+ break;
+ case DB_COLUMN_DURATION:
+ text = dur;
+ break;
+ case DB_COLUMN_TRACK:
+ text = track;
+ break;
+ }
+ }
+ else if (c->format) {
+ pl_format_title (it, fmt_text, sizeof (fmt_text), c->format);
+ text = fmt_text;
+ }
+ if (text) {
+ if (c->align_right) {
+ draw_text_with_colors (x+5, row * rowheight - ps->scrollpos * rowheight + rowheight/2 - draw_get_font_size ()/2 - 2, c->width-10, 1, text);
+ }
+ else {
+ draw_text_with_colors (x + 5, row * rowheight - ps->scrollpos * rowheight + rowheight/2 - draw_get_font_size ()/2 - 2, c->width-10, 0, text);
+ }
}
}
- x += ps->colwidths[i];
+ x += c->width;
}
}
@@ -459,15 +597,23 @@ gtkpl_mouse1_pressed (gtkplaylist_t *ps, int state, int ex, int ey, double time)
&& fabs(ps->lastpos[1] - ey) < 3) {
// doubleclick - play this item
if (ps->row != -1) {
- gtkplaylist_t main_playlist;
playItem_t *it = gtkpl_get_for_idx (ps, ps->row);
it->selected = 1;
int r = pl_get_idx_of (it);
+ int prev = main_playlist.row;
+ if (prev != r) {
+ main_playlist.row = r;
+ if (prev != -1) {
+ gtkpl_redraw_pl_row (&main_playlist, prev, pl_get_for_idx (prev));
+ }
+ if (r != -1) {
+ gtkpl_redraw_pl_row (&main_playlist, r, it);
+ }
+ }
messagepump_push (M_PLAYSONGNUM, 0, r, 0);
return;
}
-
// prevent next click to trigger doubleclick
ps->clicktime = time-1;
}
@@ -558,6 +704,8 @@ gtkpl_mouse1_released (gtkplaylist_t *ps, int state, int ex, int ey, double time
gtkpl_select_single (ps, y);
}
else if (areaselect) {
+ playlist_scroll_direction = 0;
+ playlist_scroll_pointer_y = -1;
areaselect = 0;
}
}
@@ -599,6 +747,51 @@ gtkpl_draw_areasel (GtkWidget *widget, int x, int y) {
}
#endif
+static gboolean
+gtkpl_scroll_playlist_cb (gpointer data) {
+ gtkplaylist_t *ps = (gtkplaylist_t *)data;
+ playlist_scroll_active = 1;
+ struct timeval tm;
+ gettimeofday (&tm, NULL);
+ if (tm.tv_sec - tm_prevscroll.tv_sec + (tm.tv_usec - tm_prevscroll.tv_usec) / 1000000.0 < scroll_sleep_time) {
+ return TRUE;
+ }
+ memcpy (&tm_prevscroll, &tm, sizeof (tm));
+ if (playlist_scroll_pointer_y == -1) {
+ playlist_scroll_active = 0;
+ return FALSE;
+ }
+ if (playlist_scroll_direction == 0) {
+ playlist_scroll_active = 0;
+ return FALSE;
+ }
+ int sc = ps->scrollpos + playlist_scroll_direction;
+ if (sc < 0) {
+ playlist_scroll_active = 0;
+ return FALSE;
+ }
+ if (sc >= *ps->pcount) {
+ playlist_scroll_active = 0;
+ return FALSE;
+ }
+ GDK_THREADS_ENTER ();
+ gtk_range_set_value (GTK_RANGE (ps->scrollbar), sc);
+ if (playlist_scroll_mode == 0) {
+ GdkEventMotion ev;
+ ev.y = playlist_scroll_pointer_y;
+ gtkpl_mousemove (ps, &ev);
+ }
+ else if (playlist_scroll_mode == 1) {
+ gtkpl_track_dragdrop (ps, playlist_scroll_pointer_y);
+ }
+ GDK_THREADS_LEAVE ();
+ scroll_sleep_time -= 0.1;
+ if (scroll_sleep_time < 0.05) {
+ scroll_sleep_time = 0.05;
+ }
+ return TRUE;
+}
+
void
gtkpl_mousemove (gtkplaylist_t *ps, GdkEventMotion *event) {
if (dragwait) {
@@ -617,22 +810,51 @@ gtkpl_mousemove (gtkplaylist_t *ps, GdkEventMotion *event) {
else if (areaselect) {
GtkWidget *widget = ps->playlist;
int y = event->y/rowheight + ps->scrollpos;
- if (y != shift_sel_anchor) {
+ //if (y != shift_sel_anchor)
+ {
int start = min (y, shift_sel_anchor);
int end = max (y, shift_sel_anchor);
int idx=0;
for (playItem_t *it = playlist_head[ps->iterator]; it; it = it->next[ps->iterator], idx++) {
if (idx >= start && idx <= end) {
- it->selected = 1;
- gtkpl_redraw_pl_row (ps, idx, it);
+ if (!it->selected) {
+ it->selected = 1;
+ gtkpl_redraw_pl_row (ps, idx, it);
+ }
}
- else if (it->selected)
- {
+ else if (it->selected) {
it->selected = 0;
gtkpl_redraw_pl_row (ps, idx, it);
}
}
}
+
+ if (event->y < 10) {
+ playlist_scroll_mode = 0;
+ playlist_scroll_pointer_y = event->y;
+ playlist_scroll_direction = -1;
+ // start scrolling up
+ if (!playlist_scroll_active) {
+ scroll_sleep_time = 0.2;
+ gettimeofday (&tm_prevscroll, NULL);
+ g_idle_add (gtkpl_scroll_playlist_cb, ps);
+ }
+ }
+ else if (event->y > ps->playlist->allocation.height-10) {
+ playlist_scroll_mode = 0;
+ playlist_scroll_pointer_y = event->y;
+ playlist_scroll_direction = 1;
+ // start scrolling up
+ if (!playlist_scroll_active) {
+ scroll_sleep_time = 0.2;
+ gettimeofday (&tm_prevscroll, NULL);
+ g_idle_add (gtkpl_scroll_playlist_cb, ps);
+ }
+ }
+ else {
+ playlist_scroll_direction = 0;
+ playlist_scroll_pointer_y = -1;
+ }
// debug only
// gtkpl_draw_areasel (widget, event->x, event->y);
}
@@ -724,7 +946,7 @@ void
gtkpl_songchanged (gtkplaylist_t *ps, int from, int to) {
if (!dragwait && to != -1) {
GtkWidget *widget = ps->playlist;
- if (session_get_scroll_follows_playback ()) {
+ if (conf_get_int ("playlist.scroll.followplayback", 0)) {
if (to < ps->scrollpos || to >= ps->scrollpos + ps->nvisiblefullrows) {
gtk_range_set_value (GTK_RANGE (ps->scrollbar), to - ps->nvisiblerows/2);
}
@@ -921,6 +1143,32 @@ gtkpl_track_dragdrop (gtkplaylist_t *ps, int y) {
draw_rect (0, drag_motion_y * rowheight-3, 3, 7, 1);
draw_rect (widget->allocation.width-3, drag_motion_y * rowheight-3, 3, 7, 1);
draw_end ();
+ if (y < 10) {
+ playlist_scroll_pointer_y = y;
+ playlist_scroll_direction = -1;
+ playlist_scroll_mode = 1;
+ // start scrolling up
+ if (!playlist_scroll_active) {
+ scroll_sleep_time = 0.2;
+ gettimeofday (&tm_prevscroll, NULL);
+ g_idle_add (gtkpl_scroll_playlist_cb, ps);
+ }
+ }
+ else if (y > ps->playlist->allocation.height-10) {
+ playlist_scroll_mode = 1;
+ playlist_scroll_pointer_y = y;
+ playlist_scroll_direction = 1;
+ // start scrolling up
+ if (!playlist_scroll_active) {
+ scroll_sleep_time = 0.2;
+ gettimeofday (&tm_prevscroll, NULL);
+ g_idle_add (gtkpl_scroll_playlist_cb, ps);
+ }
+ }
+ else {
+ playlist_scroll_direction = 0;
+ playlist_scroll_pointer_y = -1;
+ }
}
void
@@ -937,7 +1185,6 @@ gtkpl_handle_drag_drop (gtkplaylist_t *ps, int drop_y, uint32_t *d, int length)
int idx = 0;
playItem_t *next = NULL;
for (playItem_t *it = playlist_head[ps->iterator]; it && processed < length; it = next, idx++) {
- // printf ("idx: %d\n", d[i]);
next = it->next[ps->iterator];
if (idx == d[processed]) {
if (it->prev[ps->iterator]) {
@@ -998,6 +1245,8 @@ on_playlist_drag_end (GtkWidget *widget,
// invalidate entire cache - slow, but rare
gtkpl_draw_playlist (ps, 0, 0, widget->allocation.width, widget->allocation.height);
gtkpl_expose (ps, 0, 0, widget->allocation.width, widget->allocation.height);
+ playlist_scroll_direction = 0;
+ playlist_scroll_pointer_y = -1;
}
void
@@ -1088,9 +1337,9 @@ gtkpl_add_fm_dropped_files (gtkplaylist_t *ps, char *ptr, int length, int drop_y
//strncpy (fname, p, pe - p);
//fname[pe - p] = 0;
int abort = 0;
- playItem_t *inserted = pl_insert_dir (after, fname + 7, &abort, gtkpl_add_file_info_cb, NULL);
+ playItem_t *inserted = pl_insert_dir (after, fname, &abort, gtkpl_add_file_info_cb, NULL);
if (!inserted && !abort) {
- inserted = pl_insert_file (after, fname + 7, &abort, gtkpl_add_file_info_cb, NULL);
+ inserted = pl_insert_file (after, fname, &abort, gtkpl_add_file_info_cb, NULL);
}
if (inserted) {
after = inserted;
@@ -1128,36 +1377,74 @@ gtkpl_header_draw (gtkplaylist_t *ps) {
const char *detail = "toolbar";
// fill background
- gdk_draw_rectangle (ps->backbuf_header, widget->style->bg_gc[0], TRUE, 0, 0, widget->allocation.width, widget->allocation.height);
- for (int i = 0; i < pl_ncolumns; i++) {
- if (x >= widget->allocation.width) {
- break;
- }
- w = ps->colwidths[i];
- if (w > 0) {
- gtk_paint_box (widget->style, ps->backbuf_header, GTK_STATE_NORMAL, GTK_SHADOW_OUT, NULL, NULL, detail, x, 0, w - 2, h);
- gtk_paint_vline (widget->style, ps->backbuf_header, GTK_STATE_NORMAL, NULL, NULL, NULL, 0, h, x+w - 2);
- }
- x += w;
- }
- if (x < widget->allocation.width) {
- gtk_paint_box (widget->style, ps->backbuf_header, GTK_STATE_INSENSITIVE, GTK_SHADOW_OUT, NULL, NULL, detail, x, 0, widget->allocation.width-x, h);
- }
+ gtk_paint_box (widget->style, ps->backbuf_header, GTK_STATE_NORMAL, GTK_SHADOW_OUT, NULL, NULL, detail, 0, 0, widget->allocation.width, widget->allocation.height);
draw_begin ((uintptr_t)ps->backbuf_header);
x = -ps->hscrollpos;
- for (int i = 0; i < pl_ncolumns; i++) {
- if (x >= widget->allocation.width) {
- break;
+ gtkpl_column_t *c;
+ int need_draw_moving = 0;
+ int idx = 0;
+ for (c = ps->columns; c; c = c->next, idx++) {
+ w = c->width;
+ int xx = x;
+ if (colhdr_anim.anim_active) {
+ if (idx == colhdr_anim.c2) {
+ xx = colhdr_anim.ax1;
+ }
+ else if (idx == colhdr_anim.c1) {
+ xx = colhdr_anim.ax2;
+ }
}
- w = ps->colwidths[i];
- if (w > 0) {
- GdkColor *gdkfg = &widget->style->fg[0];
- float fg[3] = {(float)gdkfg->red/0xffff, (float)gdkfg->green/0xffff, (float)gdkfg->blue/0xffff};
- draw_set_fg_color (fg);
- draw_text (x + 5, h/2-draw_get_font_size()/2, ps->colwidths[i]-10, 0, colnames[i]);
+ if (header_dragging < 0 || idx != header_dragging) {
+ if (xx >= widget->allocation.width) {
+ continue;
+ }
+ if (w > 0) {
+ gtk_paint_vline (widget->style, ps->backbuf_header, GTK_STATE_NORMAL, NULL, NULL, NULL, 0, h, xx+w - 2);
+ GdkColor *gdkfg = &widget->style->fg[0];
+ float fg[3] = {(float)gdkfg->red/0xffff, (float)gdkfg->green/0xffff, (float)gdkfg->blue/0xffff};
+ draw_set_fg_color (fg);
+ draw_text (xx + 5, h/2-draw_get_font_size()/2, c->width-10, 0, c->title);
+ }
+ }
+ else {
+ need_draw_moving = 1;
}
x += w;
}
+ if (need_draw_moving) {
+ x = -ps->hscrollpos;
+ idx = 0;
+ for (c = ps->columns; c; c = c->next, idx++) {
+ w = c->width;
+ if (idx == header_dragging) {
+ if (colhdr_anim.anim_active) {
+ if (idx == colhdr_anim.c2) {
+ x = colhdr_anim.ax1;
+ }
+ else if (idx == colhdr_anim.c1) {
+ x = colhdr_anim.ax2;
+ }
+ }
+ // draw empty slot
+ if (x < widget->allocation.width) {
+ gtk_paint_box (widget->style, ps->backbuf_header, GTK_STATE_ACTIVE, GTK_SHADOW_ETCHED_IN, NULL, NULL, "button", x, 0, w, h);
+ }
+ x = c->movepos;
+ if (x >= widget->allocation.width) {
+ break;
+ }
+ if (w > 0) {
+ gtk_paint_box (widget->style, ps->backbuf_header, GTK_STATE_SELECTED, GTK_SHADOW_OUT, NULL, NULL, "button", x, 0, w, h);
+ GdkColor *gdkfg = &widget->style->fg[0];
+ float fg[3] = {(float)gdkfg->red/0xffff, (float)gdkfg->green/0xffff, (float)gdkfg->blue/0xffff};
+ draw_set_fg_color (fg);
+ draw_text (x + 5, h/2-draw_get_font_size()/2, c->width-10, 0, c->title);
+ }
+ break;
+ }
+ x += w;
+ }
+ }
draw_end ();
}
@@ -1189,12 +1476,6 @@ on_header_configure_event (GtkWidget *widget,
}
-GdkCursor* cursor_sz;
-GdkCursor* cursor_drag;
-int header_dragging = -1;
-int header_sizing = -1;
-int header_dragpt[2];
-
void
on_header_realize (GtkWidget *widget,
gpointer user_data)
@@ -1217,34 +1498,103 @@ on_header_motion_notify_event (GtkWidget *widget,
GTKPL_PROLOGUE;
if (header_dragging >= 0) {
gdk_window_set_cursor (widget->window, cursor_drag);
+ gtkpl_column_t *c;
+ int i;
+ for (i = 0, c = ps->columns; i < header_dragging && c; c = c->next, i++);
+ c->movepos = event->x - header_dragpt[0];
+
+ // find closest column to the left
+ int inspos = -1;
+ gtkpl_column_t *cc;
+ int x = 0;
+ int idx = 0;
+ int x1 = -1, x2 = -1;
+ for (cc = ps->columns; cc; cc = cc->next, idx++) {
+ if (x < c->movepos && x + c->width > c->movepos) {
+ inspos = idx;
+ x1 = x;
+ }
+ else if (idx == header_dragging) {
+ x2 = x;
+ }
+ x += cc->width;
+ }
+ if (inspos >= 0 && inspos != header_dragging) {
+ int c1 = inspos;
+ int c2 = header_dragging;
+ // remove c from list
+ if (c == ps->columns) {
+ ps->columns = c->next;
+ }
+ else {
+ for (cc = ps->columns; cc; cc = cc->next) {
+ if (cc->next == c) {
+ cc->next = c->next;
+ }
+ }
+ }
+ c->next = NULL;
+ // reinsert c at position inspos update header_dragging to new idx
+ header_dragging = inspos;
+ if (inspos == 0) {
+ c->next = ps->columns;
+ ps->columns = c;
+ }
+ else {
+ idx = 0;
+ gtkpl_column_t *prev = NULL;
+ for (cc = ps->columns; cc; cc = cc->next, idx++, prev = cc) {
+ if (idx+1 == inspos) {
+ gtkpl_column_t *next = cc->next;
+ cc->next = c;
+ c->next = next;
+ break;
+ }
+ }
+ }
+// colhdr_anim_swap (ps, c1, c2, x1, x2);
+ // force redraw of everything
+// gtkpl_setup_hscrollbar (ps);
+ gtkpl_draw_playlist (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height);
+ gtkpl_expose (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height);
+ gtkpl_column_update_config (ps, c, i);
+ }
+ else {
+ // only redraw that if not animating
+ gtkpl_header_draw (ps);
+ gtkpl_expose_header (ps, 0, 0, ps->header->allocation.width, ps->header->allocation.height);
+ }
}
else if (header_sizing >= 0) {
// limit event rate
if (event->time - last_header_motion_ev < 50 || prev_header_x == event->x) {
return FALSE;
}
- //printf ("%f\n", event->time - last_header_motion_ev);
last_header_motion_ev = event->time;
prev_header_x = event->x;
gdk_window_set_cursor (widget->window, cursor_sz);
// get column start pos
int x = -ps->hscrollpos;
- for (int i = 0; i < header_sizing; i++) {
- int w = ps->colwidths[i];
- x += w;
+ int i = 0;
+ gtkpl_column_t *c;
+ for (c = ps->columns; c && i < header_sizing; c = c->next, i++) {
+ x += c->width;
}
- int newx = event->x > x + 40 ? event->x : x + 40;
- ps->colwidths[header_sizing] = newx - x;
+
+ int newx = event->x > x + MIN_COLUMN_WIDTH ? event->x : x + MIN_COLUMN_WIDTH;
+ c->width = newx - x;
gtkpl_setup_hscrollbar (ps);
gtkpl_header_draw (ps);
gtkpl_expose_header (ps, 0, 0, ps->header->allocation.width, ps->header->allocation.height);
gtkpl_draw_playlist (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height);
gtkpl_expose (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height);
+ gtkpl_column_update_config (ps, c, i);
}
else {
int x = -ps->hscrollpos;
- for (int i = 0; i < pl_ncolumns; i++) {
- int w = ps->colwidths[i];
+ gtkpl_column_t *c;
+ for (c = ps->columns; c; c = c->next) {
+ int w = c->width;
if (w > 0) { // ignore collapsed columns (hack for search window)
if (event->x >= x + w - 2 && event->x <= x + w) {
gdk_window_set_cursor (widget->window, cursor_sz);
@@ -1276,15 +1626,20 @@ on_header_button_press_event (GtkWidget *widget,
header_dragpt[0] = event->x;
header_dragpt[1] = event->y;
int x = -ps->hscrollpos;
- for (int i = 0; i < pl_ncolumns; i++) {
- int w = ps->colwidths[i];
+ int i = 0;
+ gtkpl_column_t *c;
+ for (c = ps->columns; c; c = c->next, i++) {
+ int w = c->width;
if (event->x >= x + w - 2 && event->x <= x + w) {
header_sizing = i;
header_dragging = -1;
break;
}
- else {
+ else if (event->x > x + 2 && event->x < x + w - 2) {
+ header_dragpt[0] = event->x - x;
header_dragging = i;
+ header_sizing = -1;
+ break;
}
x += w;
}
@@ -1300,20 +1655,36 @@ on_header_button_release_event (GtkWidget *widget,
gpointer user_data)
{
GTKPL_PROLOGUE;
- header_dragging = -1;
- header_sizing = -1;
- int x = 0;
- for (int i = 0; i < pl_ncolumns; i++) {
- int w = ps->colwidths[i];
- if (event->x >= x + w - 2 && event->x <= x + w) {
- gdk_window_set_cursor (widget->window, cursor_sz);
- break;
+ if (event->button == 1) {
+ header_sizing = -1;
+ int x = 0;
+ gtkpl_column_t *c;
+ for (c = ps->columns; c; c = c->next) {
+ int w = c->width;
+ if (event->x >= x + w - 2 && event->x <= x + w) {
+ gdk_window_set_cursor (widget->window, cursor_sz);
+ break;
+ }
+ else {
+ gdk_window_set_cursor (widget->window, NULL);
+ }
+ x += w;
}
- else {
- gdk_window_set_cursor (widget->window, NULL);
+ if (header_dragging >= 0) {
+ header_dragging = -1;
+ gtkpl_setup_hscrollbar (ps);
+ gtkpl_header_draw (ps);
+ gtkpl_expose_header (ps, 0, 0, ps->header->allocation.width, ps->header->allocation.height);
+ gtkpl_draw_playlist (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height);
+ gtkpl_expose (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height);
+ gtkpl_column_rewrite_config (ps);
}
- x += w;
}
+// NOTE: disabled for 0.3.0 release
+// else if (event->button == 3) {
+// GtkWidget *menu = create_headermenu ();
+// gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, widget, 0, gtk_get_current_event_time());
+// }
return FALSE;
}
@@ -1418,3 +1789,249 @@ playlist_refresh (void) {
gtkpl_expose (ps, 0, 0, widget->allocation.width, widget->allocation.height);
search_refresh ();
}
+
+gtkpl_column_t *
+gtkpl_column_alloc (const char *title, int width, int id, const char *format, int align_right) {
+ gtkpl_column_t *c = malloc (sizeof (gtkpl_column_t));
+ memset (c, 0, sizeof (gtkpl_column_t));
+ c->title = strdup (title);
+ c->id = id;
+ c->format = format ? strdup (format) : NULL;
+ c->width = width;
+ c->align_right = align_right;
+ return c;
+}
+
+void
+gtkpl_column_append (gtkplaylist_t *pl, gtkpl_column_t *c) {
+ int idx = 0;
+ if (pl->columns) {
+ idx++;
+ gtkpl_column_t *tail = pl->columns;
+ while (tail->next) {
+ tail = tail->next;
+ idx++;
+ }
+ tail->next = c;
+ }
+ else {
+ pl->columns = c;
+ }
+ gtkpl_column_update_config (pl, c, idx);
+}
+
+void
+gtkpl_column_free (gtkpl_column_t *c) {
+ if (c->title) {
+ free (c->title);
+ }
+ if (c->format) {
+ free (c->format);
+ }
+ free (c);
+}
+
+void
+gtkpl_column_remove (gtkplaylist_t *pl, gtkpl_column_t *c) {
+ if (pl->columns == c) {
+ pl->columns = pl->columns->next;
+ gtkpl_column_free (c);
+ return;
+ }
+ gtkpl_column_t *cc = pl->columns;
+ while (cc) {
+ if (cc->next == c) {
+ cc->next = cc->next->next;
+ gtkpl_column_free (c);
+ return;
+ }
+ cc = cc->next;
+ }
+ assert (cc && "gtkpl: attempted to remove column that is not in list");
+}
+
+void
+gtkpl_append_column_from_textdef (gtkplaylist_t *pl, const uint8_t *def) {
+ // syntax: "title" "format" id width alignright
+ char title[128];
+ char format[128];
+ int id;
+ int width;
+ int align_right;
+ // title
+ if (*def != '"') {
+ return;
+ }
+ def++;
+ if (*def == 0) {
+ return;
+ }
+ const uint8_t *e = def;
+ e++;
+ while (*e && *e != '"') {
+ e++;
+ }
+ if (*e == 0) {
+ return;
+ }
+ memcpy (title, def, e-def);
+ title[e-def] = 0;
+ // skip whitespace
+ def = e;
+ def++;
+ while (*def && *def <= ' ') {
+ def++;
+ }
+ if (*def == 0) {
+ return;
+ }
+ // format
+ if (*def != '"') {
+ return;
+ }
+ def++;
+ if (*def == 0) {
+ return;
+ }
+ e = def;
+ while (*e && *e != '"') {
+ e++;
+ }
+ if (*e == 0) {
+ return;
+ }
+ memcpy (format, def, e-def);
+ format[e-def] = 0;
+ // skip whitespace
+ def = e;
+ def++;
+ while (*def && *def <= ' ') {
+ def++;
+ }
+ if (*def == 0) {
+ return;
+ }
+ // id
+ e = def;
+ while (*e && (isdigit (*e) || *e == '-')) {
+ e++;
+ }
+ if (*e == 0) {
+ return;
+ }
+ {
+ char s[e-def+1];
+ memcpy (s, def, e-def);
+ s[e-def] = 0;
+ id = atoi (s);
+ }
+ // skip whitespace
+ def = e;
+ def++;
+ while (*def && *def <= ' ') {
+ def++;
+ }
+ if (*def == 0) {
+ return;
+ }
+ // width
+ e = def;
+ while (*e && isdigit (*e)) {
+ e++;
+ }
+ if (*e == 0) {
+ return;
+ }
+ {
+ char s[e-def+1];
+ memcpy (s, def, e-def);
+ s[e-def] = 0;
+ width = atoi (s);
+ }
+ // skip whitespace
+ def = e;
+ def++;
+ while (*def && *def <= ' ') {
+ def++;
+ }
+ if (*def == 0) {
+ return;
+ }
+ // align_right
+ e = def;
+ while (*e && isdigit (*e)) {
+ e++;
+ }
+ {
+ char s[e-def+1];
+ memcpy (s, def, e-def);
+ s[e-def] = 0;
+ align_right = atoi (s);
+ }
+ gtkpl_column_append (pl, gtkpl_column_alloc (title, width, id, format[0] ? format : NULL, align_right));
+}
+
+void
+gtkpl_column_update_config (gtkplaylist_t *pl, gtkpl_column_t *c, int idx) {
+ char key[128];
+ char value[128];
+ snprintf (key, sizeof (key), "%s.column.%d", pl->title, idx);
+ snprintf (value, sizeof (value), "\"%s\" \"%s\" %d %d %d", c->title, c->format ? c->format : "", c->id, c->width, c->align_right);
+ conf_set_str (key, value);
+}
+
+void
+gtkpl_column_rewrite_config (gtkplaylist_t *pl) {
+ char key[128];
+ char value[128];
+ snprintf (key, sizeof (key), "%s.column.", pl->title);
+ conf_remove_items (key);
+
+ gtkpl_column_t *c;
+ int i = 0;
+ for (c = pl->columns; c; c = c->next, i++) {
+ snprintf (key, sizeof (key), "%s.column.%d", pl->title, i);
+ snprintf (value, sizeof (value), "\"%s\" \"%s\" %d %d %d", c->title, c->format ? c->format : "", c->id, c->width, c->align_right);
+ conf_set_str (key, value);
+ }
+}
+
+void
+set_tray_tooltip (const char *text) {
+#if (GTK_MINOR_VERSION < 16)
+ gtk_status_icon_set_tooltip (trayicon, text);
+#else
+ gtk_status_icon_set_tooltip_text (trayicon, text);
+#endif
+}
+
+void
+gtkpl_current_track_changed (playItem_t *it) {
+ char str[600];
+ char dname[512];
+ pl_format_item_display_name (it, dname, 512);
+ snprintf (str, 600, "DeaDBeeF - %s", dname);
+ gtk_window_set_title (GTK_WINDOW (mainwin), str);
+ set_tray_tooltip (str);
+}
+
+void
+gtkpl_songchanged_wrapper (int from, int to) {
+ GDK_THREADS_ENTER ();
+ // update window title
+ if (from >= 0 || to >= 0) {
+ if (to >= 0) {
+ playItem_t *it = pl_get_for_idx (to);
+ if (it) { // it might have been deleted after event was sent
+ gtkpl_current_track_changed (it);
+ }
+ }
+ else {
+ gtk_window_set_title (GTK_WINDOW (mainwin), "DeaDBeeF");
+ set_tray_tooltip ("DeaDBeeF");
+ }
+ }
+ // update playlist view
+ gtkpl_songchanged (&main_playlist, from, to);
+ GDK_THREADS_LEAVE ();
+}
diff --git a/gtkplaylist.h b/gtkplaylist.h
index 9504df1d..2e827839 100644
--- a/gtkplaylist.h
+++ b/gtkplaylist.h
@@ -46,8 +46,19 @@ enum {
COLO_COUNT
};
-#define pl_ncolumns 5
-#define pl_colname_max 100
+//#define pl_ncolumns 5
+//#define pl_colname_max 100
+
+typedef struct gtkpl_column_s {
+ char *title;
+ int id; // id is faster than format, set to -1 to use format
+ char *format;
+ int width;
+ int movepos; // valid only while `moving' is 1
+ struct gtkpl_column_s *next;
+ unsigned align_right : 1;
+// unsigned moving : 1;
+} gtkpl_column_t;
// structure of this kind must be set as user data for playlist, header and scrollbar widgets
// pointer to this structure must be passed too all functions that
@@ -60,6 +71,7 @@ typedef struct {
GtkWidget *hscrollbar;
GdkPixmap *backbuf;
GdkPixmap *backbuf_header;
+ const char *title; // unique id, used for config writing, etc
// parameters
playItem_t **pcurr; // pointer to current item
int *pcount; // pointer to count of items in list
@@ -73,17 +85,24 @@ typedef struct {
double clicktime; // for doubleclick detection
int nvisiblerows;
int nvisiblefullrows;
- int *colwidths;//[pl_ncolumns]; // current column widths
+// int *colwidths;//[pl_ncolumns]; // current column widths
+// int ncolumns;
+ gtkpl_column_t *columns;
} gtkplaylist_t;
#define GTKPL_PROLOGUE \
gtkplaylist_t *ps = (gtkplaylist_t *)gtk_object_get_data (GTK_OBJECT (widget), "ps"); assert (ps);
+extern int rowheight;
+
// that must be called before gtk_init
void
gtkpl_init (void);
void
+gtkpl_free (gtkplaylist_t *pl);
+
+void
gtkpl_redraw_pl_row (gtkplaylist_t *ps, int row, playItem_t *it);
void
@@ -205,4 +224,38 @@ theme_set_cairo_source_rgb (cairo_t *cr, int col);
void
playlist_refresh (void);
+// column utilities
+gtkpl_column_t *
+gtkpl_column_alloc (const char *title, int width, int id, const char *format, int align_right);
+
+void
+gtkpl_column_append (gtkplaylist_t *pl, gtkpl_column_t *c);
+
+void
+gtkpl_column_remove (gtkplaylist_t *pl, gtkpl_column_t *c);
+
+void
+gtkpl_column_free (gtkpl_column_t *c);
+
+void
+gtkpl_append_column_from_textdef (gtkplaylist_t *pl, const uint8_t *def);
+
+void
+gtkpl_column_update_config (gtkplaylist_t *pl, gtkpl_column_t *c, int idx);
+
+void
+gtkpl_column_rewrite_config (gtkplaylist_t *pl);
+
+void
+gtkpl_expose_header (gtkplaylist_t *ps, int x, int y, int w, int h);
+
+void
+set_tray_tooltip (const char *text);
+
+void
+gtkpl_songchanged_wrapper (int from, int to);
+
+void
+gtkpl_current_track_changed (playItem_t *it);
+
#endif // __GTKPLAYLIST_H
diff --git a/gtksession.c b/gtksession.c
index 45a320f0..e24706b6 100644
--- a/gtksession.c
+++ b/gtksession.c
@@ -22,20 +22,23 @@
# include <config.h>
#endif
#include "session.h"
+#include "conf.h"
void
session_capture_window_attrs (uintptr_t window) {
GtkWindow *wnd = GTK_WINDOW (window);
- extern int session_win_attrs[5];
- gtk_window_get_position (wnd, &session_win_attrs[0], &session_win_attrs[1]);
- gtk_window_get_size (wnd, &session_win_attrs[2], &session_win_attrs[3]);
- //printf ("attrs: %d %d %d %d\n", session_win_attrs[0], session_win_attrs[1], session_win_attrs[2], session_win_attrs[3]);
+ int win_attrs[4];
+ gtk_window_get_position (wnd, &win_attrs[0], &win_attrs[1]);
+ gtk_window_get_size (wnd, &win_attrs[2], &win_attrs[3]);
+ conf_set_int ("mainwin.geometry.x", win_attrs[0]);
+ conf_set_int ("mainwin.geometry.y", win_attrs[1]);
+ conf_set_int ("mainwin.geometry.w", win_attrs[2]);
+ conf_set_int ("mainwin.geometry.h", win_attrs[3]);
}
void
session_restore_window_attrs (uintptr_t window) {
GtkWindow *wnd = GTK_WINDOW (window);
- extern int session_win_attrs[5];
- gtk_window_move (wnd, session_win_attrs[0], session_win_attrs[1]);
- gtk_window_resize (wnd, session_win_attrs[2], session_win_attrs[3]);
+ gtk_window_move (wnd, conf_get_int ("mainwin.geometry.x", 40), conf_get_int ("mainwin.geometry.y", 40));
+ gtk_window_resize (wnd, conf_get_int ("mainwin.geometry.w", 500), conf_get_int ("mainwin.geometry.h", 300));
}
diff --git a/interface.c b/interface.c
index a83d8321..88acb8d0 100644
--- a/interface.c
+++ b/interface.c
@@ -35,26 +35,31 @@ create_mainwin (void)
GtkWidget *menuitem1;
GtkWidget *menuitem1_menu;
GtkWidget *open;
- GtkWidget *image46;
+ GtkWidget *image120;
GtkWidget *separator2;
GtkWidget *add_files;
- GtkWidget *image47;
+ GtkWidget *image121;
GtkWidget *add_folders;
- GtkWidget *image48;
+ GtkWidget *image122;
+ GtkWidget *add_audio_cd;
+ GtkWidget *image123;
+ GtkWidget *add_location1;
GtkWidget *separatormenuitem1;
GtkWidget *quit;
- GtkWidget *image49;
+ GtkWidget *image124;
GtkWidget *edit1;
GtkWidget *edit1_menu;
GtkWidget *clear1;
- GtkWidget *image50;
+ GtkWidget *image125;
GtkWidget *select_all1;
GtkWidget *selection1;
GtkWidget *selection1_menu;
GtkWidget *remove1;
- GtkWidget *image51;
+ GtkWidget *image126;
GtkWidget *crop1;
GtkWidget *find1;
+ GtkWidget *separator5;
+ GtkWidget *preferences;
GtkWidget *playlist1;
GtkWidget *playlist1_menu;
GtkWidget *playlist_load;
@@ -77,7 +82,7 @@ create_mainwin (void)
GtkWidget *menuitem4_menu;
GtkWidget *about1;
GtkWidget *help1;
- GtkWidget *image52;
+ GtkWidget *image127;
GtkWidget *hbox2;
GtkWidget *hbox3;
GtkWidget *stopbtn;
@@ -133,9 +138,9 @@ create_mainwin (void)
GDK_O, (GdkModifierType) GDK_CONTROL_MASK,
GTK_ACCEL_VISIBLE);
- image46 = gtk_image_new_from_stock ("gtk-open", GTK_ICON_SIZE_MENU);
- gtk_widget_show (image46);
- gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (open), image46);
+ image120 = gtk_image_new_from_stock ("gtk-open", GTK_ICON_SIZE_MENU);
+ gtk_widget_show (image120);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (open), image120);
separator2 = gtk_separator_menu_item_new ();
gtk_widget_show (separator2);
@@ -146,17 +151,29 @@ create_mainwin (void)
gtk_widget_show (add_files);
gtk_container_add (GTK_CONTAINER (menuitem1_menu), add_files);
- image47 = gtk_image_new_from_stock ("gtk-add", GTK_ICON_SIZE_MENU);
- gtk_widget_show (image47);
- gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (add_files), image47);
+ image121 = gtk_image_new_from_stock ("gtk-add", GTK_ICON_SIZE_MENU);
+ gtk_widget_show (image121);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (add_files), image121);
add_folders = gtk_image_menu_item_new_with_mnemonic ("Add folder(s)");
gtk_widget_show (add_folders);
gtk_container_add (GTK_CONTAINER (menuitem1_menu), add_folders);
- image48 = gtk_image_new_from_stock ("gtk-add", GTK_ICON_SIZE_MENU);
- gtk_widget_show (image48);
- gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (add_folders), image48);
+ image122 = gtk_image_new_from_stock ("gtk-add", GTK_ICON_SIZE_MENU);
+ gtk_widget_show (image122);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (add_folders), image122);
+
+ add_audio_cd = gtk_image_menu_item_new_with_mnemonic ("Add Audio CD");
+ gtk_widget_show (add_audio_cd);
+ gtk_container_add (GTK_CONTAINER (menuitem1_menu), add_audio_cd);
+
+ image123 = gtk_image_new_from_stock ("gtk-add", GTK_ICON_SIZE_MENU);
+ gtk_widget_show (image123);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (add_audio_cd), image123);
+
+ add_location1 = gtk_menu_item_new_with_mnemonic ("Add location");
+ gtk_widget_show (add_location1);
+ gtk_container_add (GTK_CONTAINER (menuitem1_menu), add_location1);
separatormenuitem1 = gtk_separator_menu_item_new ();
gtk_widget_show (separatormenuitem1);
@@ -170,9 +187,9 @@ create_mainwin (void)
GDK_Q, (GdkModifierType) GDK_CONTROL_MASK,
GTK_ACCEL_VISIBLE);
- image49 = gtk_image_new_from_stock ("gtk-quit", GTK_ICON_SIZE_MENU);
- gtk_widget_show (image49);
- gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (quit), image49);
+ image124 = gtk_image_new_from_stock ("gtk-quit", GTK_ICON_SIZE_MENU);
+ gtk_widget_show (image124);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (quit), image124);
edit1 = gtk_menu_item_new_with_mnemonic ("Edit");
gtk_widget_show (edit1);
@@ -185,9 +202,9 @@ create_mainwin (void)
gtk_widget_show (clear1);
gtk_container_add (GTK_CONTAINER (edit1_menu), clear1);
- image50 = gtk_image_new_from_stock ("gtk-clear", GTK_ICON_SIZE_MENU);
- gtk_widget_show (image50);
- gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (clear1), image50);
+ image125 = gtk_image_new_from_stock ("gtk-clear", GTK_ICON_SIZE_MENU);
+ gtk_widget_show (image125);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (clear1), image125);
select_all1 = gtk_menu_item_new_with_mnemonic ("Select all");
gtk_widget_show (select_all1);
@@ -210,9 +227,9 @@ create_mainwin (void)
GDK_Delete, (GdkModifierType) 0,
GTK_ACCEL_VISIBLE);
- image51 = gtk_image_new_from_stock ("gtk-remove", GTK_ICON_SIZE_MENU);
- gtk_widget_show (image51);
- gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (remove1), image51);
+ image126 = gtk_image_new_from_stock ("gtk-remove", GTK_ICON_SIZE_MENU);
+ gtk_widget_show (image126);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (remove1), image126);
crop1 = gtk_menu_item_new_with_mnemonic ("Crop");
gtk_widget_show (crop1);
@@ -225,6 +242,15 @@ create_mainwin (void)
GDK_F, (GdkModifierType) GDK_CONTROL_MASK,
GTK_ACCEL_VISIBLE);
+ separator5 = gtk_separator_menu_item_new ();
+ gtk_widget_show (separator5);
+ gtk_container_add (GTK_CONTAINER (edit1_menu), separator5);
+ gtk_widget_set_sensitive (separator5, FALSE);
+
+ preferences = gtk_menu_item_new_with_mnemonic ("Preferences");
+ gtk_widget_show (preferences);
+ gtk_container_add (GTK_CONTAINER (edit1_menu), preferences);
+
playlist1 = gtk_menu_item_new_with_mnemonic ("Playlist");
gtk_widget_show (playlist1);
gtk_container_add (GTK_CONTAINER (menubar1), playlist1);
@@ -314,9 +340,9 @@ create_mainwin (void)
gtk_widget_show (help1);
gtk_container_add (GTK_CONTAINER (menuitem4_menu), help1);
- image52 = gtk_image_new_from_stock ("gtk-help", GTK_ICON_SIZE_MENU);
- gtk_widget_show (image52);
- gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (help1), image52);
+ image127 = gtk_image_new_from_stock ("gtk-help", GTK_ICON_SIZE_MENU);
+ gtk_widget_show (image127);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (help1), image127);
hbox2 = gtk_hbox_new (FALSE, 0);
gtk_widget_show (hbox2);
@@ -441,7 +467,7 @@ create_mainwin (void)
gtk_widget_show (header);
gtk_box_pack_start (GTK_BOX (vbox3), header, FALSE, TRUE, 0);
gtk_widget_set_size_request (header, -1, 24);
- gtk_widget_set_events (header, GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
+ gtk_widget_set_events (header, GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
playlist = gtk_drawing_area_new ();
gtk_widget_show (playlist);
@@ -482,6 +508,12 @@ create_mainwin (void)
g_signal_connect ((gpointer) add_folders, "activate",
G_CALLBACK (on_add_folders_activate),
NULL);
+ g_signal_connect ((gpointer) add_audio_cd, "activate",
+ G_CALLBACK (on_add_audio_cd_activate),
+ NULL);
+ g_signal_connect ((gpointer) add_location1, "activate",
+ G_CALLBACK (on_add_location_activate),
+ NULL);
g_signal_connect ((gpointer) quit, "activate",
G_CALLBACK (on_quit_activate),
NULL);
@@ -500,6 +532,9 @@ create_mainwin (void)
g_signal_connect ((gpointer) find1, "activate",
G_CALLBACK (on_find_activate),
NULL);
+ g_signal_connect ((gpointer) preferences, "activate",
+ G_CALLBACK (on_preferences_activate),
+ NULL);
g_signal_connect ((gpointer) playlist_load, "activate",
G_CALLBACK (on_playlist_load_activate),
NULL);
@@ -667,26 +702,31 @@ create_mainwin (void)
GLADE_HOOKUP_OBJECT (mainwin, menuitem1, "menuitem1");
GLADE_HOOKUP_OBJECT (mainwin, menuitem1_menu, "menuitem1_menu");
GLADE_HOOKUP_OBJECT (mainwin, open, "open");
- GLADE_HOOKUP_OBJECT (mainwin, image46, "image46");
+ GLADE_HOOKUP_OBJECT (mainwin, image120, "image120");
GLADE_HOOKUP_OBJECT (mainwin, separator2, "separator2");
GLADE_HOOKUP_OBJECT (mainwin, add_files, "add_files");
- GLADE_HOOKUP_OBJECT (mainwin, image47, "image47");
+ GLADE_HOOKUP_OBJECT (mainwin, image121, "image121");
GLADE_HOOKUP_OBJECT (mainwin, add_folders, "add_folders");
- GLADE_HOOKUP_OBJECT (mainwin, image48, "image48");
+ GLADE_HOOKUP_OBJECT (mainwin, image122, "image122");
+ GLADE_HOOKUP_OBJECT (mainwin, add_audio_cd, "add_audio_cd");
+ GLADE_HOOKUP_OBJECT (mainwin, image123, "image123");
+ GLADE_HOOKUP_OBJECT (mainwin, add_location1, "add_location1");
GLADE_HOOKUP_OBJECT (mainwin, separatormenuitem1, "separatormenuitem1");
GLADE_HOOKUP_OBJECT (mainwin, quit, "quit");
- GLADE_HOOKUP_OBJECT (mainwin, image49, "image49");
+ GLADE_HOOKUP_OBJECT (mainwin, image124, "image124");
GLADE_HOOKUP_OBJECT (mainwin, edit1, "edit1");
GLADE_HOOKUP_OBJECT (mainwin, edit1_menu, "edit1_menu");
GLADE_HOOKUP_OBJECT (mainwin, clear1, "clear1");
- GLADE_HOOKUP_OBJECT (mainwin, image50, "image50");
+ GLADE_HOOKUP_OBJECT (mainwin, image125, "image125");
GLADE_HOOKUP_OBJECT (mainwin, select_all1, "select_all1");
GLADE_HOOKUP_OBJECT (mainwin, selection1, "selection1");
GLADE_HOOKUP_OBJECT (mainwin, selection1_menu, "selection1_menu");
GLADE_HOOKUP_OBJECT (mainwin, remove1, "remove1");
- GLADE_HOOKUP_OBJECT (mainwin, image51, "image51");
+ GLADE_HOOKUP_OBJECT (mainwin, image126, "image126");
GLADE_HOOKUP_OBJECT (mainwin, crop1, "crop1");
GLADE_HOOKUP_OBJECT (mainwin, find1, "find1");
+ GLADE_HOOKUP_OBJECT (mainwin, separator5, "separator5");
+ GLADE_HOOKUP_OBJECT (mainwin, preferences, "preferences");
GLADE_HOOKUP_OBJECT (mainwin, playlist1, "playlist1");
GLADE_HOOKUP_OBJECT (mainwin, playlist1_menu, "playlist1_menu");
GLADE_HOOKUP_OBJECT (mainwin, playlist_load, "playlist_load");
@@ -707,7 +747,7 @@ create_mainwin (void)
GLADE_HOOKUP_OBJECT (mainwin, menuitem4_menu, "menuitem4_menu");
GLADE_HOOKUP_OBJECT (mainwin, about1, "about1");
GLADE_HOOKUP_OBJECT (mainwin, help1, "help1");
- GLADE_HOOKUP_OBJECT (mainwin, image52, "image52");
+ GLADE_HOOKUP_OBJECT (mainwin, image127, "image127");
GLADE_HOOKUP_OBJECT (mainwin, hbox2, "hbox2");
GLADE_HOOKUP_OBJECT (mainwin, hbox3, "hbox3");
GLADE_HOOKUP_OBJECT (mainwin, stopbtn, "stopbtn");
@@ -739,48 +779,6 @@ create_mainwin (void)
}
GtkWidget*
-create_aboutdialog (void)
-{
- GtkWidget *aboutdialog;
- const gchar *authors[] = {
- "Core developer:",
- "Alexey Yakovenko",
- "<waker@users.sourceforge.net>",
- " ",
- "Hotkeys plugin:",
- "Viktor Semykin",
- "<thesame.ml@gmail.com>",
- " ",
- "Desktop file and Debian packages:",
- "Alexey A. Smirnov",
- "<alexey.smirnov@gmx.com>",
- NULL
- };
- const gchar *artists[] = {
- "Button artwork:",
- "Stas \"uncle lag\" Akimushkin <uncle.lag@gmail.com>",
- NULL
- };
-
- aboutdialog = gtk_about_dialog_new ();
- gtk_container_set_border_width (GTK_CONTAINER (aboutdialog), 4);
- gtk_window_set_destroy_with_parent (GTK_WINDOW (aboutdialog), TRUE);
- gtk_about_dialog_set_version (GTK_ABOUT_DIALOG (aboutdialog), VERSION);
- gtk_about_dialog_set_name (GTK_ABOUT_DIALOG (aboutdialog), "DeaDBeeF");
- gtk_about_dialog_set_copyright (GTK_ABOUT_DIALOG (aboutdialog), "Copyright \302\251 2009 Alexey Yakovenko");
- gtk_about_dialog_set_license (GTK_ABOUT_DIALOG (aboutdialog), "DeaDBeeF - ultimate music player for GNU/Linux systems with X11\nCopyright (C) 2009 Alexey Yakovenko\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n\n\nNote that other included libraries have separate licensing terms.\n\n\nDUMB - Dynamic Universal Music Bibliotheque, Version 0.9.3\nCopyright (C) 2001-2005 Ben Davis, Robert J Ohannessian and Julien Cugniere.\n\n\nGame_Music_Emu Version 0.5.2\nCopyright (C) 2003-2006 Shay Green.\n\n\nFunctions to compute MD5 message digest of files or memory blocks\nWritten by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995.\nModified by Gray Watson <http://256.com/gray/>, 1997.\n\n\nlibsidplay2 - commodore 64 SID emulation library\nCopyright (C) Simon White and other authors.\n\n\nffap - monkey's audio decoder based on apedec code from ffmpeg\nCopyright (c) 2007 Benjamin Zores <ben@geexbox.org>");
- gtk_about_dialog_set_website (GTK_ABOUT_DIALOG (aboutdialog), "http://deadbeef.sf.net");
- gtk_about_dialog_set_website_label (GTK_ABOUT_DIALOG (aboutdialog), "website");
- gtk_about_dialog_set_authors (GTK_ABOUT_DIALOG (aboutdialog), authors);
- gtk_about_dialog_set_artists (GTK_ABOUT_DIALOG (aboutdialog), artists);
-
- /* Store pointers to all widgets, for use by lookup_widget(). */
- GLADE_HOOKUP_OBJECT_NO_REF (aboutdialog, aboutdialog, "aboutdialog");
-
- return aboutdialog;
-}
-
-GtkWidget*
create_searchwin (void)
{
GtkWidget *searchwin;
@@ -1062,6 +1060,7 @@ create_addprogress (void)
GtkWidget *vbox6;
GtkWidget *progresstitle;
GtkWidget *hbox7;
+ GtkWidget *label22;
GtkWidget *button1;
addprogress = gtk_window_new (GTK_WINDOW_TOPLEVEL);
@@ -1087,6 +1086,10 @@ create_addprogress (void)
gtk_widget_show (hbox7);
gtk_box_pack_start (GTK_BOX (vbox6), hbox7, FALSE, TRUE, 2);
+ label22 = gtk_label_new ("");
+ gtk_widget_show (label22);
+ gtk_box_pack_start (GTK_BOX (hbox7), label22, TRUE, FALSE, 0);
+
button1 = gtk_button_new_with_mnemonic ("Abort");
gtk_widget_show (button1);
gtk_box_pack_start (GTK_BOX (hbox7), button1, FALSE, FALSE, 0);
@@ -1105,6 +1108,7 @@ create_addprogress (void)
GLADE_HOOKUP_OBJECT (addprogress, vbox6, "vbox6");
GLADE_HOOKUP_OBJECT (addprogress, progresstitle, "progresstitle");
GLADE_HOOKUP_OBJECT (addprogress, hbox7, "hbox7");
+ GLADE_HOOKUP_OBJECT (addprogress, label22, "label22");
GLADE_HOOKUP_OBJECT (addprogress, button1, "button1");
return addprogress;
@@ -1119,6 +1123,7 @@ create_helpwindow (void)
helpwindow = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_set_size_request (helpwindow, 600, 400);
+ gtk_widget_set_events (helpwindow, GDK_KEY_PRESS_MASK);
gtk_window_set_title (GTK_WINDOW (helpwindow), "Help");
gtk_window_set_modal (GTK_WINDOW (helpwindow), TRUE);
gtk_window_set_destroy_with_parent (GTK_WINDOW (helpwindow), TRUE);
@@ -1136,6 +1141,10 @@ create_helpwindow (void)
gtk_text_view_set_left_margin (GTK_TEXT_VIEW (helptext), 10);
gtk_text_view_set_right_margin (GTK_TEXT_VIEW (helptext), 10);
+ g_signal_connect ((gpointer) helpwindow, "key_press_event",
+ G_CALLBACK (on_helpwindow_key_press_event),
+ NULL);
+
/* Store pointers to all widgets, for use by lookup_widget(). */
GLADE_HOOKUP_OBJECT_NO_REF (helpwindow, helpwindow, "helpwindow");
GLADE_HOOKUP_OBJECT (helpwindow, scrolledwindow1, "scrolledwindow1");
@@ -1144,3 +1153,585 @@ create_helpwindow (void)
return helpwindow;
}
+GtkWidget*
+create_prefwin (void)
+{
+ GtkWidget *prefwin;
+ GtkWidget *notebook2;
+ GtkWidget *table3;
+ GtkWidget *label4;
+ GtkWidget *label5;
+ GtkWidget *label6;
+ GtkWidget *label8;
+ GtkWidget *label9;
+ GtkWidget *label15;
+ GtkWidget *pref_alsa_resampling;
+ GtkWidget *pref_replaygain_scale;
+ GtkWidget *pref_alsa_freewhenstopped;
+ GtkWidget *pref_soundcard;
+ GtkWidget *pref_src_quality;
+ GtkWidget *pref_replaygain_mode;
+ GtkWidget *Sound;
+ GtkWidget *table4;
+ GtkWidget *label7;
+ GtkWidget *pref_close_send_to_tray;
+ GtkWidget *label2;
+ GtkWidget *table6;
+ GtkWidget *label17;
+ GtkWidget *label18;
+ GtkWidget *pref_network_proxyaddress;
+ GtkWidget *pref_network_enableproxy;
+ GtkWidget *label19;
+ GtkWidget *pref_network_proxyport;
+ GtkWidget *label20;
+ GtkWidget *pref_network_proxytype;
+ GtkWidget *label16;
+ GtkWidget *hpaned1;
+ GtkWidget *scrolledwindow2;
+ GtkWidget *pref_pluginlist;
+ GtkWidget *table5;
+ GtkWidget *label11;
+ GtkWidget *label12;
+ GtkWidget *label13;
+ GtkWidget *label14;
+ GtkWidget *pref_plugin_descr;
+ GtkWidget *pref_plugin_author;
+ GtkWidget *pref_plugin_email;
+ GtkWidget *pref_plugin_website;
+ GtkWidget *label3;
+
+ prefwin = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_widget_set_size_request (prefwin, 642, 372);
+ gtk_window_set_title (GTK_WINDOW (prefwin), "Preferences");
+ gtk_window_set_modal (GTK_WINDOW (prefwin), TRUE);
+
+ notebook2 = gtk_notebook_new ();
+ gtk_widget_show (notebook2);
+ gtk_container_add (GTK_CONTAINER (prefwin), notebook2);
+
+ table3 = gtk_table_new (6, 2, FALSE);
+ gtk_widget_show (table3);
+ gtk_container_add (GTK_CONTAINER (notebook2), table3);
+ gtk_container_set_border_width (GTK_CONTAINER (table3), 3);
+ gtk_table_set_col_spacings (GTK_TABLE (table3), 3);
+
+ label4 = gtk_label_new ("Output device");
+ gtk_widget_show (label4);
+ gtk_table_attach (GTK_TABLE (table3), label4, 0, 1, 0, 1,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment (GTK_MISC (label4), 0, 0.5);
+
+ label5 = gtk_label_new ("Software ALSA resampling");
+ gtk_widget_show (label5);
+ gtk_table_attach (GTK_TABLE (table3), label5, 0, 1, 1, 2,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment (GTK_MISC (label5), 0, 0.5);
+
+ label6 = gtk_label_new ("SRC quality (libsamplerate)");
+ gtk_widget_show (label6);
+ gtk_table_attach (GTK_TABLE (table3), label6, 0, 1, 2, 3,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment (GTK_MISC (label6), 0, 0.5);
+
+ label8 = gtk_label_new ("Replaygain mode");
+ gtk_widget_show (label8);
+ gtk_table_attach (GTK_TABLE (table3), label8, 0, 1, 3, 4,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment (GTK_MISC (label8), 0, 0.5);
+
+ label9 = gtk_label_new ("Replaygain peak scale");
+ gtk_widget_show (label9);
+ gtk_table_attach (GTK_TABLE (table3), label9, 0, 1, 4, 5,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment (GTK_MISC (label9), 0, 0.5);
+
+ label15 = gtk_label_new ("Release ALSA while stopped");
+ gtk_widget_show (label15);
+ gtk_table_attach (GTK_TABLE (table3), label15, 0, 1, 5, 6,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment (GTK_MISC (label15), 0, 0.5);
+
+ pref_alsa_resampling = gtk_check_button_new_with_mnemonic ("");
+ gtk_widget_show (pref_alsa_resampling);
+ gtk_table_attach (GTK_TABLE (table3), pref_alsa_resampling, 1, 2, 1, 2,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+
+ pref_replaygain_scale = gtk_check_button_new_with_mnemonic ("");
+ gtk_widget_show (pref_replaygain_scale);
+ gtk_table_attach (GTK_TABLE (table3), pref_replaygain_scale, 1, 2, 4, 5,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+
+ pref_alsa_freewhenstopped = gtk_check_button_new_with_mnemonic ("");
+ gtk_widget_show (pref_alsa_freewhenstopped);
+ gtk_table_attach (GTK_TABLE (table3), pref_alsa_freewhenstopped, 1, 2, 5, 6,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+
+ pref_soundcard = gtk_combo_box_new_text ();
+ gtk_widget_show (pref_soundcard);
+ gtk_table_attach (GTK_TABLE (table3), pref_soundcard, 1, 2, 0, 1,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (GTK_FILL), 0, 0);
+
+ pref_src_quality = gtk_combo_box_new_text ();
+ gtk_widget_show (pref_src_quality);
+ gtk_table_attach (GTK_TABLE (table3), pref_src_quality, 1, 2, 2, 3,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (GTK_FILL), 0, 0);
+ gtk_combo_box_append_text (GTK_COMBO_BOX (pref_src_quality), "sinc_best_quality");
+ gtk_combo_box_append_text (GTK_COMBO_BOX (pref_src_quality), "sinc_medium_quality");
+ gtk_combo_box_append_text (GTK_COMBO_BOX (pref_src_quality), "sinc_fastest");
+ gtk_combo_box_append_text (GTK_COMBO_BOX (pref_src_quality), "sinc_zero_order_hold");
+ gtk_combo_box_append_text (GTK_COMBO_BOX (pref_src_quality), "linear");
+
+ pref_replaygain_mode = gtk_combo_box_new_text ();
+ gtk_widget_show (pref_replaygain_mode);
+ gtk_table_attach (GTK_TABLE (table3), pref_replaygain_mode, 1, 2, 3, 4,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (GTK_FILL), 0, 0);
+ gtk_combo_box_append_text (GTK_COMBO_BOX (pref_replaygain_mode), "Disable");
+ gtk_combo_box_append_text (GTK_COMBO_BOX (pref_replaygain_mode), "Track");
+ gtk_combo_box_append_text (GTK_COMBO_BOX (pref_replaygain_mode), "Album");
+
+ Sound = gtk_label_new ("Sound");
+ gtk_widget_show (Sound);
+ gtk_notebook_set_tab_label (GTK_NOTEBOOK (notebook2), gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook2), 0), Sound);
+
+ table4 = gtk_table_new (1, 2, FALSE);
+ gtk_widget_show (table4);
+ gtk_container_add (GTK_CONTAINER (notebook2), table4);
+ gtk_container_set_border_width (GTK_CONTAINER (table4), 3);
+ gtk_table_set_col_spacings (GTK_TABLE (table4), 3);
+
+ label7 = gtk_label_new ("Close minimizes to tray");
+ gtk_widget_show (label7);
+ gtk_table_attach (GTK_TABLE (table4), label7, 0, 1, 0, 1,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment (GTK_MISC (label7), 0, 0.5);
+
+ pref_close_send_to_tray = gtk_check_button_new_with_mnemonic ("");
+ gtk_widget_show (pref_close_send_to_tray);
+ gtk_table_attach (GTK_TABLE (table4), pref_close_send_to_tray, 1, 2, 0, 1,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+
+ label2 = gtk_label_new ("GUI");
+ gtk_widget_show (label2);
+ gtk_notebook_set_tab_label (GTK_NOTEBOOK (notebook2), gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook2), 1), label2);
+
+ table6 = gtk_table_new (4, 2, FALSE);
+ gtk_widget_show (table6);
+ gtk_container_add (GTK_CONTAINER (notebook2), table6);
+ gtk_container_set_border_width (GTK_CONTAINER (table6), 3);
+ gtk_table_set_col_spacings (GTK_TABLE (table6), 3);
+
+ label17 = gtk_label_new ("Enable proxy server");
+ gtk_widget_show (label17);
+ gtk_table_attach (GTK_TABLE (table6), label17, 0, 1, 0, 1,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment (GTK_MISC (label17), 0, 0.5);
+
+ label18 = gtk_label_new ("Proxy server address");
+ gtk_widget_show (label18);
+ gtk_table_attach (GTK_TABLE (table6), label18, 0, 1, 1, 2,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment (GTK_MISC (label18), 0, 0.5);
+
+ pref_network_proxyaddress = gtk_entry_new ();
+ gtk_widget_show (pref_network_proxyaddress);
+ gtk_table_attach (GTK_TABLE (table6), pref_network_proxyaddress, 1, 2, 1, 2,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_entry_set_invisible_char (GTK_ENTRY (pref_network_proxyaddress), 8226);
+
+ pref_network_enableproxy = gtk_check_button_new_with_mnemonic ("");
+ gtk_widget_show (pref_network_enableproxy);
+ gtk_table_attach (GTK_TABLE (table6), pref_network_enableproxy, 1, 2, 0, 1,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+
+ label19 = gtk_label_new ("Proxy server port");
+ gtk_widget_show (label19);
+ gtk_table_attach (GTK_TABLE (table6), label19, 0, 1, 2, 3,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment (GTK_MISC (label19), 0, 0.5);
+
+ pref_network_proxyport = gtk_entry_new ();
+ gtk_widget_show (pref_network_proxyport);
+ gtk_table_attach (GTK_TABLE (table6), pref_network_proxyport, 1, 2, 2, 3,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_entry_set_invisible_char (GTK_ENTRY (pref_network_proxyport), 8226);
+
+ label20 = gtk_label_new ("Proxy type");
+ gtk_widget_show (label20);
+ gtk_table_attach (GTK_TABLE (table6), label20, 0, 1, 3, 4,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment (GTK_MISC (label20), 0, 0.5);
+
+ pref_network_proxytype = gtk_combo_box_new_text ();
+ gtk_widget_show (pref_network_proxytype);
+ gtk_table_attach (GTK_TABLE (table6), pref_network_proxytype, 1, 2, 3, 4,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (GTK_FILL), 0, 0);
+ gtk_combo_box_append_text (GTK_COMBO_BOX (pref_network_proxytype), "HTTP");
+ gtk_combo_box_append_text (GTK_COMBO_BOX (pref_network_proxytype), "HTTP_1_0");
+ gtk_combo_box_append_text (GTK_COMBO_BOX (pref_network_proxytype), "SOCKS4");
+ gtk_combo_box_append_text (GTK_COMBO_BOX (pref_network_proxytype), "SOCKS5");
+ gtk_combo_box_append_text (GTK_COMBO_BOX (pref_network_proxytype), "SOCKS4A");
+ gtk_combo_box_append_text (GTK_COMBO_BOX (pref_network_proxytype), "SOCKS5_HOSTNAME");
+
+ label16 = gtk_label_new ("Network");
+ gtk_widget_show (label16);
+ gtk_notebook_set_tab_label (GTK_NOTEBOOK (notebook2), gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook2), 2), label16);
+
+ hpaned1 = gtk_hpaned_new ();
+ gtk_widget_show (hpaned1);
+ gtk_container_add (GTK_CONTAINER (notebook2), hpaned1);
+
+ scrolledwindow2 = gtk_scrolled_window_new (NULL, NULL);
+ gtk_widget_show (scrolledwindow2);
+ gtk_paned_pack1 (GTK_PANED (hpaned1), scrolledwindow2, FALSE, TRUE);
+ gtk_widget_set_size_request (scrolledwindow2, 280, -1);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow2), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow2), GTK_SHADOW_IN);
+
+ pref_pluginlist = gtk_tree_view_new ();
+ gtk_widget_show (pref_pluginlist);
+ gtk_container_add (GTK_CONTAINER (scrolledwindow2), pref_pluginlist);
+
+ table5 = gtk_table_new (4, 2, FALSE);
+ gtk_widget_show (table5);
+ gtk_paned_pack2 (GTK_PANED (hpaned1), table5, TRUE, TRUE);
+ gtk_widget_set_size_request (table5, 400, -1);
+
+ label11 = gtk_label_new ("Description");
+ gtk_widget_show (label11);
+ gtk_table_attach (GTK_TABLE (table5), label11, 0, 1, 0, 1,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment (GTK_MISC (label11), 0, 0.5);
+
+ label12 = gtk_label_new ("Author(s)");
+ gtk_widget_show (label12);
+ gtk_table_attach (GTK_TABLE (table5), label12, 0, 1, 1, 2,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment (GTK_MISC (label12), 0, 0.5);
+
+ label13 = gtk_label_new ("Email");
+ gtk_widget_show (label13);
+ gtk_table_attach (GTK_TABLE (table5), label13, 0, 1, 2, 3,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment (GTK_MISC (label13), 0, 0.5);
+
+ label14 = gtk_label_new ("Website");
+ gtk_widget_show (label14);
+ gtk_table_attach (GTK_TABLE (table5), label14, 0, 1, 3, 4,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment (GTK_MISC (label14), 0, 0.5);
+
+ pref_plugin_descr = gtk_entry_new ();
+ gtk_widget_show (pref_plugin_descr);
+ gtk_table_attach (GTK_TABLE (table5), pref_plugin_descr, 1, 2, 0, 1,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_editable_set_editable (GTK_EDITABLE (pref_plugin_descr), FALSE);
+ gtk_entry_set_invisible_char (GTK_ENTRY (pref_plugin_descr), 9679);
+
+ pref_plugin_author = gtk_entry_new ();
+ gtk_widget_show (pref_plugin_author);
+ gtk_table_attach (GTK_TABLE (table5), pref_plugin_author, 1, 2, 1, 2,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_editable_set_editable (GTK_EDITABLE (pref_plugin_author), FALSE);
+ gtk_entry_set_invisible_char (GTK_ENTRY (pref_plugin_author), 9679);
+
+ pref_plugin_email = gtk_entry_new ();
+ gtk_widget_show (pref_plugin_email);
+ gtk_table_attach (GTK_TABLE (table5), pref_plugin_email, 1, 2, 2, 3,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_editable_set_editable (GTK_EDITABLE (pref_plugin_email), FALSE);
+ gtk_entry_set_invisible_char (GTK_ENTRY (pref_plugin_email), 9679);
+
+ pref_plugin_website = gtk_entry_new ();
+ gtk_widget_show (pref_plugin_website);
+ gtk_table_attach (GTK_TABLE (table5), pref_plugin_website, 1, 2, 3, 4,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_editable_set_editable (GTK_EDITABLE (pref_plugin_website), FALSE);
+ gtk_entry_set_invisible_char (GTK_ENTRY (pref_plugin_website), 9679);
+
+ label3 = gtk_label_new ("Plugins");
+ gtk_widget_show (label3);
+ gtk_notebook_set_tab_label (GTK_NOTEBOOK (notebook2), gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook2), 3), label3);
+
+ g_signal_connect ((gpointer) prefwin, "key_press_event",
+ G_CALLBACK (on_prefwin_key_press_event),
+ NULL);
+ g_signal_connect ((gpointer) pref_alsa_resampling, "clicked",
+ G_CALLBACK (on_pref_alsa_resampling_clicked),
+ NULL);
+ g_signal_connect ((gpointer) pref_replaygain_scale, "clicked",
+ G_CALLBACK (on_pref_replaygain_scale_clicked),
+ NULL);
+ g_signal_connect ((gpointer) pref_alsa_freewhenstopped, "clicked",
+ G_CALLBACK (on_pref_alsa_freewhenstopped_clicked),
+ NULL);
+ g_signal_connect ((gpointer) pref_soundcard, "changed",
+ G_CALLBACK (on_pref_soundcard_changed),
+ NULL);
+ g_signal_connect ((gpointer) pref_src_quality, "changed",
+ G_CALLBACK (on_pref_src_quality_changed),
+ NULL);
+ g_signal_connect ((gpointer) pref_replaygain_mode, "changed",
+ G_CALLBACK (on_pref_replaygain_mode_changed),
+ NULL);
+ g_signal_connect ((gpointer) pref_close_send_to_tray, "clicked",
+ G_CALLBACK (on_pref_close_send_to_tray_clicked),
+ NULL);
+ g_signal_connect ((gpointer) pref_network_proxyaddress, "changed",
+ G_CALLBACK (on_pref_network_proxyaddress_changed),
+ NULL);
+ g_signal_connect ((gpointer) pref_network_enableproxy, "clicked",
+ G_CALLBACK (on_pref_network_enableproxy_clicked),
+ NULL);
+ g_signal_connect ((gpointer) pref_network_proxyport, "changed",
+ G_CALLBACK (on_pref_network_proxyport_changed),
+ NULL);
+ g_signal_connect ((gpointer) pref_network_proxytype, "changed",
+ G_CALLBACK (on_pref_network_proxytype_changed),
+ NULL);
+ g_signal_connect ((gpointer) pref_pluginlist, "cursor_changed",
+ G_CALLBACK (on_pref_pluginlist_cursor_changed),
+ NULL);
+
+ /* Store pointers to all widgets, for use by lookup_widget(). */
+ GLADE_HOOKUP_OBJECT_NO_REF (prefwin, prefwin, "prefwin");
+ GLADE_HOOKUP_OBJECT (prefwin, notebook2, "notebook2");
+ GLADE_HOOKUP_OBJECT (prefwin, table3, "table3");
+ GLADE_HOOKUP_OBJECT (prefwin, label4, "label4");
+ GLADE_HOOKUP_OBJECT (prefwin, label5, "label5");
+ GLADE_HOOKUP_OBJECT (prefwin, label6, "label6");
+ GLADE_HOOKUP_OBJECT (prefwin, label8, "label8");
+ GLADE_HOOKUP_OBJECT (prefwin, label9, "label9");
+ GLADE_HOOKUP_OBJECT (prefwin, label15, "label15");
+ GLADE_HOOKUP_OBJECT (prefwin, pref_alsa_resampling, "pref_alsa_resampling");
+ GLADE_HOOKUP_OBJECT (prefwin, pref_replaygain_scale, "pref_replaygain_scale");
+ GLADE_HOOKUP_OBJECT (prefwin, pref_alsa_freewhenstopped, "pref_alsa_freewhenstopped");
+ GLADE_HOOKUP_OBJECT (prefwin, pref_soundcard, "pref_soundcard");
+ GLADE_HOOKUP_OBJECT (prefwin, pref_src_quality, "pref_src_quality");
+ GLADE_HOOKUP_OBJECT (prefwin, pref_replaygain_mode, "pref_replaygain_mode");
+ GLADE_HOOKUP_OBJECT (prefwin, Sound, "Sound");
+ GLADE_HOOKUP_OBJECT (prefwin, table4, "table4");
+ GLADE_HOOKUP_OBJECT (prefwin, label7, "label7");
+ GLADE_HOOKUP_OBJECT (prefwin, pref_close_send_to_tray, "pref_close_send_to_tray");
+ GLADE_HOOKUP_OBJECT (prefwin, label2, "label2");
+ GLADE_HOOKUP_OBJECT (prefwin, table6, "table6");
+ GLADE_HOOKUP_OBJECT (prefwin, label17, "label17");
+ GLADE_HOOKUP_OBJECT (prefwin, label18, "label18");
+ GLADE_HOOKUP_OBJECT (prefwin, pref_network_proxyaddress, "pref_network_proxyaddress");
+ GLADE_HOOKUP_OBJECT (prefwin, pref_network_enableproxy, "pref_network_enableproxy");
+ GLADE_HOOKUP_OBJECT (prefwin, label19, "label19");
+ GLADE_HOOKUP_OBJECT (prefwin, pref_network_proxyport, "pref_network_proxyport");
+ GLADE_HOOKUP_OBJECT (prefwin, label20, "label20");
+ GLADE_HOOKUP_OBJECT (prefwin, pref_network_proxytype, "pref_network_proxytype");
+ GLADE_HOOKUP_OBJECT (prefwin, label16, "label16");
+ GLADE_HOOKUP_OBJECT (prefwin, hpaned1, "hpaned1");
+ GLADE_HOOKUP_OBJECT (prefwin, scrolledwindow2, "scrolledwindow2");
+ GLADE_HOOKUP_OBJECT (prefwin, pref_pluginlist, "pref_pluginlist");
+ GLADE_HOOKUP_OBJECT (prefwin, table5, "table5");
+ GLADE_HOOKUP_OBJECT (prefwin, label11, "label11");
+ GLADE_HOOKUP_OBJECT (prefwin, label12, "label12");
+ GLADE_HOOKUP_OBJECT (prefwin, label13, "label13");
+ GLADE_HOOKUP_OBJECT (prefwin, label14, "label14");
+ GLADE_HOOKUP_OBJECT (prefwin, pref_plugin_descr, "pref_plugin_descr");
+ GLADE_HOOKUP_OBJECT (prefwin, pref_plugin_author, "pref_plugin_author");
+ GLADE_HOOKUP_OBJECT (prefwin, pref_plugin_email, "pref_plugin_email");
+ GLADE_HOOKUP_OBJECT (prefwin, pref_plugin_website, "pref_plugin_website");
+ GLADE_HOOKUP_OBJECT (prefwin, label3, "label3");
+
+ return prefwin;
+}
+
+GtkWidget*
+create_headermenu (void)
+{
+ GtkWidget *headermenu;
+ GtkWidget *add_column;
+ GtkWidget *add_column_menu;
+ GtkWidget *artist;
+ GtkWidget *album;
+ GtkWidget *tracknum;
+ GtkWidget *duration;
+ GtkWidget *playing;
+ GtkWidget *title;
+ GtkWidget *separator7;
+ GtkWidget *custom;
+ GtkWidget *remove_column;
+
+ headermenu = gtk_menu_new ();
+
+ add_column = gtk_menu_item_new_with_mnemonic ("Add column");
+ gtk_widget_show (add_column);
+ gtk_container_add (GTK_CONTAINER (headermenu), add_column);
+
+ add_column_menu = gtk_menu_new ();
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (add_column), add_column_menu);
+
+ artist = gtk_menu_item_new_with_mnemonic ("Artist");
+ gtk_widget_show (artist);
+ gtk_container_add (GTK_CONTAINER (add_column_menu), artist);
+
+ album = gtk_menu_item_new_with_mnemonic ("Album");
+ gtk_widget_show (album);
+ gtk_container_add (GTK_CONTAINER (add_column_menu), album);
+
+ tracknum = gtk_menu_item_new_with_mnemonic ("Track number");
+ gtk_widget_show (tracknum);
+ gtk_container_add (GTK_CONTAINER (add_column_menu), tracknum);
+
+ duration = gtk_menu_item_new_with_mnemonic ("Duration");
+ gtk_widget_show (duration);
+ gtk_container_add (GTK_CONTAINER (add_column_menu), duration);
+
+ playing = gtk_menu_item_new_with_mnemonic ("Playing status");
+ gtk_widget_show (playing);
+ gtk_container_add (GTK_CONTAINER (add_column_menu), playing);
+
+ title = gtk_menu_item_new_with_mnemonic ("Title");
+ gtk_widget_show (title);
+ gtk_container_add (GTK_CONTAINER (add_column_menu), title);
+
+ separator7 = gtk_separator_menu_item_new ();
+ gtk_widget_show (separator7);
+ gtk_container_add (GTK_CONTAINER (add_column_menu), separator7);
+ gtk_widget_set_sensitive (separator7, FALSE);
+
+ custom = gtk_menu_item_new_with_mnemonic ("Custom");
+ gtk_widget_show (custom);
+ gtk_container_add (GTK_CONTAINER (add_column_menu), custom);
+
+ remove_column = gtk_menu_item_new_with_mnemonic ("Remove this column");
+ gtk_widget_show (remove_column);
+ gtk_container_add (GTK_CONTAINER (headermenu), remove_column);
+
+ g_signal_connect ((gpointer) artist, "activate",
+ G_CALLBACK (on_artist_activate),
+ NULL);
+ g_signal_connect ((gpointer) album, "activate",
+ G_CALLBACK (on_album_activate),
+ NULL);
+ g_signal_connect ((gpointer) tracknum, "activate",
+ G_CALLBACK (on_tracknum_activate),
+ NULL);
+ g_signal_connect ((gpointer) duration, "activate",
+ G_CALLBACK (on_duration_activate),
+ NULL);
+ g_signal_connect ((gpointer) playing, "activate",
+ G_CALLBACK (on_playing_activate),
+ NULL);
+ g_signal_connect ((gpointer) title, "activate",
+ G_CALLBACK (on_title_activate),
+ NULL);
+ g_signal_connect ((gpointer) custom, "activate",
+ G_CALLBACK (on_custom_activate),
+ NULL);
+ g_signal_connect ((gpointer) remove_column, "activate",
+ G_CALLBACK (on_remove_column_activate),
+ NULL);
+
+ /* Store pointers to all widgets, for use by lookup_widget(). */
+ GLADE_HOOKUP_OBJECT_NO_REF (headermenu, headermenu, "headermenu");
+ GLADE_HOOKUP_OBJECT (headermenu, add_column, "add_column");
+ GLADE_HOOKUP_OBJECT (headermenu, add_column_menu, "add_column_menu");
+ GLADE_HOOKUP_OBJECT (headermenu, artist, "artist");
+ GLADE_HOOKUP_OBJECT (headermenu, album, "album");
+ GLADE_HOOKUP_OBJECT (headermenu, tracknum, "tracknum");
+ GLADE_HOOKUP_OBJECT (headermenu, duration, "duration");
+ GLADE_HOOKUP_OBJECT (headermenu, playing, "playing");
+ GLADE_HOOKUP_OBJECT (headermenu, title, "title");
+ GLADE_HOOKUP_OBJECT (headermenu, separator7, "separator7");
+ GLADE_HOOKUP_OBJECT (headermenu, custom, "custom");
+ GLADE_HOOKUP_OBJECT (headermenu, remove_column, "remove_column");
+
+ return headermenu;
+}
+
+GtkWidget*
+create_addlocation (void)
+{
+ GtkWidget *addlocation;
+ GtkWidget *vbox7;
+ GtkWidget *addlocation_entry;
+ GtkWidget *hbox8;
+ GtkWidget *label21;
+ GtkWidget *addlocation_ok;
+
+ addlocation = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title (GTK_WINDOW (addlocation), "Add Location");
+ gtk_window_set_modal (GTK_WINDOW (addlocation), TRUE);
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (addlocation), TRUE);
+
+ vbox7 = gtk_vbox_new (FALSE, 0);
+ gtk_widget_show (vbox7);
+ gtk_container_add (GTK_CONTAINER (addlocation), vbox7);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox7), 4);
+
+ addlocation_entry = gtk_entry_new ();
+ gtk_widget_show (addlocation_entry);
+ gtk_box_pack_start (GTK_BOX (vbox7), addlocation_entry, FALSE, FALSE, 0);
+ gtk_widget_set_size_request (addlocation_entry, 346, -1);
+ gtk_entry_set_invisible_char (GTK_ENTRY (addlocation_entry), 8226);
+
+ hbox8 = gtk_hbox_new (FALSE, 0);
+ gtk_widget_show (hbox8);
+ gtk_box_pack_start (GTK_BOX (vbox7), hbox8, FALSE, TRUE, 2);
+
+ label21 = gtk_label_new ("");
+ gtk_widget_show (label21);
+ gtk_box_pack_start (GTK_BOX (hbox8), label21, TRUE, FALSE, 0);
+
+ addlocation_ok = gtk_button_new_with_mnemonic ("OK");
+ gtk_widget_show (addlocation_ok);
+ gtk_box_pack_start (GTK_BOX (hbox8), addlocation_ok, FALSE, FALSE, 0);
+ gtk_widget_set_size_request (addlocation_ok, 83, -1);
+ GTK_WIDGET_UNSET_FLAGS (addlocation_ok, GTK_CAN_FOCUS);
+
+ g_signal_connect ((gpointer) addlocation, "key_press_event",
+ G_CALLBACK (on_addlocation_key_press_event),
+ NULL);
+ g_signal_connect ((gpointer) addlocation_entry, "activate",
+ G_CALLBACK (on_addlocation_entry_activate),
+ NULL);
+ g_signal_connect ((gpointer) addlocation_ok, "clicked",
+ G_CALLBACK (on_addlocation_ok_clicked),
+ NULL);
+
+ /* Store pointers to all widgets, for use by lookup_widget(). */
+ GLADE_HOOKUP_OBJECT_NO_REF (addlocation, addlocation, "addlocation");
+ GLADE_HOOKUP_OBJECT (addlocation, vbox7, "vbox7");
+ GLADE_HOOKUP_OBJECT (addlocation, addlocation_entry, "addlocation_entry");
+ GLADE_HOOKUP_OBJECT (addlocation, hbox8, "hbox8");
+ GLADE_HOOKUP_OBJECT (addlocation, label21, "label21");
+ GLADE_HOOKUP_OBJECT (addlocation, addlocation_ok, "addlocation_ok");
+
+ return addlocation;
+}
+
diff --git a/interface.h b/interface.h
index 66e3a07a..f9b470ff 100644
--- a/interface.h
+++ b/interface.h
@@ -3,8 +3,10 @@
*/
GtkWidget* create_mainwin (void);
-GtkWidget* create_aboutdialog (void);
GtkWidget* create_searchwin (void);
GtkWidget* create_traymenu (void);
GtkWidget* create_addprogress (void);
GtkWidget* create_helpwindow (void);
+GtkWidget* create_prefwin (void);
+GtkWidget* create_headermenu (void);
+GtkWidget* create_addlocation (void);
diff --git a/junklib.c b/junklib.c
index 0169e1a8..f0b4417c 100644
--- a/junklib.c
+++ b/junklib.c
@@ -22,9 +22,11 @@
#include <string.h>
#include "playlist.h"
#include "utf8.h"
+#include "plugins.h"
+
+//#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+#define trace(fmt,...)
-#define trace(...) { fprintf(stderr, __VA_ARGS__); }
-//#define trace(fmt,...)
#define min(x,y) ((x)<(y)?(x):(y))
#define max(x,y) ((x)>(y)?(x):(y))
@@ -420,15 +422,15 @@ str_trim_right (uint8_t *str, int len) {
// should read both id3v1 and id3v1.1
int
-junk_read_id3v1 (playItem_t *it, FILE *fp) {
+junk_read_id3v1 (playItem_t *it, DB_FILE *fp) {
if (!it || !fp) {
trace ("bad call to junk_read_id3v1!\n");
return -1;
}
uint8_t buffer[128];
// try reading from end
- fseek (fp, -128, SEEK_END);
- if (fread (buffer, 1, 128, fp) != 128) {
+ deadbeef->fseek (fp, -128, SEEK_END);
+ if (deadbeef->fread (buffer, 1, 128, fp) != 128) {
return -1;
}
if (strncmp (buffer, "TAG", 3)) {
@@ -496,24 +498,24 @@ junk_read_id3v1 (playItem_t *it, FILE *fp) {
}
int
-junk_read_ape (playItem_t *it, FILE *fp) {
+junk_read_ape (playItem_t *it, DB_FILE *fp) {
// trace ("trying to read ape tag\n");
// try to read footer, position must be already at the EOF right before
// id3v1 (if present)
uint8_t header[32];
- if (fseek (fp, -32, SEEK_END) == -1) {
+ if (deadbeef->fseek (fp, -32, SEEK_END) == -1) {
return -1; // something bad happened
}
- if (fread (header, 1, 32, fp) != 32) {
+ if (deadbeef->fread (header, 1, 32, fp) != 32) {
return -1; // something bad happened
}
if (strncmp (header, "APETAGEX", 8)) {
// try to skip 128 bytes backwards (id3v1)
- if (fseek (fp, -128-32, SEEK_END) == -1) {
+ if (deadbeef->fseek (fp, -128-32, SEEK_END) == -1) {
return -1; // something bad happened
}
- if (fread (header, 1, 32, fp) != 32) {
+ if (deadbeef->fread (header, 1, 32, fp) != 32) {
return -1; // something bad happened
}
if (strncmp (header, "APETAGEX", 8)) {
@@ -534,14 +536,14 @@ junk_read_ape (playItem_t *it, FILE *fp) {
//trace ("APEv%d, size=%d, items=%d, flags=%x\n", version, size, numitems, flags);
// now seek to beginning of the tag (exluding header)
- if (fseek (fp, -size, SEEK_CUR) == -1) {
+ if (deadbeef->fseek (fp, -size, SEEK_CUR) == -1) {
trace ("failed to seek to tag start (-%d)\n", size);
return -1;
}
int i;
for (i = 0; i < numitems; i++) {
uint8_t buffer[8];
- if (fread (buffer, 1, 8, fp) != 8) {
+ if (deadbeef->fread (buffer, 1, 8, fp) != 8) {
return -1;
}
uint32_t itemsize = extract_i32_le (&buffer[0]);
@@ -550,7 +552,7 @@ junk_read_ape (playItem_t *it, FILE *fp) {
char key[256];
int keysize = 0;
while (keysize <= 255) {
- if (fread (&key[keysize], 1, 1, fp) != 1) {
+ if (deadbeef->fread (&key[keysize], 1, 1, fp) != 1) {
return -1;
}
if (key[keysize] == 0) {
@@ -564,7 +566,7 @@ junk_read_ape (playItem_t *it, FILE *fp) {
key[255] = 0;
// read value
char value[itemsize+1];
- if (fread (value, 1, itemsize, fp) != itemsize) {
+ if (deadbeef->fread (value, 1, itemsize, fp) != itemsize) {
return -1;
}
value[itemsize] = 0;
@@ -644,14 +646,14 @@ id3v2_string_read (int version, uint8_t *out, int sz, int unsync, const uint8_t
}
int
-junk_get_leading_size (FILE *fp) {
+junk_get_leading_size (DB_FILE *fp) {
uint8_t header[10];
- int pos = ftell (fp);
- if (fread (header, 1, 10, fp) != 10) {
- fseek (fp, pos, SEEK_SET);
+ int pos = deadbeef->ftell (fp);
+ if (deadbeef->fread (header, 1, 10, fp) != 10) {
+ deadbeef->fseek (fp, pos, SEEK_SET);
return -1; // too short
}
- fseek (fp, pos, SEEK_SET);
+ deadbeef->fseek (fp, pos, SEEK_SET);
if (strncmp (header, "ID3", 3)) {
return -1; // no tag
}
@@ -678,15 +680,15 @@ junk_get_leading_size (FILE *fp) {
}
int
-junk_read_id3v2 (playItem_t *it, FILE *fp) {
+junk_read_id3v2 (playItem_t *it, DB_FILE *fp) {
int title_added = 0;
if (!it || !fp) {
trace ("bad call to junk_read_id3v2!\n");
return -1;
}
- rewind (fp);
+ deadbeef->rewind (fp);
uint8_t header[10];
- if (fread (header, 1, 10, fp) != 10) {
+ if (deadbeef->fread (header, 1, 10, fp) != 10) {
return -1; // too short
}
if (strncmp (header, "ID3", 3)) {
@@ -725,7 +727,7 @@ junk_read_id3v2 (playItem_t *it, FILE *fp) {
return -1;
}
uint8_t tag[size];
- if (fread (tag, 1, size, fp) != size) {
+ if (deadbeef->fread (tag, 1, size, fp) != size) {
return -1; // bad size
}
uint8_t *readptr = tag;
diff --git a/junklib.h b/junklib.h
index 76399bd6..807c300d 100644
--- a/junklib.h
+++ b/junklib.h
@@ -18,21 +18,21 @@
#ifndef __JUNKLIB_H
#define __JUNKLIB_H
-#include <stdio.h>
+#include "deadbeef.h"
struct playItem_s;
int
-junk_read_id3v1 (struct playItem_s *it, FILE *fp);
+junk_read_id3v1 (struct playItem_s *it, DB_FILE *fp);
int
-junk_read_id3v2 (struct playItem_s *it, FILE *fp);
+junk_read_id3v2 (struct playItem_s *it, DB_FILE *fp);
int
-junk_read_ape (struct playItem_s *it, FILE *fp);
+junk_read_ape (struct playItem_s *it, DB_FILE *fp);
int
-junk_get_leading_size (FILE *fp);
+junk_get_leading_size (DB_FILE *fp);
const char *
junk_detect_charset (const char *s);
diff --git a/main.c b/main.c
index bf042db5..eab1d1d6 100644
--- a/main.c
+++ b/main.c
@@ -29,6 +29,7 @@
#include <sys/fcntl.h>
#include <sys/errno.h>
#include <sys/prctl.h>
+#include <signal.h>
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
@@ -40,7 +41,6 @@
#include "unistd.h"
#include "threading.h"
#include "messagepump.h"
-#include "messages.h"
#include "gtkplaylist.h"
#include "codec.h"
#include "streamer.h"
@@ -67,15 +67,6 @@ GtkWidget *searchwin;
GtkStatusIcon *trayicon;
GtkWidget *traymenu;
-void
-set_tray_tooltip (const char *text) {
-#if (GTK_MINOR_VERSION < 16)
- gtk_status_icon_set_tooltip (trayicon, text);
-#else
- gtk_status_icon_set_tooltip_text (trayicon, text);
-#endif
-}
-
// playlist configuration structures
gtkplaylist_t main_playlist;
gtkplaylist_t search_playlist;
@@ -89,29 +80,58 @@ void
update_songinfo (void) {
char sbtext_new[512] = "-";
float songpos = last_songpos;
- if (p_ispaused ()) {
- strcpy (sbtext_new, "Paused");
- songpos = streamer_get_playpos ();
- }
- else if (p_isstopped ()) {
- strcpy (sbtext_new, "Stopped");
+
+ int daystotal = (int)pl_totaltime / (3600*24);
+ int hourtotal = ((int)pl_totaltime / 3600) % 24;
+ int mintotal = ((int)pl_totaltime/60) % 60;
+ int sectotal = ((int)pl_totaltime) % 60;
+
+ char totaltime_str[512] = "";
+ if (daystotal == 0)
+ snprintf (totaltime_str, sizeof (totaltime_str), "%d:%02d:%02d", hourtotal, mintotal, sectotal);
+
+ else if (daystotal == 1)
+ snprintf (totaltime_str, sizeof (totaltime_str), "1 day %d:%02d:%02d", hourtotal, mintotal, sectotal);
+
+ else
+ snprintf (totaltime_str, sizeof (totaltime_str), "%d days %d:%02d:%02d", daystotal, hourtotal, mintotal, sectotal);
+
+ if (p_isstopped ()) {
+ snprintf (sbtext_new, sizeof (sbtext_new), "Stopped | %s total playtime", totaltime_str);
songpos = 0;
}
else if (str_playing_song.decoder) {
- codec_lock ();
+// codec_lock ();
DB_decoder_t *c = str_playing_song.decoder;
float playpos = streamer_get_playpos ();
int minpos = playpos / 60;
int secpos = playpos - minpos * 60;
- int mindur = str_playing_song.duration / 60;
- int secdur = str_playing_song.duration - mindur * 60;
+ int mindur = str_playing_song._duration / 60;
+ int secdur = str_playing_song._duration - mindur * 60;
+
const char *mode = c->info.channels == 1 ? "Mono" : "Stereo";
int samplerate = c->info.samplerate;
int bitspersample = c->info.bps;
songpos = playpos;
- codec_unlock ();
+// codec_unlock ();
+
+ char t[100];
+ if (str_playing_song._duration >= 0) {
+ snprintf (t, sizeof (t), "%d:%02d", mindur, secdur);
+ }
+ else {
+ strcpy (t, "-:--");
+ }
- snprintf (sbtext_new, 512, "[%s] %dHz | %d bit | %s | %d:%02d / %d:%02d | %d songs total", str_playing_song.filetype ? str_playing_song.filetype:"-", samplerate, bitspersample, mode, minpos, secpos, mindur, secdur, pl_getcount ());
+ char sbitrate[20] = "";
+#if 0 // NOTE: do not enable that for stable branch yet
+ int bitrate = streamer_get_bitrate ();
+ if (bitrate > 0) {
+ snprintf (sbitrate, sizeof (sbitrate), "%d kbps ", bitrate);
+ }
+#endif
+ const char *spaused = p_ispaused () ? "Paused | " : "";
+ snprintf (sbtext_new, sizeof (sbtext_new), "%s%s %s| %dHz | %d bit | %s | %d:%02d / %s | %d songs | %s total playtime", spaused, str_playing_song.filetype ? str_playing_song.filetype:"-", sbitrate, samplerate, bitspersample, mode, minpos, secpos, t, pl_getcount (), totaltime_str);
}
if (strcmp (sbtext_new, sb_text)) {
@@ -137,7 +157,7 @@ update_songinfo (void) {
if (mainwin) {
GtkWidget *widget = lookup_widget (mainwin, "seekbar");
// translate volume to seekbar pixels
- songpos /= str_playing_song.duration;
+ songpos /= str_playing_song._duration;
songpos *= widget->allocation.width;
if ((int)(songpos*2) != (int)(last_songpos*2)) {
GDK_THREADS_ENTER();
@@ -324,7 +344,7 @@ player_thread (uintptr_t ctx) {
messagepump_push (M_TERMINATE, 0, 0, 0);
}
}
- plug_trigger_event (DB_EV_FRAMEUPDATE);
+ plug_trigger_event (DB_EV_FRAMEUPDATE, 0);
uint32_t msg;
uintptr_t ctx;
uint32_t p1;
@@ -352,39 +372,33 @@ player_thread (uintptr_t ctx) {
GDK_THREADS_LEAVE();
return;
case M_SONGCHANGED:
- GDK_THREADS_ENTER();
- // update window title
- int from = p1;
- int to = p2;
- if (from >= 0 || to >= 0) {
- if (to >= 0) {
- playItem_t *it = pl_get_for_idx (to);
- if (it) { // it might have been deleted after event was sent
- char str[600];
- char dname[512];
- pl_format_item_display_name (it, dname, 512);
- snprintf (str, 600, "DeaDBeeF - %s", dname);
- gtk_window_set_title (GTK_WINDOW (mainwin), str);
- set_tray_tooltip (str);
- }
- }
- else {
- gtk_window_set_title (GTK_WINDOW (mainwin), "DeaDBeeF");
- set_tray_tooltip ("DeaDBeeF");
- }
+ {
+ int from = p1;
+ int to = p2;
+ gtkpl_songchanged_wrapper (from, to);
+ plug_trigger_event (DB_EV_SONGCHANGED, 0);
}
- // update playlist view
- gtkpl_songchanged (&main_playlist, p1, p2);
- GDK_THREADS_LEAVE();
- plug_trigger_event (DB_EV_SONGCHANGED);
break;
case M_PLAYSONG:
gtkpl_playsong (&main_playlist);
- GDK_THREADS_ENTER();
if (playlist_current_ptr) {
+ GDK_THREADS_ENTER();
gtkpl_redraw_pl_row (&main_playlist, pl_get_idx_of (playlist_current_ptr), playlist_current_ptr);
+ GDK_THREADS_LEAVE();
+ }
+ break;
+ case M_TRACKCHANGED:
+ {
+ playItem_t *it = pl_get_for_idx (p1);
+ if (it) {
+ GDK_THREADS_ENTER();
+ gtkpl_redraw_pl_row (&main_playlist, p1, it);
+ if (it == playlist_current_ptr) {
+ gtkpl_current_track_changed (it);
+ }
+ GDK_THREADS_LEAVE();
+ }
}
- GDK_THREADS_LEAVE();
break;
case M_PLAYSONGNUM:
GDK_THREADS_ENTER();
@@ -461,6 +475,11 @@ player_thread (uintptr_t ctx) {
search_refresh ();
GDK_THREADS_LEAVE();
break;
+ case M_CONFIGCHANGED:
+ palsa_configchanged ();
+ streamer_configchanged ();
+ plug_trigger_event (DB_EV_CONFIGCHANGED, 0);
+ break;
}
}
usleep(50000);
@@ -537,6 +556,15 @@ on_trayicon_popup_menu (GtkWidget *widget,
return FALSE;
}
+void
+sigterm_handler (int sig) {
+ fprintf (stderr, "got sigterm, saving...\n");
+ pl_save (defpl);
+ conf_save ();
+ fprintf (stderr, "bye.\n");
+ exit (0);
+}
+
int
main (int argc, char *argv[]) {
srand (time (NULL));
@@ -637,18 +665,18 @@ main (int argc, char *argv[]) {
}
close(s);
+ signal (SIGTERM, sigterm_handler);
// become a server
server_start ();
conf_load ();
plug_load_all ();
pl_load (defpl);
- session_reset ();
session_load (sessfile);
messagepump_init ();
codec_init_locking ();
streamer_init ();
- p_init ();
+// p_init ();
thread_start (player_thread, 0);
g_thread_init (NULL);
@@ -683,18 +711,16 @@ main (int argc, char *argv[]) {
gdk_pixbuf_unref (mainwin_icon_pixbuf);
}
session_restore_window_attrs ((uintptr_t)mainwin);
- volume_set_db (session_get_volume ());
+ volume_set_db (conf_get_float ("playback.volume", 0));
// order and looping
const char *orderwidgets[3] = { "order_linear", "order_shuffle", "order_random" };
const char *loopingwidgets[3] = { "loop_all", "loop_disable", "loop_single" };
const char *w;
- w = orderwidgets[session_get_playlist_order ()];
+ w = orderwidgets[conf_get_int ("playback.order", 0)];
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (lookup_widget (mainwin, w)), TRUE);
- pl_set_order (session_get_playlist_order ());
- w = loopingwidgets[session_get_playlist_looping ()];
+ w = loopingwidgets[conf_get_int ("playback.loop", 0)];
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (lookup_widget (mainwin, w)), TRUE);
- gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (lookup_widget (mainwin, "scroll_follows_playback")), session_get_scroll_follows_playback () ? TRUE : FALSE);
- pl_set_loop_mode (session_get_playlist_looping ());
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (lookup_widget (mainwin, "scroll_follows_playback")), conf_get_int ("playlist.scroll.followplayback", 0) ? TRUE : FALSE);
searchwin = create_searchwin ();
gtk_window_set_transient_for (GTK_WINDOW (searchwin), GTK_WINDOW (mainwin));
@@ -718,15 +744,22 @@ main (int argc, char *argv[]) {
gtk_widget_show (mainwin);
gtk_main ();
+ // save config
+ pl_save (defpl);
+ conf_save ();
+ // stop receiving messages from outside
server_close ();
+ // at this point we can simply do exit(0), but let's clean up for debugging
+ gtkpl_free (&main_playlist);
+ gtkpl_free (&search_playlist);
gdk_threads_leave ();
messagepump_free ();
p_free ();
streamer_free ();
codec_free_locking ();
session_save (sessfile);
- pl_save (defpl);
pl_free ();
+ conf_free ();
plug_unload_all ();
return 0;
}
diff --git a/messages.h b/messages.h
deleted file mode 100644
index f0bfcb49..00000000
--- a/messages.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- DeaDBeeF - ultimate music player for GNU/Linux systems with X11
- Copyright (C) 2009 Alexey Yakovenko
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-*/
-#ifndef __MESSAGES_H
-#define __MESSAGES_H
-
-enum {
- M_SONGFINISHED,
- M_NEXTSONG,
- M_PREVSONG,
- M_PLAYSONG,
- M_PLAYSONGNUM,
- M_STOPSONG,
- M_PAUSESONG,
- M_PLAYRANDOM,
- M_SONGCHANGED, // p1=from, p2=to
- M_ADDDIR, // ctx = pointer to string, which must be freed by f_free
- M_ADDFILES, // ctx = GSList pointer, must be freed with g_slist_free
- M_ADDDIRS, // ctx = GSList pointer, must be freed with g_slist_free
- M_OPENFILES, // ctx = GSList pointer, must be freed with g_slist_free
- M_FMDRAGDROP, // ctx = char* ptr, must be freed with standard free, p1 is length of data, p2 is drop_y
- M_TERMINATE, // must be sent to player thread to terminate
- M_PLAYLISTREFRESH,
- M_REINIT_SOUND,
-};
-
-#endif // __MESSAGES_H
diff --git a/moduleconf.h b/moduleconf.h
index fbb48a9a..4fdc123c 100644
--- a/moduleconf.h
+++ b/moduleconf.h
@@ -1,3 +1,4 @@
PLUG(gme)
PLUG(dumb)
PLUG(sid)
+PLUG(stdio)
diff --git a/palsa.c b/palsa.c
index 15fc6e86..80ec577b 100644
--- a/palsa.c
+++ b/palsa.c
@@ -29,58 +29,50 @@
#include "conf.h"
#include "volume.h"
#include "messagepump.h"
-#include "messages.h"
+#include "deadbeef.h"
-#define trace(...) { fprintf(stderr, __VA_ARGS__); }
-//#define trace(fmt,...)
+//#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+#define trace(fmt,...)
static snd_pcm_t *audio;
-static int16_t *samplebuffer;
static int bufsize = -1;
static int alsa_terminate;
-static int alsa_rate = 48000;
+static int alsa_rate = 44100;
static int state; // 0 = stopped, 1 = playing, 2 = pause
static uintptr_t mutex;
-static int canpause;
static intptr_t alsa_tid;
+static int conf_alsa_resample = 0;
+static char conf_alsa_soundcard[100] = "default";
+
static void
palsa_callback (char *stream, int len);
static void
palsa_thread (uintptr_t context);
-int
-palsa_init (void) {
- int err;
- snd_pcm_hw_params_t *hw_params;
- snd_pcm_sw_params_t *sw_params;
- state = 0;
- alsa_rate = conf_samplerate;
+static int
+palsa_set_hw_params (int samplerate) {
+ snd_pcm_hw_params_t *hw_params = NULL;
+// int alsa_resample = conf_get_int ("alsa.resample", 0);
+ int err = 0;
- if ((err = snd_pcm_open (&audio, conf_alsa_soundcard, SND_PCM_STREAM_PLAYBACK, 0))) {
- trace ("could not open audio device (%s)\n",
- snd_strerror (err));
- exit (-1);
- }
-
- mutex = mutex_create ();
if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
trace ("cannot allocate hardware parameter structure (%s)\n",
snd_strerror (err));
- goto open_error;
+ goto error;
}
if ((err = snd_pcm_hw_params_any (audio, hw_params)) < 0) {
trace ("cannot initialize hardware parameter structure (%s)\n",
snd_strerror (err));
- goto open_error;
+ goto error;
}
if ((err = snd_pcm_hw_params_set_access (audio, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
trace ("cannot set access type (%s)\n",
snd_strerror (err));
- goto open_error;
+ goto error;
}
snd_pcm_format_t fmt;
@@ -92,52 +84,50 @@ palsa_init (void) {
if ((err = snd_pcm_hw_params_set_format (audio, hw_params, fmt)) < 0) {
trace ("cannot set sample format (%s)\n",
snd_strerror (err));
- goto open_error;
+ goto error;
}
snd_pcm_hw_params_get_format (hw_params, &fmt);
- printf ("chosen sample format: %04Xh\n", (int)fmt);
+ trace ("chosen sample format: %04Xh\n", (int)fmt);
- canpause = 0;//snd_pcm_hw_params_can_pause (hw_params);
-
- int val = alsa_rate;
+ int val = samplerate;
int ret = 0;
- if ((err = snd_pcm_hw_params_set_rate_resample (audio, hw_params, 0)) < 0) {
+ if ((err = snd_pcm_hw_params_set_rate_resample (audio, hw_params, conf_alsa_resample)) < 0) {
trace ("cannot setup resampling (%s)\n",
snd_strerror (err));
- goto open_error;
+ goto error;
}
if ((err = snd_pcm_hw_params_set_rate_near (audio, hw_params, &val, &ret)) < 0) {
trace ("cannot set sample rate (%s)\n",
snd_strerror (err));
- goto open_error;
+ goto error;
}
alsa_rate = val;
- printf ("chosen samplerate: %d Hz\n", alsa_rate);
+ trace ("chosen samplerate: %d Hz\n", alsa_rate);
if ((err = snd_pcm_hw_params_set_channels (audio, hw_params, 2)) < 0) {
trace ("cannot set channel count (%s)\n",
snd_strerror (err));
- goto open_error;
+ goto error;
}
int nchan;
snd_pcm_hw_params_get_channels (hw_params, &nchan);
- printf ("alsa channels: %d\n", nchan);
+ trace ("alsa channels: %d\n", nchan);
unsigned int buffer_time = 500000;
int dir;
if ((err = snd_pcm_hw_params_set_buffer_time_near (audio, hw_params, &buffer_time, &dir)) < 0) {
trace ("Unable to set buffer time %i for playback: %s\n", buffer_time, snd_strerror(err));
- goto open_error;
+ goto error;
}
trace ("alsa buffer time: %d ms\n", buffer_time);
snd_pcm_uframes_t size;
if ((err = snd_pcm_hw_params_get_buffer_size (hw_params, &size)) < 0) {
trace ("Unable to get buffer size for playback: %s\n", snd_strerror(err));
- goto open_error;
+ goto error;
}
trace ("alsa buffer size: %d frames\n", size);
bufsize = size;
@@ -145,10 +135,39 @@ palsa_init (void) {
if ((err = snd_pcm_hw_params (audio, hw_params)) < 0) {
trace ("cannot set parameters (%s)\n",
snd_strerror (err));
- goto open_error;
+ goto error;
+ }
+error:
+ if (hw_params) {
+ snd_pcm_hw_params_free (hw_params);
+ }
+ return err;
+}
+
+int
+palsa_init (void) {
+ int err;
+
+ // get and cache conf variables
+ strcpy (conf_alsa_soundcard, conf_get_str ("alsa_soundcard", "default"));
+ conf_alsa_resample = conf_get_int ("alsa.resample", 0);
+ trace ("alsa_soundcard: %s\n", conf_alsa_soundcard);
+ trace ("alsa.resample: %d\n", conf_alsa_resample);
+
+ snd_pcm_sw_params_t *sw_params = NULL;
+ state = 0;
+ //const char *conf_alsa_soundcard = conf_get_str ("alsa_soundcard", "default");
+ if ((err = snd_pcm_open (&audio, conf_alsa_soundcard, SND_PCM_STREAM_PLAYBACK, 0))) {
+ trace ("could not open audio device (%s)\n",
+ snd_strerror (err));
+ return -1;
}
- snd_pcm_hw_params_free (hw_params);
+ mutex = mutex_create ();
+
+ if (palsa_set_hw_params (alsa_rate) < 0) {
+ goto open_error;
+ }
if ((err = snd_pcm_sw_params_malloc (&sw_params)) < 0) {
trace ("cannot allocate software parameters structure (%s)\n",
@@ -161,7 +180,7 @@ palsa_init (void) {
goto open_error;
}
- if ((err = snd_pcm_sw_params_set_avail_min (audio, sw_params, size/2)) < 0) {
+ if ((err = snd_pcm_sw_params_set_avail_min (audio, sw_params, bufsize/2)) < 0) {
trace ("cannot set minimum available count (%s)\n",
snd_strerror (err));
goto open_error;
@@ -184,6 +203,8 @@ palsa_init (void) {
snd_strerror (err));
goto open_error;
}
+ snd_pcm_sw_params_free (sw_params);
+ sw_params = NULL;
/* the interface will interrupt the kernel every N frames, and ALSA
will wake up this program very soon after that.
@@ -197,80 +218,95 @@ palsa_init (void) {
snd_pcm_start (audio);
- // take returned fragsize
- samplebuffer = malloc (bufsize);
-
- if (!samplebuffer)
- {
- trace ("AUDIO: Unable to allocate memory for sample buffers.\n");
- goto open_error;
- }
-
alsa_terminate = 0;
alsa_tid = thread_start (palsa_thread, 0);
return 0;
open_error:
- if (audio != NULL)
- {
+ if (sw_params) {
+ snd_pcm_sw_params_free (sw_params);
+ }
+ if (audio != NULL) {
palsa_free ();
}
return -1;
}
+int
+palsa_change_rate (int rate) {
+ if (!audio) {
+ return 0;
+ }
+ if (rate == alsa_rate) {
+ trace ("palsa_change_rate: same rate (%d), ignored\n", rate);
+ return rate;
+ }
+ trace ("trying to change samplerate to: %d\n", rate);
+ mutex_lock (mutex);
+ snd_pcm_drop (audio);
+ int ret = palsa_set_hw_params (rate);
+ if (state != 0) {
+ snd_pcm_start (audio);
+ }
+ mutex_unlock (mutex);
+ if (ret < 0) {
+ return -1;
+ }
+ trace ("chosen samplerate: %d Hz\n", alsa_rate);
+ return alsa_rate;
+}
+
void
palsa_free (void) {
- if (audio) {
- mutex_lock (mutex);
+ trace ("palsa_free\n");
+ if (audio && !alsa_terminate) {
alsa_terminate = 1;
- if (alsa_tid) {
- thread_join (alsa_tid);
- alsa_tid = 0;
- }
+ thread_join (alsa_tid);
+ alsa_tid = 0;
snd_pcm_close(audio);
audio = NULL;
- if (samplebuffer) {
- free (samplebuffer);
- samplebuffer = NULL;
- }
- mutex_unlock (mutex);
mutex_free (mutex);
+ state = 0;
+ alsa_terminate = 0;
}
}
-static int hwpaused;
static void
palsa_hw_pause (int pause) {
- mutex_lock (mutex);
- if (canpause) {
- snd_pcm_pause (audio, pause);
+ if (!audio) {
+ return;
+ }
+ if (state == 0) {
+ return;
+ }
+ if (pause == 1) {
+ snd_pcm_drop (audio);
}
else {
- if (pause == 1) {
- snd_pcm_drop (audio);
- }
- else {
- snd_pcm_prepare (audio);
- snd_pcm_start (audio);
- }
- hwpaused = pause;
+ snd_pcm_prepare (audio);
+ snd_pcm_start (audio);
}
- hwpaused = pause;
- mutex_unlock (mutex);
}
int
palsa_play (void) {
- // start updating thread
int err;
if (state == 0) {
- if ((err = snd_pcm_prepare (audio)) < 0) {
- trace ("cannot prepare audio interface for use (%s)\n",
- snd_strerror (err));
+ if (!audio) {
+ if (palsa_init () < 0) {
+ state = 0;
+ return -1;
+ }
+ }
+ else {
+ if ((err = snd_pcm_prepare (audio)) < 0) {
+ trace ("cannot prepare audio interface for use (%s)\n",
+ snd_strerror (err));
+ return -1;
+ }
}
- streamer_reset (1);
}
if (state != 1) {
state = 1;
@@ -282,9 +318,19 @@ palsa_play (void) {
int
palsa_stop (void) {
- // set stop state
+ if (!audio) {
+ return 0;
+ }
state = 0;
- snd_pcm_drop (audio);
+ if (conf_get_int ("alsa.freeonstop", 0)) {
+ palsa_free ();
+ }
+ else {
+ mutex_lock (mutex);
+ snd_pcm_drop (audio);
+ mutex_unlock (mutex);
+ }
+ streamer_reset (1);
return 0;
}
@@ -301,9 +347,12 @@ palsa_ispaused (void) {
int
palsa_pause (void) {
+ if (state == 0 || !audio) {
+ return -1;
+ }
// set pause state
- state = 2;
palsa_hw_pause (1);
+ state = 2;
return 0;
}
@@ -334,6 +383,7 @@ palsa_thread (uintptr_t context) {
usleep (10000);
continue;
}
+
mutex_lock (mutex);
/* wait till the interface is ready for data, or 1 second
has elapsed.
@@ -378,7 +428,7 @@ palsa_thread (uintptr_t context) {
snd_pcm_start (audio);
}
mutex_unlock (mutex);
- usleep (1000); // removing this causes deadlock on exit
+// usleep (1000); // removing this causes deadlock on exit
}
}
@@ -389,11 +439,84 @@ palsa_callback (char *stream, int len) {
return;
}
int bytesread = streamer_read (stream, len);
- int ivolume = volume_get_amp () * 1000;
+
+// FIXME: move volume control to streamer_read for copy optimization
+#if 0
+ int16_t vol[4];
+ vol[0] = volume_get_amp () * 255; // that will be extra 8 bits
+ // pack 4 times
+ vol[1] = vol[2] = vol[3] = vol[0];
+
+ // apply volume with mmx
+ __asm__ volatile(
+ " mov %0, %%ecx\n\t"
+ " shr $4, %%ecx\n\t"
+ " mov %1, %%eax\n\t"
+ " movq %2, %mm1\n\t"
+ "1:\n\t"
+ " movq [%%eax], %mm0\n\t"
+ " movq %mm0, %mm2\n\t"
+ " movq %mm0, %mm3\n\t"
+ " pmullw %mm1, %mm2\n\t"
+ " pmulhw %mm1, %mm3\n\t"
+ " psrlw $8, %mm2\n\t" // discard lowest 8 bits
+ " psllw $8, %mm3\n\t" // shift left 8 lsbs of hiwords
+ " por %mm3, %mm2\n\t" // OR them together
+ " movq %mm3, [%%eax]\n\t" // load back to memory
+ " add $8, %%eax\n\t"
+ " dec %%ecx\n\t"
+ " jnz 1b\n\t"
+ :
+ : "r"(len), "r"(stream), "r"(vol)
+ : "%ecx", "%eax"
+ );
+
+#else
+ int16_t ivolume = volume_get_amp () * 1000;
for (int i = 0; i < bytesread/2; i++) {
((int16_t*)stream)[i] = (int16_t)(((int32_t)(((int16_t*)stream)[i])) * ivolume / 1000);
}
+#endif
if (bytesread < len) {
memset (stream + bytesread, 0, len-bytesread);
}
}
+
+void
+palsa_configchanged (void) {
+ int alsa_resample = conf_get_int ("alsa.resample", 0);
+ const char *alsa_soundcard = conf_get_str ("alsa_soundcard", "default");
+ if (alsa_resample != conf_alsa_resample
+ || strcmp (alsa_soundcard, conf_alsa_soundcard)) {
+ messagepump_push (M_REINIT_SOUND, 0, 0, 0);
+ }
+}
+
+// derived from alsa-utils/aplay.c
+void
+palsa_enum_soundcards (void (*callback)(const char *name, const char *desc, void *), void *userdata) {
+ void **hints, **n;
+ char *name, *descr, *descr1, *io;
+ const char *filter = "Output";
+ if (snd_device_name_hint(-1, "pcm", &hints) < 0)
+ return;
+ n = hints;
+ while (*n != NULL) {
+ name = snd_device_name_get_hint(*n, "NAME");
+ descr = snd_device_name_get_hint(*n, "DESC");
+ io = snd_device_name_get_hint(*n, "IOID");
+ if (io == NULL || !strcmp(io, filter)) {
+ if (name && descr && callback) {
+ callback (name, descr, userdata);
+ }
+ }
+ if (name != NULL)
+ free(name);
+ if (descr != NULL)
+ free(descr);
+ if (io != NULL)
+ free(io);
+ n++;
+ }
+ snd_device_name_free_hint(hints);
+}
diff --git a/palsa.h b/palsa.h
index a544790c..2896702a 100644
--- a/palsa.h
+++ b/palsa.h
@@ -25,6 +25,9 @@ void
palsa_free (void);
int
+palsa_change_rate (int rate);
+
+int
palsa_play (void);
int
@@ -45,5 +48,11 @@ palsa_unpause (void);
int
palsa_get_rate (void);
+void
+palsa_configchanged (void);
+
+void
+palsa_enum_soundcards (void (*callback)(const char *name, const char *desc, void*), void *userdata);
+
#endif // __PALSA_H
diff --git a/pixmaps/Makefile.am b/pixmaps/Makefile.am
index 86bc3ef0..fd8452cd 100644
--- a/pixmaps/Makefile.am
+++ b/pixmaps/Makefile.am
@@ -1,10 +1,11 @@
pixmapsdir = $(pkgdatadir)/pixmaps
pixmaps_DATA =\
-next_24.png\
pause_16.png\
-pause_24.png\
play_16.png\
+buffering_16.png\
+next_24.png\
+pause_24.png\
play_24.png\
prev_24.png\
random_24.png\
diff --git a/pixmaps/buffering_16.png b/pixmaps/buffering_16.png
new file mode 100644
index 00000000..bf5b8887
--- /dev/null
+++ b/pixmaps/buffering_16.png
Binary files differ
diff --git a/playlist.c b/playlist.c
index c47de08d..ac93df67 100644
--- a/playlist.c
+++ b/playlist.c
@@ -30,10 +30,11 @@
#include "codec.h"
#include "streamer.h"
#include "messagepump.h"
-#include "messages.h"
#include "playback.h"
#include "plugins.h"
#include "junklib.h"
+#include "vfs.h"
+#include "conf.h"
// 1.0->1.1 changelog:
// added sample-accurate seek positions for sub-tracks
@@ -45,12 +46,15 @@
#define SKIP_BLANK_CUE_TRACKS 1
+#define min(x,y) ((x)<(y)?(x):(y))
+
playItem_t *playlist_head[PL_MAX_ITERATORS];
playItem_t *playlist_tail[PL_MAX_ITERATORS];
playItem_t *playlist_current_ptr;
int pl_count = 0;
-static int pl_order = 0; // 0 = linear, 1 = shuffle, 2 = random
-static int pl_loop_mode = 0; // 0 = loop, 1 = don't loop, 2 = loop single
+float pl_totaltime = 0;
+//static int pl_order = 0; // 0 = linear, 1 = shuffle, 2 = random
+//static int pl_loop_mode = 0; // 0 = loop, 1 = don't loop, 2 = loop single
void
pl_free (void) {
@@ -59,7 +63,14 @@ pl_free (void) {
}
}
-static const char *
+static const uint8_t *
+pl_str_skipspaces (const uint8_t *p, const uint8_t *end) {
+ while (p < end && *p <= ' ') {
+ p++;
+ }
+ return p;
+}
+static const uint8_t *
pl_cue_skipspaces (const uint8_t *p) {
while (*p && *p <= ' ') {
p++;
@@ -68,7 +79,7 @@ pl_cue_skipspaces (const uint8_t *p) {
}
static void
-pl_get_qvalue_from_cue (const char *p, int sz, char *out) {
+pl_get_qvalue_from_cue (const uint8_t *p, int sz, char *out) {
char *str = out;
if (*p == 0) {
*out = 0;
@@ -187,22 +198,22 @@ pl_process_cue_track (playItem_t *after, const char *fname, playItem_t **prev, c
return after;
}
(*prev)->endsample = (prevtime * samplerate) - 1;
- (*prev)->duration = (float)((*prev)->endsample - (*prev)->startsample + 1) / samplerate;
- if ((*prev)->duration < 0) {
+ pl_set_item_duration (*prev, (float)((*prev)->endsample - (*prev)->startsample + 1) / samplerate);
+ if ((*prev)->_duration < 0) {
// might be bad cuesheet file, try to fix
trace ("cuesheet seems to be corrupted, trying workaround\n");
- trace ("[bad:] calc endsample=%d, prevtime=%f, samplerate=%d, prev track duration=%f\n", (*prev)->endsample, prevtime, samplerate, (*prev)->duration);
+ //trace ("[bad:] calc endsample=%d, prevtime=%f, samplerate=%d, prev track duration=%f\n", (*prev)->endsample, prevtime, samplerate, (*prev)->duration);
prevtime = f_index01;
(*prev)->endsample = (prevtime * samplerate) - 1;
- (*prev)->duration = (float)((*prev)->endsample - (*prev)->startsample + 1) / samplerate;
- if ((*prev)->duration > 0) {
+ pl_set_item_duration (*prev, (float)((*prev)->endsample - (*prev)->startsample + 1) / samplerate);
+ if ((*prev)->_duration > 0) {
trace ("success :-D\n");
}
else {
trace ("fail :-(\n");
}
}
- trace ("calc endsample=%d, prevtime=%f, samplerate=%d, prev track duration=%f\n", (*prev)->endsample, prevtime, samplerate, (*prev)->duration);
+ //trace ("calc endsample=%d, prevtime=%f, samplerate=%d, prev track duration=%f\n", (*prev)->endsample, prevtime, samplerate, (*prev)->duration);
}
// non-compliant hack to handle tracks which only store pregap info
if (!index01[0]) {
@@ -307,8 +318,9 @@ pl_insert_cue_from_buffer (playItem_t *after, const char *fname, const uint8_t *
}
after = pl_process_cue_track (after, fname, &prev, track, index00, index01, pregap, title, performer, albumtitle, decoder, ftype, samplerate);
if (after) {
+ trace ("last track endsample: %d\n", numsamples-1);
after->endsample = numsamples-1;
- after->duration = (float)(after->endsample - after->startsample + 1) / samplerate;
+ pl_set_item_duration (after, (float)(after->endsample - after->startsample + 1) / samplerate);
}
return after;
}
@@ -349,45 +361,289 @@ pl_insert_cue (playItem_t *after, const char *fname, struct DB_decoder_s *decode
}
playItem_t *
+pl_insert_m3u (playItem_t *after, const char *fname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data) {
+ // skip all empty lines and comments
+ DB_FILE *fp = vfs_fopen (fname);
+ if (!fp) {
+ trace ("failed to open file %s\n", fname);
+ return NULL;
+ }
+ int sz = vfs_fgetlength (fp);
+ if (sz > 1024*1024) {
+ vfs_fclose (fp);
+ trace ("file %s is too large to be a playlist\n", fname);
+ return NULL;
+ }
+ if (sz < 30) {
+ vfs_fclose (fp);
+ trace ("file %s is too small to be a playlist (%d)\n", fname, sz);
+ return NULL;
+ }
+ trace ("loading m3u...\n");
+ uint8_t buffer[sz];
+ vfs_fread (buffer, 1, sz, fp);
+ vfs_fclose (fp);
+ const uint8_t *p = buffer;
+ const uint8_t *end = buffer+sz;
+ while (p < end) {
+ p = pl_str_skipspaces (p, end);
+ if (p >= end) {
+ break;
+ }
+ if (*p == '#') {
+ while (p < end && *p >= 0x20) {
+ p++;
+ }
+ if (p >= end) {
+ break;
+ }
+ continue;
+ }
+ const uint8_t *e = p;
+ while (e < end && *e >= 0x20) {
+ e++;
+ }
+ int n = e-p;
+ uint8_t nm[n+1];
+ memcpy (nm, p, n);
+ nm[n] = 0;
+ trace ("adding file %s\n", nm);
+ playItem_t *it = pl_insert_file (after, nm, pabort, cb, user_data);
+ if (it) {
+ after = it;
+ }
+ if (pabort && *pabort) {
+ return after;
+ }
+ p = e;
+ if (p >= end) {
+ break;
+ }
+ }
+ return after;
+}
+
+// that has to be opened with vfs functions to allow loading from http, as
+// referenced from M3U.
+playItem_t *
+pl_insert_pls (playItem_t *after, const char *fname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data) {
+ DB_FILE *fp = vfs_fopen (fname);
+ if (!fp) {
+ trace ("failed to open file %s\n", fname);
+ return NULL;
+ }
+ int sz = vfs_fgetlength (fp);
+ if (sz > 1024*1024) {
+ vfs_fclose (fp);
+ trace ("file %s is too large to be a playlist\n", fname);
+ return NULL;
+ }
+ if (sz < 30) {
+ vfs_fclose (fp);
+ trace ("file %s is too small to be a playlist\n", fname);
+ return NULL;
+ }
+ vfs_rewind (fp);
+ uint8_t buffer[sz];
+ vfs_fread (buffer, 1, sz, fp);
+ vfs_fclose (fp);
+ // 1st line must be "[playlist]"
+ const uint8_t *p = buffer;
+ const uint8_t *end = buffer+sz;
+ if (strncasecmp (p, "[playlist]", 10)) {
+ trace ("file %s doesn't begin with [playlist]\n", fname);
+ return NULL;
+ }
+ p += 10;
+ p = pl_str_skipspaces (p, end);
+ if (p >= end) {
+ trace ("file %s finished before numberofentries had been read\n", fname);
+ return NULL;
+ }
+ if (strncasecmp (p, "numberofentries=", 16)) {
+ trace ("can't get number of entries from %s\n", fname);
+ return NULL;
+ }
+ p += 15;
+ // ignore numentries - no real need for it here
+ while (p < end && *p > 0x20) {
+ p++;
+ }
+ p = pl_str_skipspaces (p, end);
+ // fetch all tracks
+ char url[1024] = "";
+ char title[1024] = "";
+ char length[20] = "";
+ int nfile = 1;
+ while (p < end) {
+ if (p >= end) {
+ break;
+ }
+ if (end-p < 6) {
+ break;
+ }
+ if (strncasecmp (p, "file", 4)) {
+ break;
+ }
+ p += 4;
+ while (p < end && *p != '=') {
+ p++;
+ }
+ p++;
+ if (p >= end) {
+ break;
+ }
+ const uint8_t *e = p;
+ while (e < end && *e >= 0x20) {
+ e++;
+ }
+ int n = e-p;
+ n = min (n, sizeof (url)-1);
+ memcpy (url, p, n);
+ url[n] = 0;
+ trace ("url: %s\n", url);
+ p = ++e;
+ p = pl_str_skipspaces (p, end);
+ if (strncasecmp (p, "title", 5)) {
+ break;
+ }
+ p += 5;
+ while (p < end && *p != '=') {
+ p++;
+ }
+ p++;
+ if (p >= end) {
+ break;
+ }
+ e = p;
+ while (e < end && *e >= 0x20) {
+ e++;
+ }
+ n = e-p;
+ n = min (n, sizeof (title)-1);
+ memcpy (title, p, n);
+ title[n] = 0;
+ trace ("title: %s\n", title);
+ p = ++e;
+ p = pl_str_skipspaces (p, end);
+ if (strncasecmp (p, "length", 6)) {
+ break;
+ }
+ p += 6;
+ while (p < end && *p != '=') {
+ p++;
+ }
+ p++;
+ if (p >= end) {
+ break;
+ }
+ e = p;
+ while (e < end && *e >= 0x20) {
+ e++;
+ }
+ n = e-p;
+ n = min (n, sizeof (length)-1);
+ memcpy (length, p, n);
+ length[n] = 0;
+ trace ("length: %s\n", length);
+ // add track
+ playItem_t *it = pl_insert_file (after, url, pabort, cb, user_data);
+ if (it) {
+ after = it;
+ pl_set_item_duration (it, atoi (length));
+ pl_add_meta (it, "title", title);
+ }
+ if (pabort && *pabort) {
+ return after;
+ }
+ }
+ return after;
+}
+
+playItem_t *
pl_insert_file (playItem_t *after, const char *fname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data) {
if (!fname) {
return NULL;
}
+
// detect decoder
-// DB_decoder_t *decoder = NULL;
const char *eol = fname + strlen (fname) - 1;
while (eol > fname && *eol != '.') {
eol--;
}
eol++;
+ // detect pls/m3u files
+ // they must be handled before checking for http://,
+ // so that remote playlist files referenced from other playlist files could
+ // be loaded correctly
+ if (!memcmp (eol, "m3u", 4)) {
+ return pl_insert_m3u (after, fname, pabort, cb, user_data);
+ }
+ else if (!memcmp (eol, "pls", 4)) {
+
+ return pl_insert_pls (after, fname, pabort, cb, user_data);
+ }
+
+ // add all posible streams as special-case:
+ // set decoder to NULL, and filetype to "content"
+ // streamer is responsible to determine content type on 1st access and
+ // update decoder and filetype fields
+ if (strncasecmp (fname, "file://", 7)) {
+ const char *p = fname;
+ int detect_on_access = 1;
+ for (p = fname; *p; p++) {
+ if (!strncmp (p, "://", 3)) {
+ break;
+ }
+ if (!isalpha (*p)) {
+ detect_on_access = 0;
+ break;
+ }
+ }
+ if (detect_on_access && *fname != ':') {
+ playItem_t *it = pl_item_alloc ();
+ it->decoder = NULL;
+ it->fname = strdup (fname);
+ it->filetype = "content";
+ pl_set_item_duration (it, -1);
+ pl_add_meta (it, "title", NULL);
+ return pl_insert_item (after, it);
+ }
+ }
+ else {
+ fname += 7;
+ }
+
DB_decoder_t **decoders = plug_get_decoder_list ();
// match by decoder
for (int i = 0; decoders[i]; i++) {
if (decoders[i]->exts && decoders[i]->insert) {
const char **exts = decoders[i]->exts;
- if (exts) {
- for (int e = 0; exts[e]; e++) {
- if (!strcasecmp (exts[e], eol)) {
- playItem_t *inserted = NULL;
- if ((inserted = (playItem_t *)decoders[i]->insert (DB_PLAYITEM (after), fname)) != NULL) {
- if (cb) {
- if (cb (inserted, user_data) < 0) {
- *pabort = 1;
- }
+ for (int e = 0; exts[e]; e++) {
+ if (!strcasecmp (exts[e], eol)) {
+ playItem_t *inserted = NULL;
+ if ((inserted = (playItem_t *)decoders[i]->insert (DB_PLAYITEM (after), fname)) != NULL) {
+ if (cb) {
+ if (cb (inserted, user_data) < 0) {
+ *pabort = 1;
}
- return inserted;
}
+ return inserted;
}
}
}
}
}
+ fprintf (stderr, "no decoder found for %s\n", fname);
return NULL;
}
playItem_t *
pl_insert_dir (playItem_t *after, const char *dirname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data) {
+ if (!memcmp (dirname, "file://", 7)) {
+ dirname += 7;
+ }
struct stat buf;
lstat (dirname, &buf);
if (S_ISLNK(buf.st_mode)) {
@@ -449,46 +705,6 @@ pl_add_dir (const char *dirname, int (*cb)(playItem_t *it, void *data), void *us
return 0;
}
return -1;
-// {{{ original pl_add_dir code
-#if 0
- struct stat buf;
- lstat (dirname, &buf);
- if (S_ISLNK(buf.st_mode)) {
- return -1;
- }
- struct dirent **namelist = NULL;
- int n;
-
- n = scandir (dirname, &namelist, NULL, alphasort);
- if (n < 0)
- {
- if (namelist)
- free (namelist);
- return -1; // not a dir or no read access
- }
- else
- {
- int i;
- for (i = 0; i < n; i++)
- {
- // no hidden files
- if (namelist[i]->d_name[0] != '.')
- {
- char fullname[1024];
- strcpy (fullname, dirname);
- strncat (fullname, "/", 1024);
- strncat (fullname, namelist[i]->d_name, 1024);
- if (pl_add_dir (fullname)) {
- pl_add_file (fullname);
- }
- }
- free (namelist[i]);
- }
- free (namelist);
- }
- return 0;
-#endif
-// }}}
}
int
@@ -514,6 +730,13 @@ pl_remove (playItem_t *it) {
else {
playlist_tail[PL_MAIN] = it->prev[PL_MAIN];
}
+ // totaltime
+ if (it->_duration > 0) {
+ pl_totaltime -= it->_duration;
+ if (pl_totaltime < 0) {
+ pl_totaltime = 0;
+ }
+ }
pl_item_free (it);
free (it);
return 0;
@@ -604,6 +827,11 @@ pl_insert_item (playItem_t *after, playItem_t *it) {
it->shufflerating = rand ();
it->played = 0;
+ // totaltime
+ if (it->_duration > 0) {
+ pl_totaltime += it->_duration;
+ }
+
return it;
}
@@ -614,7 +842,7 @@ pl_item_copy (playItem_t *out, playItem_t *it) {
out->tracknum = it->tracknum;
out->startsample = it->startsample;
out->endsample = it->endsample;
- out->duration = it->duration;
+ pl_set_item_duration (out, it->_duration);
out->shufflerating = it->shufflerating;
out->filetype = it->filetype;
out->replaygain_album_gain = it->replaygain_album_gain;
@@ -649,6 +877,8 @@ playItem_t *
pl_item_alloc (void) {
playItem_t *it = malloc (sizeof (playItem_t));
memset (it, 0, sizeof (playItem_t));
+ it->replaygain_album_peak = 1;
+ it->replaygain_track_peak = 1;
return it;
}
@@ -674,6 +904,8 @@ pl_prevsong (void) {
streamer_set_nextsong (-2, 1);
return 0;
}
+ int pl_order = conf_get_int ("playback.order", 0);
+ int pl_loop_mode = conf_get_int ("playback.loop", 0);
if (pl_order == 1) { // shuffle
if (!playlist_current_ptr) {
return pl_nextsong (1);
@@ -740,12 +972,15 @@ pl_prevsong (void) {
int
pl_nextsong (int reason) {
+ playItem_t *curr = streamer_get_streaming_track ();
if (!playlist_head[PL_MAIN]) {
streamer_set_nextsong (-2, 1);
return 0;
}
+ int pl_order = conf_get_int ("playback.order", 0);
+ int pl_loop_mode = conf_get_int ("playback.loop", 0);
if (pl_order == 1) { // shuffle
- if (!playlist_current_ptr) {
+ if (!curr) {
// find minimal notplayed
playItem_t *pmin = NULL; // notplayed minimum
playItem_t *i = NULL;
@@ -774,12 +1009,12 @@ pl_nextsong (int reason) {
else {
trace ("pl_next_song: reason=%d, loop=%d\n", reason, pl_loop_mode);
if (reason == 0 && pl_loop_mode == 2) { // song finished, loop mode is "loop 1 track"
- int r = pl_get_idx_of (playlist_current_ptr);
+ int r = pl_get_idx_of (curr);
streamer_set_nextsong (r, 1);
return 0;
}
// find minimal notplayed above current
- int rating = playlist_current_ptr->shufflerating;
+ int rating = curr->shufflerating;
playItem_t *pmin = NULL; // notplayed minimum
playItem_t *i = NULL;
for (playItem_t *i = playlist_head[PL_MAIN]; i; i = i->next[PL_MAIN]) {
@@ -808,13 +1043,13 @@ pl_nextsong (int reason) {
}
else if (pl_order == 0) { // linear
playItem_t *it = NULL;
- if (playlist_current_ptr) {
- if (reason == 0 && pl_loop_mode == 2) {
- int r = pl_get_idx_of (playlist_current_ptr);
+ if (curr) {
+ if (reason == 0 && pl_loop_mode == 2) { // loop same track
+ int r = pl_get_idx_of (curr);
streamer_set_nextsong (r, 1);
return 0;
}
- it = playlist_current_ptr->next[PL_MAIN];
+ it = curr->next[PL_MAIN];
}
if (!it) {
if (pl_loop_mode == 0) {
@@ -833,8 +1068,8 @@ pl_nextsong (int reason) {
return 0;
}
else if (pl_order == 2) { // random
- if (reason == 0 && pl_loop_mode == 2 && playlist_current_ptr) {
- int r = pl_get_idx_of (playlist_current_ptr);
+ if (reason == 0 && pl_loop_mode == 2 && curr) {
+ int r = pl_get_idx_of (curr);
streamer_set_nextsong (r, 1);
return 0;
}
@@ -891,8 +1126,6 @@ pl_add_meta (playItem_t *it, const char *key, const char *value) {
m = malloc (sizeof (metaInfo_t));
m->key = key;
m->value = strdup (value);
-// strncpy (m->value, value, META_FIELD_SIZE-1);
-// m->value[META_FIELD_SIZE-1] = 0;
m->next = it->meta;
it->meta = m;
}
@@ -908,50 +1141,6 @@ pl_format_item_display_name (playItem_t *it, char *str, int len) {
title = "Unknown title";
}
snprintf (str, len, "%s - %s", artist, title);
-#if 0
- // artist - title
- const char *track = pl_find_meta (it, "track");
- const char *artist = pl_find_meta (it, "artist");
- const char *album = pl_find_meta (it, "album");
- const char *title = pl_find_meta (it, "title");
- if (*track == '?' && *album == '?' && *artist != '?' && *title != '?') {
- snprintf (str, len, "%s - %s", artist, title);
- }
- else if (*artist != '?' && *track != '?' && *title != '?') {
- snprintf (str, len, "%s. %s - %s", track, artist, title);
- }
- else if (*artist == '?' && *track != '?' && *album != '?') {
- snprintf (str, len, "%s. %s", track, album);
- }
- else if (*artist != '?' && *track != '?' && *album != '?') {
- snprintf (str, len, "%s. %s - %s", track, artist, album);
- }
- else if (*artist != '?' && *title != '?') {
- snprintf (str, len, "%s - %s", artist, title);
- }
- else if (*artist != '?') {
- snprintf (str, len, "%s", artist);
- }
- else if (*title != '?') {
- snprintf (str, len, "%s", title);
- }
- else {
- // cut filename without path and extension
- char *pext = it->fname + strlen (it->fname) - 1;
- while (pext >= it->fname && *pext != '.') {
- pext--;
- }
- char *pname = pext;
- while (pname >= it->fname && *pname != '/') {
- pname--;
- }
- if (*pname == '/') {
- pname++;
- }
- strncpy (str, pname, pext-pname);
- str[pext-pname] = 0;
- }
-#endif
}
const char *
@@ -994,16 +1183,6 @@ pl_crop_selected (void) {
}
}
-void
-pl_set_order (int order) {
- pl_order = order;
-}
-
-void
-pl_set_loop_mode (int mode) {
- pl_loop_mode = mode;
-}
-
int
pl_save (const char *fname) {
const char magic[] = "DBPL";
@@ -1036,12 +1215,20 @@ pl_save (const char *fname) {
if (fwrite (it->fname, 1, l, fp) != l) {
goto save_fail;
}
- ll = strlen (it->decoder->id);
- if (fwrite (&ll, 1, 1, fp) != 1) {
- goto save_fail;
+ if (it->decoder) {
+ ll = strlen (it->decoder->id);
+ if (fwrite (&ll, 1, 1, fp) != 1) {
+ goto save_fail;
+ }
+ if (fwrite (it->decoder->id, 1, ll, fp) != ll) {
+ goto save_fail;
+ }
}
- if (fwrite (it->decoder->id, 1, ll, fp) != ll) {
- goto save_fail;
+ else {
+ ll = 0;
+ if (fwrite (&ll, 1, 1, fp) != 1) {
+ goto save_fail;
+ }
}
l = it->tracknum;
if (fwrite (&l, 1, 2, fp) != 2) {
@@ -1053,7 +1240,7 @@ pl_save (const char *fname) {
if (fwrite (&it->endsample, 1, 4, fp) != 4) {
goto save_fail;
}
- if (fwrite (&it->duration, 1, 4, fp) != 4) {
+ if (fwrite (&it->_duration, 1, 4, fp) != 4) {
goto save_fail;
}
uint8_t ft = it->filetype ? strlen (it->filetype) : 0;
@@ -1173,18 +1360,23 @@ pl_load (const char *fname) {
if (ll >= 20) {
goto load_fail;
}
- char decoder[20];
- if (fread (decoder, 1, ll, fp) != ll) {
- goto load_fail;
- }
- decoder[ll] = 0;
- for (int c = 0; decoders[c]; c++) {
- if (!strcmp (decoder, decoders[c]->id)) {
- it->decoder = decoders[c];
+ if (ll) {
+ char decoder[20];
+ if (fread (decoder, 1, ll, fp) != ll) {
+ goto load_fail;
+ }
+ decoder[ll] = 0;
+ for (int c = 0; decoders[c]; c++) {
+ if (!strcmp (decoder, decoders[c]->id)) {
+ it->decoder = decoders[c];
+ }
}
+// if (!it->decoder) {
+// goto load_fail;
+// }
}
- if (!it->decoder) {
- goto load_fail;
+ else {
+ it->decoder = NULL;
}
// tracknum
if (fread (&l, 1, 2, fp) != 2) {
@@ -1200,9 +1392,11 @@ pl_load (const char *fname) {
goto load_fail;
}
// duration
- if (fread (&it->duration, 1, 4, fp) != 4) {
+ float d;
+ if (fread (&d, 1, 4, fp) != 4) {
goto load_fail;
}
+ pl_set_item_duration (it, d);
// get const filetype string from decoder
uint8_t ft;
if (fread (&ft, 1, 1, fp) != 1) {
@@ -1214,11 +1408,16 @@ pl_load (const char *fname) {
goto load_fail;
}
ftype[ft] = 0;
- if (it->decoder && it->decoder->filetypes) {
- for (int i = 0; it->decoder->filetypes[i]; i++) {
- if (!strcasecmp (it->decoder->filetypes[i], ftype)) {
- it->filetype = it->decoder->filetypes[i];
- break;
+ if (!strcmp (ftype, "content")) {
+ it->filetype = "content";
+ }
+ else {
+ if (it->decoder && it->decoder->filetypes) {
+ for (int i = 0; it->decoder->filetypes[i]; i++) {
+ if (!strcasecmp (it->decoder->filetypes[i], ftype)) {
+ it->filetype = it->decoder->filetypes[i];
+ break;
+ }
}
}
}
@@ -1229,12 +1428,18 @@ pl_load (const char *fname) {
if (fread (&it->replaygain_album_peak, 1, 4, fp) != 4) {
goto load_fail;
}
+ if (it->replaygain_album_peak == 0) {
+ it->replaygain_album_peak = 1;
+ }
if (fread (&it->replaygain_track_gain, 1, 4, fp) != 4) {
goto load_fail;
}
if (fread (&it->replaygain_track_peak, 1, 4, fp) != 4) {
goto load_fail;
}
+ if (it->replaygain_track_peak == 0) {
+ it->replaygain_track_peak = 1;
+ }
// printf ("loading file %s\n", it->fname);
int16_t nm = 0;
if (fread (&nm, 1, 2, fp) != 2) {
@@ -1327,3 +1532,112 @@ pl_reshuffle (playItem_t **ppmin, playItem_t **ppmax) {
*ppmax = pmax;
}
}
+
+void
+pl_delete_all_meta (playItem_t *it) {
+ while (it->meta) {
+ metaInfo_t *m = it->meta;
+ it->meta = m->next;
+ free (m->value);
+ free (m);
+ }
+ it->meta = NULL;
+}
+
+void
+pl_set_item_duration (playItem_t *it, float duration) {
+ if (pl_get_idx_of (it) != -1) {
+ if (it->_duration > 0) {
+ pl_totaltime -= it->_duration;
+ }
+ if (duration > 0) {
+ pl_totaltime += duration;
+ }
+ if (pl_totaltime < 0) {
+ pl_totaltime = 0;
+ }
+ }
+ it->_duration = duration;
+}
+
+float
+pl_get_item_duration (playItem_t *it) {
+ return it->_duration;
+}
+
+int
+pl_format_title (playItem_t *it, char *s, int size, const char *fmt) {
+ int n = size-1;
+ while (*fmt && n) {
+ if (*fmt != '%') {
+ *s++ = *fmt;
+ n--;
+ }
+ else {
+ fmt++;
+ const char *meta = NULL;
+ if (*fmt == 0) {
+ break;
+ }
+ else if (*fmt == 'a') {
+ meta = "artist";
+ }
+ else if (*fmt == 't') {
+ meta = "title";
+ }
+ else if (*fmt == 'b') {
+ meta = "album";
+ }
+ else if (*fmt == 'n') {
+ meta = "track";
+ }
+ else if (*fmt == 'l') {
+ char dur[50];
+ if (it->_duration >= 0) {
+ int hourdur = it->_duration / (60 * 60);
+ int mindur = (it->_duration - hourdur * 60 * 60) / 60;
+ int secdur = it->_duration - mindur * 60;
+
+ if (hourdur) {
+ snprintf (dur, sizeof (dur), "%d:%02d:%02d", hourdur, mindur, secdur);
+ }
+ else {
+ snprintf (dur, sizeof (dur), "%d:%02d", mindur, secdur);
+ }
+ }
+ else {
+ strcpy (dur, "-:--");
+ }
+ const char *value = dur;
+ while (n > 0 && *value) {
+ *s++ = *value++;
+ n--;
+ }
+ }
+ else {
+ *s++ = *fmt;
+ n--;
+ }
+
+ if (meta) {
+ const char *value = pl_find_meta (it, meta);
+ if (!value) {
+ value = "?";
+ }
+ while (n > 0 && *value) {
+ *s++ = *value++;
+ n--;
+ }
+ }
+ }
+ fmt++;
+ }
+ *s = 0;
+
+ return size - n - 1;
+}
+
+void
+pl_sort (const char *meta) {
+}
+
diff --git a/playlist.h b/playlist.h
index eb50a7f4..e7498599 100644
--- a/playlist.h
+++ b/playlist.h
@@ -37,7 +37,6 @@ typedef struct playItem_s {
int tracknum; // used for stuff like sid, nsf, cue (will be ignored by most codecs)
int startsample;
int endsample;
- float duration; // in seconds
int shufflerating; // sort order for shuffle mode
float playtime; // total playtime
time_t started_timestamp; // result of calling time(NULL)
@@ -46,6 +45,8 @@ typedef struct playItem_s {
float replaygain_album_peak;
float replaygain_track_gain;
float replaygain_track_peak;
+ // private area, must not be visible to plugins
+ float _duration; // in seconds
struct playItem_s *next[PL_MAX_ITERATORS]; // next item in linked list
struct playItem_s *prev[PL_MAX_ITERATORS]; // prev item in linked list
struct metaInfo_s *meta; // linked list storing metainfo
@@ -58,6 +59,7 @@ extern playItem_t *playlist_tail[PL_MAX_ITERATORS]; // tail of linked list
extern playItem_t *playlist_current_ptr; // pointer to a real current playlist item (or NULL)
extern int pl_count;
+extern float pl_totaltime;
int
pl_add_dir (const char *dirname, int (*cb)(playItem_t *it, void *data), void *user_data);
@@ -133,6 +135,9 @@ pl_format_item_display_name (playItem_t *it, char *str, int len);
const char *
pl_find_meta (playItem_t *it, const char *key);
+void
+pl_delete_all_meta (playItem_t *it);
+
// returns index of 1st deleted item
int
pl_delete_selected (void);
@@ -140,12 +145,6 @@ pl_delete_selected (void);
void
pl_crop_selected (void);
-void
-pl_set_order (int order);
-
-void
-pl_set_loop_mode (int mode);
-
int
pl_save (const char *fname);
@@ -158,4 +157,16 @@ pl_select_all (void);
void
pl_reshuffle (playItem_t **ppmin, playItem_t **ppmax);
+// required to calculate total playtime
+void
+pl_set_item_duration (playItem_t *it, float duration);
+
+float
+pl_get_item_duration (playItem_t *it);
+
+// returns number of characters printed, not including trailing 0
+// [a]rtist, [t]itle, al[b]um, [l]ength, track[n]umber
+int
+pl_format_title (playItem_t *it, char *s, int size, const char *fmt);
+
#endif // __PLAYLIST_H
diff --git a/plugins.c b/plugins.c
index f72a0628..72f6df3c 100644
--- a/plugins.c
+++ b/plugins.c
@@ -29,7 +29,6 @@
#include "plugins.h"
#include "md5/md5.h"
#include "messagepump.h"
-#include "messages.h"
#include "threading.h"
#include "progress.h"
#include "playlist.h"
@@ -39,6 +38,7 @@
#include "common.h"
#include "conf.h"
#include "junklib.h"
+#include "vfs.h"
//#define DISABLE_VERSIONCHECK 1
@@ -62,6 +62,7 @@ static DB_functions_t deadbeef_api = {
.playback_get_pos = plug_playback_get_pos,
.playback_set_pos = plug_playback_set_pos,
.playback_get_samplerate = p_get_rate,
+ .playback_update_bitrate = streamer_update_bitrate,
.get_config_dir = plug_get_config_dir,
.quit = plug_quit,
// threading
@@ -81,9 +82,13 @@ static DB_functions_t deadbeef_api = {
.pl_item_free = (void (*)(DB_playItem_t *))pl_item_free,
.pl_item_copy = (void (*)(DB_playItem_t *, DB_playItem_t *))pl_item_copy,
.pl_insert_item = (DB_playItem_t *(*) (DB_playItem_t *after, DB_playItem_t *it))pl_insert_item,
+ .pl_get_idx_of = (int (*) (DB_playItem_t *it))pl_get_idx_of,
+ .pl_set_item_duration = (void (*) (DB_playItem_t *it, float duration))pl_set_item_duration,
+ .pl_get_item_duration = (float (*) (DB_playItem_t *it))pl_get_item_duration,
// metainfo
.pl_add_meta = (void (*) (DB_playItem_t *, const char *, const char *))pl_add_meta,
.pl_find_meta = (const char *(*) (DB_playItem_t *, const char *))pl_find_meta,
+ .pl_delete_all_meta = (void (*) (DB_playItem_t *it))pl_delete_all_meta,
// cuesheet support
.pl_insert_cue_from_buffer = (DB_playItem_t *(*) (DB_playItem_t *after, const char *fname, const uint8_t *buffer, int buffersize, struct DB_decoder_s *decoder, const char *ftype, int numsamples, int samplerate))pl_insert_cue_from_buffer,
.pl_insert_cue = (DB_playItem_t *(*)(DB_playItem_t *, const char *, struct DB_decoder_s *, const char *ftype, int numsamples, int samplerate))pl_insert_cue,
@@ -93,12 +98,36 @@ static DB_functions_t deadbeef_api = {
.volume_set_amp = plug_volume_set_amp,
.volume_get_amp = volume_get_amp,
// junk reading
- .junk_read_id3v1 = (int (*)(DB_playItem_t *it, FILE *fp))junk_read_id3v1,
- .junk_read_id3v2 = (int (*)(DB_playItem_t *it, FILE *fp))junk_read_id3v2,
- .junk_read_ape = (int (*)(DB_playItem_t *it, FILE *fp))junk_read_ape,
+ .junk_read_id3v1 = (int (*)(DB_playItem_t *it, DB_FILE *fp))junk_read_id3v1,
+ .junk_read_id3v2 = (int (*)(DB_playItem_t *it, DB_FILE *fp))junk_read_id3v2,
+ .junk_read_ape = (int (*)(DB_playItem_t *it, DB_FILE *fp))junk_read_ape,
.junk_get_leading_size = junk_get_leading_size,
+ // vfs
+ .fopen = vfs_fopen,
+ .fclose = vfs_fclose,
+ .fread = vfs_fread,
+ .fseek = vfs_fseek,
+ .ftell = vfs_ftell,
+ .rewind = vfs_rewind,
+ .fgetlength = vfs_fgetlength,
+ .fget_content_type = vfs_get_content_type,
+ .fget_content_name = vfs_get_content_name,
+ .fget_content_genre = vfs_get_content_genre,
+ .fstop = vfs_fstop,
+ // message passing
+ .sendmessage = messagepump_push,
+ // configuration access
+ .conf_get_str = conf_get_str,
+ .conf_get_float = conf_get_float,
+ .conf_get_int = conf_get_int,
+ .conf_set_str = conf_set_str,
+ .conf_find = conf_find,
+ .gui_lock = plug_gui_lock,
+ .gui_unlock = plug_gui_unlock,
};
+DB_functions_t *deadbeef = &deadbeef_api;
+
const char *
plug_get_config_dir (void) {
return dbconfdir;
@@ -119,8 +148,24 @@ plug_volume_set_amp (float amp) {
volumebar_notify_changed ();
}
-#define MAX_DECODERS 50
-DB_decoder_t *g_decoders[MAX_DECODERS+1];
+#define MAX_PLUGINS 100
+DB_plugin_t *g_plugins[MAX_PLUGINS+1];
+
+#define MAX_DECODER_PLUGINS 50
+DB_decoder_t *g_decoder_plugins[MAX_DECODER_PLUGINS+1];
+
+#define MAX_VFS_PLUGINS 10
+DB_vfs_t *g_vfs_plugins[MAX_VFS_PLUGINS+1];
+
+void
+plug_gui_lock (void) {
+ gdk_threads_enter ();
+}
+
+void
+plug_gui_unlock (void) {
+ gdk_threads_leave ();
+}
void
plug_md5 (uint8_t sig[16], const char *in, int len) {
@@ -215,18 +260,18 @@ plug_playback_random (void) {
float
plug_playback_get_pos (void) {
- if (str_playing_song.duration <= 0) {
+ if (str_playing_song._duration <= 0) {
return 0;
}
- return streamer_get_playpos () * 100 / str_playing_song.duration;
+ return streamer_get_playpos () * 100 / str_playing_song._duration;
}
void
plug_playback_set_pos (float pos) {
- if (str_playing_song.duration <= 0) {
+ if (str_playing_song._duration <= 0) {
return;
}
- float t = pos * str_playing_song.duration / 100.f;
+ float t = pos * str_playing_song._duration / 100.f;
streamer_set_seek (t);
}
@@ -238,7 +283,7 @@ plug_quit (void) {
/////// non-api functions (plugin support)
void
-plug_trigger_event (int ev) {
+plug_trigger_event (int ev, uintptr_t param) {
mutex_lock (mutex);
DB_event_t *event;
switch (ev) {
@@ -250,6 +295,13 @@ plug_trigger_event (int ev) {
event = DB_EVENT (pev);
}
break;
+ case DB_EV_TRACKDELETED:
+ {
+ DB_event_song_t *pev = malloc (sizeof (DB_event_song_t));
+ pev->song = DB_PLAYITEM (param);
+ event = DB_EVENT (pev);
+ }
+ break;
default:
event = malloc (sizeof (DB_event_t));
}
@@ -301,9 +353,8 @@ plug_load_all (void) {
#if DISABLE_VERSIONCHECK
fprintf (stderr, "\033[0;31mDISABLE_VERSIONCHECK=1! do not distribute!\033[0;m\n");
#endif
+ const char *conf_blacklist_plugins = conf_get_str ("blacklist_plugins", "");
mutex = mutex_create ();
-// char dirname[1024];
-// snprintf (dirname, 1024, "%s/lib/deadbeef", PREFIX);
const char *dirname = LIBDIR "/deadbeef";
struct dirent **namelist = NULL;
int n = scandir (dirname, &namelist, NULL, alphasort);
@@ -391,19 +442,30 @@ plug_load_all (void) {
#include "moduleconf.h"
#undef PLUG
- // find all decoders, and put in g_decoders list
+ // categorize plugins
+ int numplugins = 0;
int numdecoders = 0;
+ int numvfs = 0;
for (plugin_t *plug = plugins; plug; plug = plug->next) {
+ g_plugins[numplugins++] = plug->plugin;
if (plug->plugin->type == DB_PLUGIN_DECODER) {
- printf ("found decoder plugin %s\n", plug->plugin->name);
- if (numdecoders >= MAX_DECODERS) {
+ fprintf (stderr, "found decoder plugin %s\n", plug->plugin->name);
+ if (numdecoders >= MAX_DECODER_PLUGINS) {
break;
}
- g_decoders[numdecoders] = (DB_decoder_t *)plug->plugin;
- numdecoders++;
+ g_decoder_plugins[numdecoders++] = (DB_decoder_t *)plug->plugin;
+ }
+ else if (plug->plugin->type == DB_PLUGIN_VFS) {
+ fprintf (stderr, "found vfs plugin %s\n", plug->plugin->name);
+ if (numvfs >= MAX_VFS_PLUGINS) {
+ break;
+ }
+ g_vfs_plugins[numvfs++] = (DB_vfs_t *)plug->plugin;
}
}
- g_decoders[numdecoders] = NULL;
+ g_plugins[numplugins] = NULL;
+ g_decoder_plugins[numdecoders] = NULL;
+ g_vfs_plugins[numvfs] = NULL;
}
void
@@ -423,6 +485,15 @@ plug_unload_all (void) {
struct DB_decoder_s **
plug_get_decoder_list (void) {
- return g_decoders;
+ return g_decoder_plugins;
+}
+
+struct DB_vfs_s **
+plug_get_vfs_list (void) {
+ return g_vfs_plugins;
}
+struct DB_plugin_s **
+plug_get_list (void) {
+ return g_plugins;
+}
diff --git a/plugins.h b/plugins.h
index ca0c5668..671da5f8 100644
--- a/plugins.h
+++ b/plugins.h
@@ -20,6 +20,8 @@
#define __PLUGINS_H
#include "deadbeef.h"
+extern DB_functions_t *deadbeef;
+
void
plug_load_all (void);
@@ -33,7 +35,7 @@ void
plug_ev_unsubscribe (DB_plugin_t *plugin, int ev, DB_callback_t callback, uintptr_t data);
void
-plug_trigger_event (int ev);
+plug_trigger_event (int ev, uintptr_t param);
void
plug_md5 (uint8_t sig[16], const char *in, int len);
@@ -68,9 +70,15 @@ plug_playback_get_pos (void);
void
plug_playback_set_pos (float pos);
+struct DB_plugin_s **
+plug_get_list (void);
+
struct DB_decoder_s **
plug_get_decoder_list (void);
+struct DB_vfs_s **
+plug_get_vfs_list (void);
+
void
plug_volume_set_db (float db);
@@ -80,4 +88,10 @@ plug_volume_set_amp (float amp);
const char *
plug_get_config_dir (void);
+void
+plug_gui_lock (void);
+
+void
+plug_gui_unlock (void);
+
#endif // __PLUGINS_H
diff --git a/plugins/cdda/Makefile.am b/plugins/cdda/Makefile.am
new file mode 100644
index 00000000..0f5509c1
--- /dev/null
+++ b/plugins/cdda/Makefile.am
@@ -0,0 +1,11 @@
+if HAVE_CDIO
+if HAVE_CDDB
+cddadir = $(libdir)/$(PACKAGE)
+pkglib_LTLIBRARIES = cdda.la
+cdda_la_SOURCES = cdda.c
+cdda_la_LDFLAGS = -module
+
+cdda_la_LIBADD = $(LDADD) $(CDDA_LIBS)
+AM_CFLAGS = $(CFLAGS) -std=c99
+endif
+endif
diff --git a/plugins/cdda/cdda.c b/plugins/cdda/cdda.c
new file mode 100644
index 00000000..4effd077
--- /dev/null
+++ b/plugins/cdda/cdda.c
@@ -0,0 +1,455 @@
+/*
+ CD audio plugin for DeaDBeeF
+ Copyright (C) 2009 Viktor Semykin <thesame.ml@gmail.com>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/types.h>
+
+#include <cdio/cdio.h>
+#include <cddb/cddb.h>
+
+#include "../../deadbeef.h"
+
+#define trace(...) { fprintf (stderr, __VA_ARGS__); }
+//#define trace(fmt,...)
+
+#define SECTORSIZE CDIO_CD_FRAMESIZE_RAW //2352
+#define SAMPLESIZE 4 //bytes
+#define BUFSIZE (CDIO_CD_FRAMESIZE_RAW * 2)
+
+static DB_decoder_t plugin;
+static DB_functions_t *deadbeef;
+
+static CdIo_t* cdio = NULL;
+static lsn_t first_sector;
+static unsigned int sector_count;
+static uint8_t tail [SECTORSIZE];
+static unsigned int tail_len;
+static int current_sector;
+static unsigned int current_sample = 0;
+static uintptr_t mutex;
+
+static int use_cddb = 1;
+static char server[1024] = "freedb.org";
+static int port = 888;
+static int proto_cddb = 1;
+
+struct cddb_thread_params
+{
+ DB_playItem_t *items[100];
+ CdIo_t *cdio;
+};
+
+static inline int
+min (int a, int b) {
+ return a < b ? a : b;
+}
+
+static char*
+trim (char* s)
+{
+ char *h, *t;
+
+ for ( h = s; *h == ' ' || *h == '\t'; h++ );
+ for ( t = s + strlen(s); *t == ' ' || *t == '\t'; *t = 0, t-- );
+ return h;
+}
+
+static int
+read_config ()
+{
+ use_cddb = deadbeef->conf_get_int ("cdda.freedb.enable", 1);
+ strncpy (server, deadbeef->conf_get_str ("cdda.freedb.host", "freedb.org"), sizeof (server)-1);
+ port = deadbeef->conf_get_int ("cdda.freedb.port", 888);
+ proto_cddb = deadbeef->conf_get_int ("cdda.protocol", 1); // 1 is cddb, 0 is http
+}
+
+static int
+cda_init (DB_playItem_t *it) {
+// trace ("CDA: initing %s\n", it->fname);
+
+ size_t l = strlen (it->fname);
+ char location[l+1];
+ memcpy (location, it->fname, l+1);
+
+ char *nr = strchr (location, '#');
+ if (nr) {
+ *nr = 0; nr++;
+ }
+ else {
+ trace ("malformed cdaudio track filename\n");
+ return -1;
+ }
+ int track_nr = atoi (nr);
+ char *fname = (*location) ? location : NULL; //NULL if empty string; means pysical CD drive
+
+ cdio = cdio_open (fname, DRIVER_UNKNOWN);
+ if (!cdio)
+ {
+ trace ("Could not open CD\n");
+ return -1;
+ }
+
+ if (TRACK_FORMAT_AUDIO != cdio_get_track_format (cdio, track_nr))
+ {
+ trace ("Not an audio track (%d)\n", track_nr);
+ return -1;
+ }
+
+ plugin.info.bps = 16,
+ plugin.info.channels = 2,
+ plugin.info.samplerate = 44100,
+ plugin.info.readpos = 0;
+
+ first_sector = cdio_get_track_lsn (cdio, track_nr);
+ sector_count = cdio_get_track_sec_count (cdio, track_nr);
+ current_sector = first_sector;
+ tail_len = 0;
+ current_sample = 0;
+}
+
+int
+cda_read_int16 (char *bytes, int size) {
+ int initsize = size;
+ int extrasize = 0;
+
+ if (tail_len > 0)
+ {
+ if (tail_len >= size)
+ {
+// trace ("Easy case\n");
+ memcpy (bytes, tail, size);
+ tail_len -= size;
+ memmove (tail, tail+size, tail_len);
+ return size;
+ }
+// trace ("Prepending with tail of %d bytes\n", tail_len);
+ extrasize = tail_len;
+ memcpy (bytes, tail, tail_len);
+ bytes += tail_len;
+ size -= tail_len;
+ tail_len = 0;
+ }
+
+ int sectors_to_read = size / SECTORSIZE + 1;
+ int end = 0;
+
+ if (current_sector + sectors_to_read > first_sector + sector_count) //we reached end of track
+ {
+ end = 1;
+ sectors_to_read = first_sector + sector_count - current_sector;
+// trace ("We reached end of track\n");
+ }
+
+ int bufsize = sectors_to_read * SECTORSIZE;
+
+ tail_len = end ? 0 : bufsize - size;
+
+ char *buf = alloca (bufsize);
+
+ driver_return_code_t ret = cdio_read_audio_sectors (cdio, buf, current_sector, sectors_to_read);
+ if (ret != DRIVER_OP_SUCCESS)
+ return 0;
+ current_sector += sectors_to_read;
+
+ int retsize = end ? bufsize : size;
+
+ memcpy (bytes, buf, retsize);
+ if (!end)
+ memcpy (tail, buf+retsize, tail_len);
+
+ retsize += extrasize;
+// trace ("requested: %d; tail_len: %d; size: %d; sectors_to_read: %d; return: %d\n", initsize, tail_len, size, sectors_to_read, retsize);
+ current_sample += retsize / SAMPLESIZE;
+ plugin.info.readpos = current_sample / 44100;
+ return retsize;
+}
+
+static void
+cda_free ()
+{
+ if (cdio)
+ {
+ cdio_destroy (cdio);
+ cdio = NULL;
+ }
+}
+
+static int
+cda_seek_sample (int sample)
+{
+ int sector = sample / (SECTORSIZE / SAMPLESIZE) + first_sector;
+ int offset = (sample % (SECTORSIZE / SAMPLESIZE)) * SAMPLESIZE; //in bytes
+ char buf [SECTORSIZE];
+
+ driver_return_code_t ret = cdio_read_audio_sector (cdio, buf, sector);
+ if (ret != DRIVER_OP_SUCCESS)
+ return -1;
+ memcpy (tail, buf + offset, SECTORSIZE - offset );
+ current_sector = sector;
+ current_sample = sample;
+ plugin.info.readpos = current_sample / 44100;
+ return 0;
+}
+
+static int
+cda_seek (float sec)
+{
+ return cda_seek_sample (sec * 44100);
+}
+
+cddb_disc_t*
+resolve_disc (CdIo_t *cdio)
+{
+ track_t first_track = cdio_get_first_track_num (cdio);
+ track_t tracks = cdio_get_num_tracks (cdio);
+ track_t i;
+ cddb_track_t *track;
+
+ cddb_disc_t *disc = cddb_disc_new();
+
+ cddb_disc_set_length (disc, cdio_get_track_lba (cdio, CDIO_CDROM_LEADOUT_TRACK) / CDIO_CD_FRAMES_PER_SEC);
+
+ for (i = 0; i < tracks; i++)
+ {
+ lsn_t offset = cdio_get_track_lba (cdio, i+first_track);
+ track = cddb_track_new();
+ cddb_track_set_frame_offset (track, offset);
+ cddb_disc_add_track (disc, track);
+ }
+ cdio_destroy (cdio);
+
+ cddb_conn_t *conn = NULL;
+
+ conn = cddb_new();
+
+ cddb_set_server_name (conn, server);
+ cddb_set_server_port (conn, port);
+
+ if (!proto_cddb)
+ {
+ cddb_http_enable (conn);
+ if (deadbeef->conf_get_int ("network.proxy", 0))
+ {
+ cddb_set_server_port(conn, deadbeef->conf_get_int ("network.proxy.port", 8080));
+ cddb_set_server_name(conn, deadbeef->conf_get_str ("network.proxy.address", ""));
+ }
+ }
+
+ int matches = cddb_query (conn, disc);
+ if (matches == -1)
+ {
+ cddb_disc_destroy (disc);
+ cddb_destroy (conn);
+ return NULL;
+ }
+ cddb_read (conn, disc);
+ cddb_destroy (conn);
+ return disc;
+}
+
+static DB_playItem_t *
+insert_single_track (CdIo_t* cdio, DB_playItem_t *after, const char* file, int track_nr)
+{
+ char tmp[file ? strlen (file) + 20 : 20];
+ if (file)
+ snprintf (tmp, sizeof (tmp), "%s#%d.cda", file, track_nr);
+ else
+ snprintf (tmp, sizeof (tmp), "#%d.cda", track_nr);
+
+ if (TRACK_FORMAT_AUDIO != cdio_get_track_format (cdio, track_nr))
+ {
+ trace ("Not an audio track (%d)\n", track_nr);
+ return NULL;
+ }
+
+ sector_count = cdio_get_track_sec_count (cdio, track_nr);
+
+ DB_playItem_t *it = deadbeef->pl_item_alloc ();
+ it->decoder = &plugin;
+ it->fname = strdup (tmp);
+ it->filetype = "cdda";
+ deadbeef->pl_set_item_duration (it, (float)sector_count / 75.0);
+
+ snprintf (tmp, sizeof (tmp), "CD Track %02d", track_nr);
+ deadbeef->pl_add_meta (it, "title", tmp);
+ snprintf (tmp, sizeof (tmp), "%02d", track_nr);
+ deadbeef->pl_add_meta (it, "track", tmp);
+
+ after = deadbeef->pl_insert_item (after, it);
+
+ return after;
+}
+
+static void
+cddb_thread (uintptr_t items_i)
+{
+ struct cddb_thread_params *params = (struct cddb_thread_params*)items_i;
+ DB_playItem_t **items = params->items;
+ DB_playItem_t *item;
+
+ trace ("calling resolve_disc\n");
+ deadbeef->mutex_lock (mutex);
+ cddb_disc_t* disc = resolve_disc (params->cdio);
+ deadbeef->mutex_unlock (mutex);
+ if (!disc)
+ {
+ trace ("disc not resolved\n");
+ free (params);
+ return;
+ }
+ trace ("disc resolved\n");
+
+ deadbeef->mutex_lock (mutex);
+ const char *disc_title = cddb_disc_get_title (disc);
+ const char *artist = cddb_disc_get_artist (disc);
+ trace ("disc_title=%s, disk_artist=%s\n", disc_title, artist);
+ cddb_track_t *track;
+ int i;
+
+ // FIXME: playlist must be locked before doing that
+ int trk = 1;
+ for (i = 0, track = cddb_disc_get_track_first (disc); items[i]; trk++, ++i, track = cddb_disc_get_track_next (disc))
+ {
+ // FIXME: problem will happen here if item(s) were deleted from playlist, and new items were added in their places
+ // possible solutions: catch EV_TRACKDELETED and mark item(s) in every thread as NULL
+ int idx = deadbeef->pl_get_idx_of (items[i]);
+ trace ("track %d, artist=%s, album=%s, title=%s\n", i, artist, disc_title, cddb_track_get_title (track));
+ if (idx == -1)
+ continue;
+
+ deadbeef->pl_delete_all_meta (items[i]);
+ deadbeef->pl_add_meta (items[i], "artist", artist);
+ deadbeef->pl_add_meta (items[i], "album", disc_title);
+ deadbeef->pl_add_meta (items[i], "title", cddb_track_get_title (track));
+ char tmp[5];
+ snprintf (tmp, sizeof (tmp), "%02d", trk);
+ deadbeef->pl_add_meta (items[i], "track", tmp);
+ deadbeef->sendmessage (M_TRACKCHANGED, 0, idx, 0);
+ }
+ cddb_disc_destroy (disc);
+ deadbeef->mutex_unlock (mutex);
+ free (params);
+}
+
+static DB_playItem_t *
+cda_insert (DB_playItem_t *after, const char *fname) {
+// trace ("CDA insert: %s\n", fname);
+
+ int all = 0;
+ int track_nr;
+ DB_playItem_t *res;
+ CdIo_t *cdio; //we need its local inst
+
+ const char* shortname = strrchr (fname, '/');
+ if (shortname) {
+ shortname++;
+ }
+ else {
+ shortname = fname;
+ }
+ const char *ext = strrchr (shortname, '.') + 1;
+ int is_image = ext && (0 == strcmp (ext, "nrg"));
+
+ if (0 == strcmp (ext, "cda")) {
+ cdio = cdio_open (NULL, DRIVER_UNKNOWN);
+ }
+ else if (is_image) {
+ cdio = cdio_open (fname, DRIVER_NRG);
+ }
+
+ if (!cdio) {
+ return NULL;
+ }
+
+ if (0 == strcasecmp (shortname, "all.cda") || is_image)
+ {
+ track_t first_track = cdio_get_first_track_num (cdio);
+ if (first_track == 0xff) {
+ trace ("cdda: no medium found\n");
+ cdio_destroy (cdio);
+ return NULL;
+ }
+ track_t tracks = cdio_get_num_tracks (cdio);
+ track_t i;
+ res = after;
+ struct cddb_thread_params *p = malloc (sizeof (struct cddb_thread_params));
+ memset (p, 0, sizeof (struct cddb_thread_params));
+ p->cdio = cdio;
+ for (i = 0; i < tracks; i++)
+ {
+ res = insert_single_track (cdio, res, is_image ? fname : NULL, i+first_track);
+ p->items[i] = res;
+ }
+ trace ("cdda: querying freedb...\n");
+ deadbeef->thread_start (cddb_thread, (uintptr_t)p); //will destroy cdio
+ }
+ else
+ {
+ track_nr = atoi (shortname);
+ res = insert_single_track (cdio, after, NULL, track_nr);
+ cdio_destroy (cdio);
+ }
+ return res;
+}
+
+static int
+cda_start (void) {
+ mutex = deadbeef->mutex_create ();
+}
+
+static int
+cda_stop (void) {
+ deadbeef->mutex_free (mutex);
+}
+
+static const char *exts[] = { "cda", "nrg", NULL };
+static const char *filetypes[] = { "cdda", NULL };
+
+// define plugin interface
+static DB_decoder_t plugin = {
+ DB_PLUGIN_SET_API_VERSION
+ .plugin.version_major = 0,
+ .plugin.version_minor = 1,
+ .plugin.type = DB_PLUGIN_DECODER,
+ .plugin.name = "Audio CD player",
+ .plugin.descr = "using libcdio, includes .nrg image support",
+ .plugin.author = "Viktor Semykin",
+ .plugin.email = "thesame.ml@gmail.com",
+ .plugin.website = "http://deadbeef.sf.net",
+ .plugin.start = cda_start,
+ .plugin.stop = cda_stop,
+ .init = cda_init,
+ .free = cda_free,
+ .read_int16 = cda_read_int16,
+ .seek = cda_seek,
+ .seek_sample = cda_seek_sample,
+ .insert = cda_insert,
+ .exts = exts,
+ .id = "cda",
+ .filetypes = filetypes,
+};
+
+DB_plugin_t *
+cdda_load (DB_functions_t *api) {
+ deadbeef = api;
+ read_config();
+ return DB_PLUGIN (&plugin);
+}
+
diff --git a/plugins/faad2/Makefile.am b/plugins/faad2/Makefile.am
new file mode 100644
index 00000000..963e4c7d
--- /dev/null
+++ b/plugins/faad2/Makefile.am
@@ -0,0 +1,9 @@
+if HAVE_FAAD
+faad2dir = $(libdir)/$(PACKAGE)
+pkglib_LTLIBRARIES = faad2.la
+faad2_la_SOURCES = faad2.c
+faad2_la_LDFLAGS = -module
+
+faad2_la_LIBADD = $(LDADD) $(FAAD2_LIBS)
+AM_CFLAGS = $(CFLAGS) -std=c99
+endif
diff --git a/plugins/faad2/faad2.c b/plugins/faad2/faad2.c
new file mode 100644
index 00000000..46a144ce
--- /dev/null
+++ b/plugins/faad2/faad2.c
@@ -0,0 +1,270 @@
+/*
+ DeaDBeeF - ultimate music player for GNU/Linux systems with X11
+ Copyright (C) 2009 Alexey Yakovenko
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include <string.h>
+#include <stdio.h>
+#include <neaacdec.h>
+#include <mp4ff.h>
+#include <stdlib.h>
+#include "../../deadbeef.h"
+
+#define min(x,y) ((x)<(y)?(x):(y))
+#define max(x,y) ((x)>(y)?(x):(y))
+
+#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+//#define trace(fmt,...)
+
+static DB_decoder_t plugin;
+static DB_functions_t *deadbeef;
+
+static int
+aac_init (DB_playItem_t *it) {
+ return 0;
+}
+
+static void
+aac_free (void) {
+}
+
+static int
+aac_read_int16 (char *bytes, int size) {
+ return 0;
+}
+
+static int
+aac_seek_sample (int sample) {
+ return 0;
+}
+
+static int
+aac_seek (float t) {
+ return aac_seek_sample (t * plugin.info.samplerate);
+}
+
+static uint32_t
+aac_fs_read (void *user_data, void *buffer, uint32_t length) {
+ trace ("aac_fs_read\n");
+ DB_FILE *fp = (DB_FILE *)user_data;
+ return deadbeef->fread (buffer, 1, length, fp);
+}
+
+static uint32_t
+aac_fs_seek (void *user_data, uint64_t position) {
+ trace ("aac_fs_seek\n");
+ DB_FILE *fp = (DB_FILE *)user_data;
+ return deadbeef->fseek (fp, position, SEEK_SET);
+}
+
+/*
+ * These routines are derived from MPlayer.
+ */
+
+/// \param srate (out) sample rate
+/// \param num (out) number of audio frames in this ADTS frame
+/// \return size of the ADTS frame in bytes
+/// aac_parse_frames needs a buffer at least 8 bytes long
+static int
+aac_parse_frame(uint8_t *buf, int *srate, int *num)
+{
+ int i = 0, sr, fl = 0;
+ static int srates[] = {96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 0, 0, 0};
+
+ if((buf[i] != 0xFF) || ((buf[i+1] & 0xF6) != 0xF0))
+ return 0;
+
+ /* We currently have no use for the id below.
+ id = (buf[i+1] >> 3) & 0x01; //id=1 mpeg2, 0: mpeg4
+ */
+ sr = (buf[i+2] >> 2) & 0x0F;
+ if(sr > 11)
+ return 0;
+ *srate = srates[sr];
+
+ fl = ((buf[i+3] & 0x03) << 11) | (buf[i+4] << 3) | ((buf[i+5] >> 5) & 0x07);
+ *num = (buf[i+6] & 0x02) + 1;
+
+ return fl;
+}
+
+static int
+parse_aac_stream(DB_FILE *stream)
+{
+ int cnt = 0, len, srate, num;
+ int8_t c;
+ off_t init, probed;
+ static uint8_t buf[8];
+
+ init = probed = deadbeef->ftell(stream);
+ while(probed-init <= 32768 && cnt < 8)
+ {
+ c = 0;
+ while(probed-init <= 32768 && c != 0xFF)
+ {
+ if (deadbeef->fread (&c, 1, 1, stream) != 1) {
+ return 1;
+ }
+ if(c < 0)
+ return 0;
+ probed = deadbeef->ftell(stream);
+ }
+ buf[0] = 0xFF;
+ if(deadbeef->fread(&(buf[1]), 1, 7, stream) < 7)
+ return 0;
+
+ len = aac_parse_frame(buf, &srate, &num);
+ if(len > 0)
+ {
+ cnt++;
+ deadbeef->fseek(stream, len - 8, SEEK_CUR);
+ }
+ probed = deadbeef->ftell(stream);
+ }
+
+ if(cnt < 8)
+ return 0;
+
+ return 1;
+}
+
+static DB_playItem_t *
+aac_insert (DB_playItem_t *after, const char *fname) {
+ return NULL; // to avoid crashes
+ trace ("adding %s\n", fname);
+ DB_FILE *fp = deadbeef->fopen (fname);
+ if (!fp) {
+ trace ("not found\n");
+ return NULL;
+ }
+ if (fp->vfs->streaming) {
+ trace ("no streaming aac yet (%s)\n", fname);
+ deadbeef->fclose (fp);
+ return NULL;
+ }
+
+ // try mp4
+
+ mp4ff_callback_t cb = {
+ .read = aac_fs_read,
+ .write = NULL,
+ .seek = aac_fs_seek,
+ .truncate = NULL,
+ .user_data = fp
+ };
+
+ float duration = -1;
+ const char *ftype = NULL;
+ mp4ff_t *mp4 = mp4ff_open_read (&cb);
+ if (!mp4) {
+ trace ("not an mp4 file\n");
+ deadbeef->fclose (fp);
+ return NULL;
+ }
+ int ntracks = mp4ff_total_tracks (mp4);
+ trace ("ntracks=%d\n", ntracks);
+ int i = -1;
+ for (i = 0; i < ntracks; i++) {
+ unsigned char* buff = 0;
+ unsigned int buff_size = 0;
+ mp4AudioSpecificConfig mp4ASC;
+ mp4ff_get_decoder_config(mp4, i, &buff, &buff_size);
+ if(buff){
+ int rc = AudioSpecificConfig(buff, buff_size, &mp4ASC);
+ free(buff);
+ if(rc < 0)
+ continue;
+ break;
+ }
+ }
+ if (i != ntracks)
+ {
+ trace ("mp4 track: %d\n", i);
+ int samplerate = mp4ff_get_sample_rate (mp4, i);
+ duration = mp4ff_get_track_duration (mp4, i) / (float)samplerate;
+ ftype = "mp4";
+ }
+ else {
+ trace ("mp4 track not found\n");
+ }
+
+ mp4ff_close (mp4);
+
+ if (duration < 0) {
+ trace ("trying raw aac\n");
+ // not an mp4, try raw aac
+ deadbeef->rewind (fp);
+ if (parse_aac_stream (fp)) {
+ trace ("not aac stream either\n");
+ deadbeef->fclose (fp);
+ return NULL;
+ }
+ else {
+ trace ("yes, it's aac indeed\n");
+ ftype = "aac";
+ }
+ }
+
+ DB_playItem_t *it = deadbeef->pl_item_alloc ();
+ it->decoder = &plugin;
+ it->fname = strdup (fname);
+ it->filetype = ftype;
+ deadbeef->pl_set_item_duration (it, duration);
+
+ // read tags
+ if (ftype == "aac") {
+ int apeerr = deadbeef->junk_read_ape (it, fp);
+ int v2err = deadbeef->junk_read_id3v2 (it, fp);
+ int v1err = deadbeef->junk_read_id3v1 (it, fp);
+ }
+ deadbeef->pl_add_meta (it, "title", NULL);
+
+ deadbeef->fclose (fp);
+
+ return deadbeef->pl_insert_item (after, it);
+}
+
+static const char * exts[] = { "aac", "mp4", "m4a", NULL };
+static const char *filetypes[] = { "aac", "mp4", NULL };
+
+// define plugin interface
+static DB_decoder_t plugin = {
+ DB_PLUGIN_SET_API_VERSION
+ .plugin.version_major = 0,
+ .plugin.version_minor = 1,
+ .plugin.type = DB_PLUGIN_DECODER,
+ .plugin.name = "faad2 AAC decoder",
+ .plugin.descr = "aac/mp4 player",
+ .plugin.author = "Alexey Yakovenko",
+ .plugin.email = "waker@users.sourceforge.net",
+ .plugin.website = "http://deadbeef.sf.net",
+ .init = aac_init,
+ .free = aac_free,
+ .read_int16 = aac_read_int16,
+ .seek = aac_seek,
+ .seek_sample = aac_seek_sample,
+ .insert = aac_insert,
+ .exts = exts,
+ .id = "aac",
+ .filetypes = filetypes
+};
+
+DB_plugin_t *
+faad2_load (DB_functions_t *api) {
+ deadbeef = api;
+ return DB_PLUGIN (&plugin);
+}
diff --git a/plugins/ffap/ffap.c b/plugins/ffap/ffap.c
index 68899578..993863ed 100644
--- a/plugins/ffap/ffap.c
+++ b/plugins/ffap/ffap.c
@@ -268,15 +268,14 @@ typedef struct APEContext {
} APEContext;
APEContext ape_ctx;
-FILE *fp;
inline static int
-read_uint16(FILE *fp, uint16_t* x)
+read_uint16(DB_FILE *fp, uint16_t* x)
{
unsigned char tmp[2];
int n;
- n = fread(tmp, 1, 2, fp);
+ n = deadbeef->fread(tmp, 1, 2, fp);
if (n != 2)
return -1;
@@ -288,18 +287,18 @@ read_uint16(FILE *fp, uint16_t* x)
inline static int
-read_int16(FILE *fp, int16_t* x)
+read_int16(DB_FILE *fp, int16_t* x)
{
return read_uint16(fp, (uint16_t*)x);
}
inline static int
-read_uint32(FILE *fp, uint32_t* x)
+read_uint32(DB_FILE *fp, uint32_t* x)
{
unsigned char tmp[4];
int n;
- n = fread(tmp, 1, 4, fp);
+ n = deadbeef->fread(tmp, 1, 4, fp);
if (n != 4)
return -1;
@@ -365,7 +364,7 @@ static void ape_dumpinfo(APEContext * ape_ctx)
}
static int
-ape_read_header(FILE *fp, APEContext *ape)
+ape_read_header(DB_FILE *fp, APEContext *ape)
{
int i;
int total_blocks;
@@ -373,7 +372,7 @@ ape_read_header(FILE *fp, APEContext *ape)
/* TODO: Skip any leading junk such as id3v2 tags */
ape->junklength = 0;
- if (fread (ape->magic, 1, 4, fp) != 4) {
+ if (deadbeef->fread (ape->magic, 1, 4, fp) != 4) {
return -1;
}
if (memcmp (ape->magic, "MAC ", 4))
@@ -413,14 +412,14 @@ ape_read_header(FILE *fp, APEContext *ape)
if (read_uint32 (fp, &ape->wavtaillength) < 0) {
return -1;
}
- if (fread (ape->md5, 1, 16, fp) != 16) {
+ if (deadbeef->fread (ape->md5, 1, 16, fp) != 16) {
return -1;
}
/* Skip any unknown bytes at the end of the descriptor.
This is for future compatibility */
if (ape->descriptorlength > 52) {
- fseek (fp, ape->descriptorlength - 52, SEEK_CUR);
+ deadbeef->fseek (fp, ape->descriptorlength - 52, SEEK_CUR);
}
/* Read header data */
@@ -478,7 +477,7 @@ ape_read_header(FILE *fp, APEContext *ape)
}
if (ape->formatflags & MAC_FORMAT_FLAG_HAS_PEAK_LEVEL) {
- fseek(fp, 4, SEEK_CUR); /* Skip the peak level */
+ deadbeef->fseek(fp, 4, SEEK_CUR); /* Skip the peak level */
ape->headerlength += 4;
}
@@ -507,7 +506,7 @@ ape_read_header(FILE *fp, APEContext *ape)
/* Skip any stored wav header */
if (!(ape->formatflags & MAC_FORMAT_FLAG_CREATE_WAV_HEADER)) {
- fseek (fp, ape->wavheaderlength, SEEK_CUR);
+ deadbeef->fseek (fp, ape->wavheaderlength, SEEK_CUR);
}
}
@@ -583,19 +582,19 @@ static inline const uint32_t bswap_32(uint32_t x)
return x;
}
-static int ape_read_packet(FILE *fp, APEContext *ape_ctx)
+static int ape_read_packet(DB_FILE *fp, APEContext *ape_ctx)
{
int ret;
int nblocks;
APEContext *ape = ape_ctx;
uint32_t extra_size = 8;
- if (feof(fp))
- return -1;
if (ape->currentframe > ape->totalframes)
return -1;
// fprintf (stderr, "seeking to %d\n", ape->frames[ape->currentframe].pos);
- fseek (fp, ape->frames[ape->currentframe].pos, SEEK_SET);
+ if (deadbeef->fseek (fp, ape->frames[ape->currentframe].pos, SEEK_SET) != 0) {
+ return -1;
+ }
/* Calculate how many blocks there are in this frame */
if (ape->currentframe == (ape->totalframes - 1))
@@ -615,7 +614,7 @@ static int ape_read_packet(FILE *fp, APEContext *ape_ctx)
int sz = PACKET_BUFFER_SIZE-8;
sz = min (sz, ape->frames[ape->currentframe].size);
// fprintf (stderr, "readsize: %d, packetsize: %d\n", sz, ape->frames[ape->currentframe].size);
- ret = fread (ape->packet_data + extra_size, 1, sz, fp);
+ ret = deadbeef->fread (ape->packet_data + extra_size, 1, sz, fp);
ape->packet_sizeleft = ape->frames[ape->currentframe].size - sz + 8;
ape->packet_remaining = sz+8;
@@ -664,10 +663,12 @@ static int ape_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp
}
#endif
+static DB_FILE *fp;
+
static int
ffap_init(DB_playItem_t *it)
{
- fp = fopen (it->fname, "rb");
+ fp = deadbeef->fopen (it->fname);
if (!fp) {
return -1;
}
@@ -1548,7 +1549,7 @@ ape_decode_frame(APEContext *s, void *data, int *data_size)
sz = min (sz, s->packet_sizeleft);
sz = sz&~3;
uint8_t *p = s->packet_data + s->packet_remaining;
- int r = fread (p, 1, sz, fp);
+ int r = deadbeef->fread (p, 1, sz, fp);
//if (r != s) {
// fprintf (stderr, "unexpected eof while reading ape frame\n");
// return -1;
@@ -1626,19 +1627,19 @@ static DB_playItem_t *
ffap_insert (DB_playItem_t *after, const char *fname) {
APEContext ape_ctx;
memset (&ape_ctx, 0, sizeof (ape_ctx));
- FILE *fp = fopen (fname, "rb");
+ DB_FILE *fp = deadbeef->fopen (fname);
if (!fp) {
return NULL;
}
if (ape_read_header (fp, &ape_ctx) < 0) {
fprintf (stderr, "failed to read ape header\n");
- fclose (fp);
+ deadbeef->fclose (fp);
ape_free_ctx (&ape_ctx);
return NULL;
}
if ((ape_ctx.fileversion < APE_MIN_VERSION) || (ape_ctx.fileversion > APE_MAX_VERSION)) {
fprintf(stderr, "unsupported file version - %.2f\n", ape_ctx.fileversion/1000.0);
- fclose (fp);
+ deadbeef->fclose (fp);
ape_free_ctx (&ape_ctx);
return NULL;
}
@@ -1647,7 +1648,7 @@ ffap_insert (DB_playItem_t *after, const char *fname) {
DB_playItem_t *it;
it = deadbeef->pl_insert_cue (after, fname, &plugin, "APE", ape_ctx.totalsamples, ape_ctx.samplerate);
if (it) {
- fclose (fp);
+ deadbeef->fclose (fp);
ape_free_ctx (&ape_ctx);
return it;
}
@@ -1656,21 +1657,21 @@ ffap_insert (DB_playItem_t *after, const char *fname) {
it->decoder = &plugin;
it->fname = strdup (fname);
it->filetype = "APE";
- it->duration = duration;
+ deadbeef->pl_set_item_duration (it, duration);
int v2err = deadbeef->junk_read_id3v2 (it, fp);
int v1err = deadbeef->junk_read_id3v1 (it, fp);
if (v1err >= 0) {
- fseek (fp, -128, SEEK_END);
+ deadbeef->fseek (fp, -128, SEEK_END);
}
else {
- fseek (fp, 0, SEEK_END);
+ deadbeef->fseek (fp, 0, SEEK_END);
}
int apeerr = deadbeef->junk_read_ape (it, fp);
deadbeef->pl_add_meta (it, "title", NULL);
after = deadbeef->pl_insert_item (after, it);
- fclose (fp);
+ deadbeef->fclose (fp);
ape_free_ctx (&ape_ctx);
return after;
}
@@ -1759,8 +1760,8 @@ static DB_decoder_t plugin = {
.plugin.version_major = 0,
.plugin.version_minor = 1,
.plugin.type = DB_PLUGIN_DECODER,
- .plugin.name = "FFAP Monkey's Audio decoder",
- .plugin.descr = "Based on ffmpeg apedec by Benjamin Zores and rockbox libdemac by Dave Chapman",
+ .plugin.name = "Monkey's Audio (APE) decoder",
+ .plugin.descr = "Derived from ffmpeg code by Benjamin Zores and rockbox code by Dave Chapman",
.plugin.author = "Alexey Yakovenko",
.plugin.email = "waker@users.sourceforge.net",
.plugin.website = "http://deadbeef.sf.net",
diff --git a/plugins/flac/Makefile.am b/plugins/flac/Makefile.am
index 3072883e..1e8b09bc 100644
--- a/plugins/flac/Makefile.am
+++ b/plugins/flac/Makefile.am
@@ -1,3 +1,4 @@
+if HAVE_FLAC
flacdir = $(libdir)/$(PACKAGE)
pkglib_LTLIBRARIES = flac.la
flac_la_SOURCES = flac.c
@@ -5,3 +6,4 @@ flac_la_LDFLAGS = -module
flac_la_LIBADD = $(LDADD) $(FLAC_LIBS)
AM_CFLAGS = $(CFLAGS) -std=c99
+endif
diff --git a/plugins/flac/flac.c b/plugins/flac/flac.c
index aa7a9cb5..37b0556e 100644
--- a/plugins/flac/flac.c
+++ b/plugins/flac/flac.c
@@ -24,8 +24,8 @@
static DB_decoder_t plugin;
static DB_functions_t *deadbeef;
-//#define trace(...) { fprintf(stderr, __VA_ARGS__); }
-#define trace(fmt,...)
+#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+//#define trace(fmt,...)
#define min(x,y) ((x)<(y)?(x):(y))
#define max(x,y) ((x)>(y)?(x):(y))
@@ -39,8 +39,10 @@ static int endsample;
static int currentsample;
typedef struct {
+ DB_FILE *file;
DB_playItem_t *after;
DB_playItem_t *last;
+ DB_playItem_t *it;
const char *fname;
int samplerate;
int channels;
@@ -48,6 +50,45 @@ typedef struct {
int bps;
} cue_cb_data_t;
+static cue_cb_data_t flac_callbacks;
+
+// callbacks
+FLAC__StreamDecoderReadStatus flac_read_cb (const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data) {
+ cue_cb_data_t *cb = (cue_cb_data_t *)client_data;
+ size_t r = deadbeef->fread (buffer, 1, *bytes, cb->file);
+ *bytes = r;
+ if (r == 0) {
+ return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
+ }
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+}
+
+FLAC__StreamDecoderSeekStatus flac_seek_cb (const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data) {
+ cue_cb_data_t *cb = (cue_cb_data_t *)client_data;
+ int r = deadbeef->fseek (cb->file, absolute_byte_offset, SEEK_SET);
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+}
+
+FLAC__StreamDecoderTellStatus flac_tell_cb (const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data) {
+ cue_cb_data_t *cb = (cue_cb_data_t *)client_data;
+ size_t r = deadbeef->ftell (cb->file);
+ *absolute_byte_offset = r;
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+}
+
+FLAC__StreamDecoderLengthStatus flac_lenght_cb (const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data) {
+ cue_cb_data_t *cb = (cue_cb_data_t *)client_data;
+ size_t pos = deadbeef->ftell (cb->file);
+ deadbeef->fseek (cb->file, 0, SEEK_END);
+ *stream_length = deadbeef->ftell (cb->file);
+ deadbeef->fseek (cb->file, pos, SEEK_SET);
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+}
+
+FLAC__bool flac_eof_cb (const FLAC__StreamDecoder *decoder, void *client_data) {
+ return 0;
+}
+
static FLAC__StreamDecoderWriteStatus
cflac_write_callback (const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const inputbuffer[], void *client_data) {
if (frame->header.blocksize == 0) {
@@ -73,7 +114,7 @@ cflac_write_callback (const FLAC__StreamDecoder *decoder, const FLAC__Frame *fra
}
}
if (readbytes > bufsize) {
- fprintf (stderr, "flac: buffer overflow, distortion will occur\n");
+ trace ("flac: buffer overflow, distortion will occur\n");
// return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
}
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
@@ -101,69 +142,65 @@ cflac_init_error_callback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecode
cflac_init_stop_decoding = 1;
}
-static void
-cflac_free (void);
-
static int
cflac_init (DB_playItem_t *it) {
- FILE *fp = fopen (it->fname, "rb");
- if (!fp) {
+ memset (&flac_callbacks, 0, sizeof (flac_callbacks));
+ flac_callbacks.file = deadbeef->fopen (it->fname);
+ if (!flac_callbacks.file) {
return -1;
}
- int skip = deadbeef->junk_get_leading_size (fp);
+ int skip = deadbeef->junk_get_leading_size (flac_callbacks.file);
if (skip > 0) {
- fseek (fp, skip, SEEK_SET);
+ deadbeef->fseek (flac_callbacks.file, skip, SEEK_SET);
}
char sign[4];
- if (fread (sign, 1, 4, fp) != 4) {
- fclose (fp);
+ if (deadbeef->fread (sign, 1, 4, flac_callbacks.file) != 4) {
+ plugin.free ();
return -1;
}
if (strncmp (sign, "fLaC", 4)) {
- fclose (fp);
+ plugin.free ();
return -1;
}
- fclose (fp);
- fp = NULL;
+ deadbeef->fseek (flac_callbacks.file, -4, SEEK_CUR);
FLAC__StreamDecoderInitStatus status;
decoder = FLAC__stream_decoder_new();
if (!decoder) {
-// printf ("FLAC__stream_decoder_new failed\n");
+ trace ("FLAC__stream_decoder_new failed\n");
return -1;
}
FLAC__stream_decoder_set_md5_checking(decoder, 0);
- cue_cb_data_t cb;
- status = FLAC__stream_decoder_init_file(decoder, it->fname, cflac_write_callback, cflac_metadata_callback, cflac_error_callback, &cb);
+ status = FLAC__stream_decoder_init_stream (decoder, flac_read_cb, flac_seek_cb, flac_tell_cb, flac_lenght_cb, flac_eof_cb, cflac_write_callback, cflac_metadata_callback, cflac_error_callback, &flac_callbacks);
if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
- cflac_free ();
+ plugin.free ();
return -1;
}
//plugin.info.samplerate = -1;
if (!FLAC__stream_decoder_process_until_end_of_metadata (decoder)) {
- cflac_free ();
+ plugin.free ();
return -1;
}
- plugin.info.samplerate = cb.samplerate;
- plugin.info.channels = cb.channels;
- plugin.info.bps = cb.bps;
+ plugin.info.samplerate = flac_callbacks.samplerate;
+ plugin.info.channels = flac_callbacks.channels;
+ plugin.info.bps = flac_callbacks.bps;
plugin.info.readpos = 0;
if (plugin.info.samplerate == -1) { // not a FLAC stream
- cflac_free ();
+ plugin.free ();
return -1;
}
if (it->endsample > 0) {
startsample = it->startsample;
endsample = it->endsample;
if (plugin.seek_sample (0) < 0) {
- cflac_free ();
+ plugin.free ();
return -1;
}
trace ("flac(cue): startsample=%d, endsample=%d, totalsamples=%d, currentsample=%d\n", startsample, endsample, flac_callbacks.totalsamples, currentsample);
}
else {
startsample = 0;
- endsample = cb.totalsamples-1;
+ endsample = flac_callbacks.totalsamples-1;
currentsample = 0;
trace ("flac: startsample=%d, endsample=%d, totalsamples=%d\n", startsample, endsample, flac_callbacks.totalsamples);
}
@@ -288,16 +325,19 @@ cflac_init_write_callback (const FLAC__StreamDecoder *decoder, const FLAC__Frame
static void
cflac_init_cue_metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) {
if (cflac_init_stop_decoding) {
+ trace ("flac: cflac_init_cue_metadata_callback: cflac_init_stop_decoding=1\n");
return;
}
cue_cb_data_t *cb = (cue_cb_data_t *)client_data;
if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
+ trace ("flac: cflac_init_cue_metadata_callback: got FLAC__METADATA_TYPE_STREAMINFO\n");
cb->samplerate = metadata->data.stream_info.sample_rate;
cb->channels = metadata->data.stream_info.channels;
//cb->duration = metadata->data.stream_info.total_samples / (float)metadata->data.stream_info.sample_rate;
cb->totalsamples = metadata->data.stream_info.total_samples;
}
else if (metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
+ trace ("flac: cflac_init_cue_metadata_callback: got FLAC__METADATA_TYPE_VORBIS_COMMENT\n");
const FLAC__StreamMetadata_VorbisComment *vc = &metadata->data.vorbis_comment;
for (int i = 0; i < vc->num_comments; i++) {
const FLAC__StreamMetadata_VorbisComment_Entry *c = &vc->comments[i];
@@ -316,13 +356,14 @@ cflac_init_cue_metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC_
static void
cflac_init_metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) {
if (cflac_init_stop_decoding) {
- fprintf (stderr, "error flag is set, ignoring init_metadata callback..\n");
+ trace ("error flag is set, ignoring init_metadata callback..\n");
return;
}
- DB_playItem_t *it = (DB_playItem_t *)client_data;
+ cue_cb_data_t *cb = (cue_cb_data_t *)client_data;
+ DB_playItem_t *it = cb->it;
//it->tracknum = 0;
if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
- it->duration = metadata->data.stream_info.total_samples / (float)metadata->data.stream_info.sample_rate;
+ deadbeef->pl_set_item_duration (it, metadata->data.stream_info.total_samples / (float)metadata->data.stream_info.sample_rate);
}
else if (metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
const FLAC__StreamMetadata_VorbisComment *vc = &metadata->data.vorbis_comment;
@@ -381,64 +422,80 @@ cflac_init_metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__Str
static DB_playItem_t *
cflac_insert (DB_playItem_t *after, const char *fname) {
+ trace ("flac: inserting %s\n", fname);
DB_playItem_t *it = NULL;
FLAC__StreamDecoder *decoder = NULL;
- FILE *fp = fopen (fname, "rb");
- if (!fp) {
+ cue_cb_data_t cb = {
+ .fname = fname,
+ .after = after,
+ .last = after
+ };
+ cb.file = deadbeef->fopen (fname);
+ if (!cb.file) {
goto cflac_insert_fail;
}
// skip id3 junk
- int skip = deadbeef->junk_get_leading_size (fp);
+ int skip = deadbeef->junk_get_leading_size (cb.file);
if (skip > 0) {
- fseek (fp, skip, SEEK_SET);
+ deadbeef->fseek (cb.file, skip, SEEK_SET);
}
char sign[4];
- if (fread (sign, 1, 4, fp) != 4) {
+ if (deadbeef->fread (sign, 1, 4, cb.file) != 4) {
+ trace ("flac: failed to read signature\n");
goto cflac_insert_fail;
}
if (strncmp (sign, "fLaC", 4)) {
+ trace ("flac: file signature is not fLaC\n");
goto cflac_insert_fail;
}
- fclose (fp);
- fp = NULL;
+ deadbeef->fseek (cb.file, -4, SEEK_CUR);
cflac_init_stop_decoding = 0;
//try embedded cue, and calculate duration
FLAC__StreamDecoderInitStatus status;
decoder = FLAC__stream_decoder_new();
if (!decoder) {
+ trace ("flac: failed to create decoder\n");
goto cflac_insert_fail;
}
FLAC__stream_decoder_set_md5_checking(decoder, 0);
// try embedded cue
- cue_cb_data_t cb = {
- .fname = fname,
- .after = after,
- .last = after
- };
FLAC__stream_decoder_set_metadata_respond_all (decoder);
- status = FLAC__stream_decoder_init_file (decoder, fname, cflac_init_write_callback, cflac_init_cue_metadata_callback, cflac_init_error_callback, &cb);
+ status = FLAC__stream_decoder_init_stream (decoder, flac_read_cb, flac_seek_cb, flac_tell_cb, flac_lenght_cb, flac_eof_cb, cflac_init_write_callback, cflac_init_cue_metadata_callback, cflac_init_error_callback, &cb);
if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK || cflac_init_stop_decoding) {
+ trace ("flac: FLAC__stream_decoder_init_stream failed\n");
goto cflac_insert_fail;
}
if (!FLAC__stream_decoder_process_until_end_of_metadata (decoder) || cflac_init_stop_decoding) {
+ trace ("flac: FLAC__stream_decoder_process_until_end_of_metadata failed\n");
goto cflac_insert_fail;
}
FLAC__stream_decoder_delete(decoder);
decoder = NULL;
if (cb.last != after) {
+ trace ("flac: loaded embedded cuesheet\n");
// that means embedded cue is loaded
+ if (cb.file) {
+ deadbeef->fclose (cb.file);
+ }
return cb.last;
}
// try external cue
- DB_playItem_t *cue_after = deadbeef->pl_insert_cue (after, fname, &plugin, "flac", cb.totalsamples, cb.samplerate);
+ DB_playItem_t *cue_after = deadbeef->pl_insert_cue (after, fname, &plugin, "FLAC", cb.totalsamples, cb.samplerate);
if (cue_after) {
+ if (cb.file) {
+ deadbeef->fclose (cb.file);
+ }
+ trace ("flac: loaded external cuesheet\n");
return cue_after;
}
decoder = FLAC__stream_decoder_new();
if (!decoder) {
+ if (cb.file) {
+ deadbeef->fclose (cb.file);
+ }
goto cflac_insert_fail;
}
FLAC__stream_decoder_set_md5_checking(decoder, 0);
@@ -448,17 +505,30 @@ cflac_insert (DB_playItem_t *after, const char *fname) {
it = deadbeef->pl_item_alloc ();
it->decoder = &plugin;
it->fname = strdup (fname);
- status = FLAC__stream_decoder_init_file (decoder, fname, cflac_init_write_callback, cflac_init_metadata_callback, cflac_init_error_callback, it);
+ cb.it = it;
+ if (skip > 0) {
+ deadbeef->fseek (cb.file, skip, SEEK_SET);
+ }
+ else {
+ deadbeef->rewind (cb.file);
+ }
+ deadbeef->fseek (cb.file, -4, SEEK_CUR);
+ status = FLAC__stream_decoder_init_stream (decoder, flac_read_cb, flac_seek_cb, flac_tell_cb, flac_lenght_cb, flac_eof_cb, cflac_init_write_callback, cflac_init_metadata_callback, cflac_init_error_callback, &cb);
if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK || cflac_init_stop_decoding) {
+ trace ("flac: FLAC__stream_decoder_init_stream [2] failed\n");
goto cflac_insert_fail;
}
if (!FLAC__stream_decoder_process_until_end_of_metadata (decoder) || cflac_init_stop_decoding) {
+ trace ("flac: FLAC__stream_decoder_process_until_end_of_metadata [2] failed\n");
goto cflac_insert_fail;
}
FLAC__stream_decoder_delete(decoder);
decoder = NULL;
it->filetype = "FLAC";
after = deadbeef->pl_insert_item (after, it);
+ if (cb.file) {
+ deadbeef->fclose (cb.file);
+ }
return after;
cflac_insert_fail:
if (it) {
@@ -467,8 +537,8 @@ cflac_insert_fail:
if (decoder) {
FLAC__stream_decoder_delete(decoder);
}
- if (fp) {
- fclose (fp);
+ if (cb.file) {
+ deadbeef->fclose (cb.file);
}
return NULL;
}
@@ -484,6 +554,7 @@ static DB_decoder_t plugin = {
.plugin.version_minor = 1,
.plugin.type = DB_PLUGIN_DECODER,
.plugin.name = "FLAC decoder",
+ .plugin.descr = "FLAC decoder using libFLAC",
.plugin.author = "Alexey Yakovenko",
.plugin.email = "waker@users.sourceforge.net",
.plugin.website = "http://deadbeef.sf.net",
diff --git a/plugins/hotkeys/hotkeys.c b/plugins/hotkeys/hotkeys.c
index e0ea637c..415e055c 100644
--- a/plugins/hotkeys/hotkeys.c
+++ b/plugins/hotkeys/hotkeys.c
@@ -103,22 +103,22 @@ trim(char* s)
static void
cmd_seek_fwd() {
- deadbeef->playback_set_pos( deadbeef->playback_get_pos() + 5 );
+ deadbeef->playback_set_pos( deadbeef->playback_get_pos() + 5 );
}
static void
cmd_seek_back() {
- deadbeef->playback_set_pos( deadbeef->playback_get_pos() - 5 );
+ deadbeef->playback_set_pos( deadbeef->playback_get_pos() - 5 );
}
static void
cmd_volume_up() {
- deadbeef->volume_set_db( deadbeef->volume_get_db() + 2 );
+ deadbeef->volume_set_db( deadbeef->volume_get_db() + 2 );
}
static void
cmd_volume_down() {
- deadbeef->volume_set_db( deadbeef->volume_get_db() - 2 );
+ deadbeef->volume_set_db( deadbeef->volume_get_db() - 2 );
}
static command_func_t
@@ -160,35 +160,27 @@ get_command( const char* command )
static int
read_config( Display *disp )
{
- char param[ 256 ];
- char config[1024];
- snprintf (config, 1024, "%s/hotkeys", deadbeef->get_config_dir());
- FILE *cfg_file = fopen (config, "rt");
- if (!cfg_file) {
- fprintf (stderr, "hotkeys: failed open %s\n", config);
- return -1;
- }
-
- int line_nr = 0;
-
- while ( fgets( param, sizeof(param), cfg_file ) )
- {
- line_nr++;
+ DB_conf_item_t *item = deadbeef->conf_find ("hotkeys.", NULL);
+ while (item) {
+// fprintf (stderr, "hotkeys: adding %s %s\n", item->key, item->value);
if ( command_count == MAX_COMMAND_COUNT )
{
- fprintf( stderr, "hotkeys: [Config line %d] Maximum count (%d) of commands exceeded\n", line_nr, MAX_COMMAND_COUNT );
+ fprintf( stderr, "hotkeys: maximum number (%d) of commands exceeded\n", MAX_COMMAND_COUNT );
break;
}
command_t *cmd_entry = &commands[ command_count ];
cmd_entry->modifier = 0;
cmd_entry->keycode = 0;
+
+ size_t l = strlen (item->value);
+ char param[l+1];
+ memcpy (param, item->value, l+1);
- param[ strlen( param )-1 ] = 0; //terminating \n
char* colon = strchr( param, ':' );
if ( !colon )
{
- fprintf( stderr, "hotkeys: [Config line %d] Wrong config line\n", line_nr );
+ fprintf( stderr, "hotkeys: bad config option %s %s\n", item->key, item->value);
continue;
}
char* command = colon+1;
@@ -235,27 +227,29 @@ read_config( Display *disp )
}
if ( !cmd_entry->keycode )
{
- fprintf( stderr, "hotkeys: [Config line %d] Unknown key: <%s>\n", line_nr, key );
- continue;
+ fprintf( stderr, "hotkeys: Unknown key: <%s> while parsing %s %s\n", key, item->key, item->value );
+ break;
}
}
- }
- while ( !done );
-
- if ( cmd_entry->keycode == 0 )
- {
- fprintf( stderr, "hotkeys: [Config line %d] Key not specified\n", line_nr );
- continue;
- }
+ } while ( !done );
- command = trim (command);
- cmd_entry->func = get_command( command );
- if ( !cmd_entry->func )
- {
- fprintf( stderr, "hotkeys: [Config line %d] Unknown command <%s>\n", line_nr, command );
- continue;
+ if (done) {
+ if ( cmd_entry->keycode == 0 ) {
+ fprintf( stderr, "hotkeys: Key not found while parsing %s %s\n", item->key, item->value);
+ }
+ else {
+ command = trim (command);
+ cmd_entry->func = get_command( command );
+ if ( !cmd_entry->func )
+ {
+ fprintf( stderr, "hotkeys: Unknown command <%s> while parsing %s %s\n", command, item->key, item->value);
+ }
+ else {
+ command_count++;
+ }
+ }
}
- command_count++;
+ item = deadbeef->conf_find ("hotkeys.", item);
}
}
@@ -275,12 +269,8 @@ static void
hotkeys_event_loop( uintptr_t unused ) {
int i;
- for ( i = 0; i < command_count; i++ )
- XGrabKey( disp, commands[ i ].keycode, commands[ i ].modifier, DefaultRootWindow( disp ), False, GrabModeAsync, GrabModeAsync );
-
while (!finished) {
XEvent event;
-
while ( XPending( disp ) )
{
XNextEvent( disp, &event );
@@ -301,8 +291,10 @@ hotkeys_event_loop( uintptr_t unused ) {
}
static int
-x_err_handler (Display *disp, XErrorEvent *evt) {
- fprintf( stderr, "hotkeys: We got an Xlib error. Most probably one or more of your hotkeys won't work\n" );
+x_err_handler (Display *d, XErrorEvent *evt) {
+ char buffer[1024];
+ XGetErrorText(d, evt->error_code, buffer, sizeof (buffer));
+ fprintf( stderr, "hotkeys: xlib error: %s\n", buffer);
}
static int
@@ -312,12 +304,19 @@ hotkeys_start (void) {
disp = XOpenDisplay( NULL );
if ( !disp )
{
- fprintf( stderr, "Could not open display\n" );
+ fprintf( stderr, "hotkeys: could not open display\n" );
return -1;
}
XSetErrorHandler( x_err_handler );
read_config( disp );
+ int i;
+ // need to grab it here to prevent gdk_x_error from being called while we're
+ // doing it on other thread
+ for (i = 0; i < command_count; i++) {
+ XGrabKey (disp, commands[i].keycode, commands[i].modifier, DefaultRootWindow (disp), False, GrabModeAsync, GrabModeAsync);
+ }
+ XSync (disp, 0);
if (command_count > 0) {
loop_tid = deadbeef->thread_start( hotkeys_event_loop, 0 );
}
@@ -339,8 +338,8 @@ hotkeys_stop (void) {
static DB_misc_t plugin = {
DB_PLUGIN_SET_API_VERSION
.plugin.type = DB_PLUGIN_MISC,
- .plugin.name = "Global Hotkeys",
- .plugin.descr = "Allows to control player using xlib global hotkeys",
+ .plugin.name = "Global hotkeys support",
+ .plugin.descr = "Allows to control player with global hotkeys",
.plugin.author = "Viktor Semykin",
.plugin.email = "thesame.ml@gmail.com",
.plugin.website = "http://deadbeef.sf.net",
diff --git a/plugins/lastfm/Makefile.am b/plugins/lastfm/Makefile.am
index c9e1e9b3..4702909e 100644
--- a/plugins/lastfm/Makefile.am
+++ b/plugins/lastfm/Makefile.am
@@ -1,8 +1,9 @@
+if HAVE_CURL
lastfmdir = $(libdir)/$(PACKAGE)
pkglib_LTLIBRARIES = lastfm.la
lastfm_la_SOURCES = lastfm.c
lastfm_la_LDFLAGS = -module
-lastfm_la_LIBADD = $(LDADD) $(LFM_LIBS)
-AM_CFLAGS = $(LASTFM_DEPS_CFLAGS) -std=c99
-
+lastfm_la_LIBADD = $(LDADD) $(CURL_LIBS)
+AM_CFLAGS = -std=c99
+endif
diff --git a/plugins/lastfm/lastfm.c b/plugins/lastfm/lastfm.c
index 77ae5657..e7c5cddb 100644
--- a/plugins/lastfm/lastfm.c
+++ b/plugins/lastfm/lastfm.c
@@ -247,7 +247,7 @@ lfm_fetch_song_info (DB_playItem_t *song, const char **a, const char **t, const
if (!*b) {
*b = "";
}
- *l = song->duration;
+ *l = deadbeef->pl_get_item_duration (song);
*n = deadbeef->pl_find_meta (song, "track");
if (!*n) {
*n = "";
@@ -425,12 +425,12 @@ lastfm_songfinished (DB_event_song_t *ev, uintptr_t data) {
#if !LFM_IGNORE_RULES
// check submission rules
// duration must be >= 30 sec
- if (ev->song->duration < 30) {
+ if (deadbeef->pl_get_item_duration (ev->song) < 30) {
trace ("song duration is %f seconds. not eligible for submission\n", ev->song->duration);
return 0;
}
// must be played for >=240sec of half the total time
- if (ev->song->playtime < 240 && ev->song->playtime < ev->song->duration/2) {
+ if (ev->song->playtime < 240 && ev->song->playtime < deadbeef->pl_get_item_duration (ev->song)/2) {
trace ("song playtime=%f seconds. not eligible for submission\n", ev->song->playtime);
return 0;
}
@@ -705,8 +705,6 @@ lastfm_start (void) {
// subscribe to frameupdate event
deadbeef->ev_subscribe (DB_PLUGIN (&plugin), DB_EV_SONGSTARTED, DB_CALLBACK (lastfm_songstarted), 0);
deadbeef->ev_subscribe (DB_PLUGIN (&plugin), DB_EV_SONGFINISHED, DB_CALLBACK (lastfm_songfinished), 0);
- // load login/pass
- char config[1024];
// {{{ lastfm v2 auth
#if 0
@@ -725,41 +723,9 @@ lastfm_start (void) {
#endif
// }}}
- snprintf (config, 1024, "%s/lastfm", deadbeef->get_config_dir ());
- FILE *fp = fopen (config, "rt");
- if (!fp) {
- trace ("lastfm: failed open %s\n", config);
- return -1;
- }
- if (!fgets (lfm_user, 50, fp)) {
- trace ("lastfm: failed to read login from %s\n", config);
- fclose (fp);
- return -1;
- }
- if (!fgets (lfm_pass, 50, fp)) {
- trace ("lastfm: failed to read pass from %s\n", config);
- fclose (fp);
- return -1;
- }
- fclose (fp);
- // remove trailing garbage
- int l;
- char *p;
- l = strlen (lfm_user);
- p = lfm_user+l-1;
- while (p >= lfm_user && *p < 0x20) {
- p--;
- }
- p++;
- *p = 0;
- l = strlen (lfm_pass);
- p = lfm_pass+l-1;
- while (p >= lfm_pass && *p < 0x20) {
- p--;
- }
- p++;
- *p = 0;
-
+ // load login/pass
+ strcpy (lfm_user, deadbeef->conf_get_str ("lastfm.login", ""));
+ strcpy (lfm_pass, deadbeef->conf_get_str ("lastfm.password", ""));
return 0;
}
diff --git a/plugins/mpgmad/Makefile.am b/plugins/mpgmad/Makefile.am
index d3019db1..08abdf59 100644
--- a/plugins/mpgmad/Makefile.am
+++ b/plugins/mpgmad/Makefile.am
@@ -1,3 +1,4 @@
+if HAVE_LIBMAD
mpgmaddir = $(libdir)/$(PACKAGE)
pkglib_LTLIBRARIES = mpgmad.la
mpgmad_la_SOURCES = mpgmad.c
@@ -5,3 +6,4 @@ mpgmad_la_LDFLAGS = -module
mpgmad_la_LIBADD = $(LDADD) $(MAD_LIBS)
AM_CFLAGS = $(CFLAGS) -std=c99
+endif
diff --git a/plugins/mpgmad/mpgmad.c b/plugins/mpgmad/mpgmad.c
index 404b0422..763b7fb1 100644
--- a/plugins/mpgmad/mpgmad.c
+++ b/plugins/mpgmad/mpgmad.c
@@ -31,12 +31,7 @@
static DB_decoder_t plugin;
static DB_functions_t *deadbeef;
-#define READBUFFER 0x10000
-#define READBUFFER_MASK 0xffff
-
-// FIXME: cache is bad name for this
-#define CACHE_SIZE 0x20000
-#define CACHE_MASK 0x1ffff
+#define READBUFFER 0x2800 // 10k is enough for single frame
// vbrmethod constants
#define LAME_CBR 1
@@ -54,22 +49,16 @@ static DB_functions_t *deadbeef;
#define VBR_SCALE_FLAG 0x0008
typedef struct {
- FILE *file;
+ DB_FILE *file;
-// // input buffer, for MPEG data
-// // FIXME: this should go away if reading happens per-frame
+ // input buffer, for MPEG data
char input[READBUFFER];
int remaining;
- // NOTE: both "output" and "cache" buffers store sampels in libmad fixed point format
-
-// // output buffer, supplied by player
+ // output buffer, supplied by player
int readsize;
-
- // cache, for extra decoded samples
- char cache[CACHE_SIZE];
- int cachefill;
- int cachepos;
+ int decode_remaining; // number of decoded samples of current mpeg frame
+ char *out;
// information, filled by cmp3_scan_stream
int version;
@@ -97,9 +86,6 @@ static struct mad_stream stream;
static struct mad_frame frame;
static struct mad_synth synth;
-static int
-cmp3_decode (void);
-
static uint32_t
extract_i32 (unsigned char *buf)
{
@@ -181,8 +167,8 @@ cmp3_scan_stream (buffer_t *buffer, int sample) {
for (;;) {
uint32_t hdr;
uint8_t sync;
- size_t pos = ftell (buffer->file);
- if (fread (&sync, 1, 1, buffer->file) != 1) {
+ size_t pos = deadbeef->ftell (buffer->file);
+ if (deadbeef->fread (&sync, 1, 1, buffer->file) != 1) {
break; // eof
}
if (sync != 0xff) {
@@ -190,7 +176,7 @@ cmp3_scan_stream (buffer_t *buffer, int sample) {
}
else {
// 2nd sync byte
- if (fread (&sync, 1, 1, buffer->file) != 1) {
+ if (deadbeef->fread (&sync, 1, 1, buffer->file) != 1) {
break; // eof
}
if ((sync >> 5) != 7) {
@@ -200,11 +186,11 @@ cmp3_scan_stream (buffer_t *buffer, int sample) {
// found frame
hdr = (0xff<<24) | (sync << 16);
// read 2 bytes more
- if (fread (&sync, 1, 1, buffer->file) != 1) {
+ if (deadbeef->fread (&sync, 1, 1, buffer->file) != 1) {
break; // eof
}
hdr |= sync << 8;
- if (fread (&sync, 1, 1, buffer->file) != 1) {
+ if (deadbeef->fread (&sync, 1, 1, buffer->file) != 1) {
break; // eof
}
hdr |= sync;
@@ -333,38 +319,40 @@ cmp3_scan_stream (buffer_t *buffer, int sample) {
// try to read xing/info tag (only on initial scans)
if (sample <= 0 && !got_xing_header)
{
- size_t framepos = ftell (buffer->file);
+ size_t framepos = deadbeef->ftell (buffer->file);
trace ("trying to read xing header\n");
if (ver == 1) {
- fseek (buffer->file, 32, SEEK_CUR);
+ deadbeef->fseek (buffer->file, 32, SEEK_CUR);
}
else {
- fseek (buffer->file, 17, SEEK_CUR);
+ deadbeef->fseek (buffer->file, 17, SEEK_CUR);
}
const char xing[] = "Xing";
const char info[] = "Info";
char magic[4];
- if (fread (magic, 1, 4, buffer->file) != 4) {
+ if (deadbeef->fread (magic, 1, 4, buffer->file) != 4) {
return -1; // EOF
}
// add information to skip this frame
- int startoffset = ftell (buffer->file) + packetlength;
+ int startoffset = deadbeef->ftell (buffer->file) + packetlength;
if (startoffset > buffer->startoffset) {
buffer->startoffset = startoffset;
}
+ trace ("xing magic: %c%c%c%c\n", magic[0], magic[1], magic[2], magic[3]);
+
if (!strncmp (xing, magic, 4) || !strncmp (info, magic, 4)) {
trace ("xing/info frame found\n");
// read flags
uint32_t flags;
uint8_t buf[4];
- if (fread (buf, 1, 4, buffer->file) != 4) {
+ if (deadbeef->fread (buf, 1, 4, buffer->file) != 4) {
return -1; // EOF
}
flags = extract_i32 (buf);
if (flags & FRAMES_FLAG) {
// read number of frames
- if (fread (buf, 1, 4, buffer->file) != 4) {
+ if (deadbeef->fread (buf, 1, 4, buffer->file) != 4) {
return -1; // EOF
}
uint32_t nframes = extract_i32 (buf);
@@ -374,82 +362,90 @@ cmp3_scan_stream (buffer_t *buffer, int sample) {
buffer->samplerate = samplerate;
}
if (flags & BYTES_FLAG) {
- fseek (buffer->file, 4, SEEK_CUR);
+ deadbeef->fseek (buffer->file, 4, SEEK_CUR);
}
if (flags & TOC_FLAG) {
- fseek (buffer->file, 100, SEEK_CUR);
+ deadbeef->fseek (buffer->file, 100, SEEK_CUR);
}
if (flags & VBR_SCALE_FLAG) {
- fseek (buffer->file, 4, SEEK_CUR);
+ deadbeef->fseek (buffer->file, 4, SEEK_CUR);
}
// lame header
- if (fread (buf, 1, 4, buffer->file) != 4) {
+ if (deadbeef->fread (buf, 1, 4, buffer->file) != 4) {
return -1; // EOF
}
- trace ("tell=%x, %c%c%c%c\n", ftell(buffer->file), buf[0], buf[1], buf[2], buf[3]);
+ trace ("tell=%x, %c%c%c%c\n", deadbeef->ftell(buffer->file), buf[0], buf[1], buf[2], buf[3]);
if (!memcmp (buf, "LAME", 4)) {
trace ("lame header found\n");
- fseek (buffer->file, 6, SEEK_CUR);
+ deadbeef->fseek (buffer->file, 6, SEEK_CUR);
// FIXME: that can be optimized by single read
uint8_t lpf;
- fread (&lpf, 1, 1, buffer->file);
+ deadbeef->fread (&lpf, 1, 1, buffer->file);
//3 floats: replay gain
- fread (buf, 1, 4, buffer->file);
+ deadbeef->fread (buf, 1, 4, buffer->file);
float rg_peaksignalamp = extract_f32 (buf);
- fread (buf, 1, 2, buffer->file);
+ deadbeef->fread (buf, 1, 2, buffer->file);
uint16_t rg_radio = extract_i16 (buf);
- fread (buf, 1, 2, buffer->file);
+ deadbeef->fread (buf, 1, 2, buffer->file);
uint16_t rg_audiophile = extract_i16 (buf);
// skip
- fseek (buffer->file, 2, SEEK_CUR);
- fread (buf, 1, 3, buffer->file);
+ deadbeef->fseek (buffer->file, 2, SEEK_CUR);
+ deadbeef->fread (buf, 1, 3, buffer->file);
uint32_t startdelay = (((uint32_t)buf[0]) << 4) | ((((uint32_t)buf[1]) & 0xf0)>>4);
uint32_t enddelay = ((((uint32_t)buf[1])&0x0f)<<8) | ((uint32_t)buf[2]);
// skip
- fseek (buffer->file, 1, SEEK_CUR);
+ deadbeef->fseek (buffer->file, 1, SEEK_CUR);
// mp3gain
uint8_t mp3gain;
- fread (&mp3gain, 1, 1, buffer->file);
+ deadbeef->fread (&mp3gain, 1, 1, buffer->file);
// skip
- fseek (buffer->file, 2, SEEK_CUR);
+ deadbeef->fseek (buffer->file, 2, SEEK_CUR);
// musiclen
- fread (buf, 1, 4, buffer->file);
+ deadbeef->fread (buf, 1, 4, buffer->file);
uint32_t musiclen = extract_i32 (buf);
trace ("lpf: %d, peaksignalamp: %f, radiogain: %d, audiophile: %d, startdelay: %d, enddelay: %d, mp3gain: %d, musiclen: %d\n", lpf, rg_peaksignalamp, rg_radio, rg_audiophile, startdelay, enddelay, mp3gain, musiclen);
// skip crc
- //fseek (buffer->file, 4, SEEK_CUR);
+ //deadbeef->fseek (buffer->file, 4, SEEK_CUR);
buffer->startdelay = startdelay;
buffer->enddelay = enddelay;
}
if (sample <= 0 && (flags&FRAMES_FLAG)) {
buffer->totalsamples -= buffer->enddelay;
trace ("lame totalsamples: %d\n", buffer->totalsamples);
- fseek (buffer->file, framepos+packetlength-4, SEEK_SET);
+ deadbeef->fseek (buffer->file, framepos+packetlength-4, SEEK_SET);
return 0;
}
}
if (sample == 0) {
// xing header failed, calculate based on file size
- fseek (buffer->file, 0, SEEK_END);
- int sz = ftell (buffer->file) - buffer->startoffset - buffer->endoffset;
+ trace ("xing header failed\n");
+ buffer->samplerate = samplerate;
+ int sz = deadbeef->fgetlength (buffer->file) - buffer->startoffset - buffer->endoffset;
+ if (sz < 0) {
+ buffer->duration = -1;
+ buffer->totalsamples = -1;
+ if (sample == 0) {
+ deadbeef->fseek (buffer->file, framepos+packetlength-4, SEEK_SET);
+ }
+ return 0;
+ }
int nframes = sz / packetlength;
buffer->duration = nframes * samples_per_frame / samplerate;
buffer->totalsamples = nframes * samples_per_frame;
- buffer->samplerate = samplerate;
// trace ("packetlength=%d, fsize=%d, nframes=%d, samples_per_frame=%d, samplerate=%d, duration=%f, totalsamples=%d\n", packetlength, sz, nframes, samples_per_frame, samplerate, buffer->duration, buffer->totalsamples);
if (sample == 0) {
- fseek (buffer->file, framepos+packetlength-4, SEEK_SET);
+ deadbeef->fseek (buffer->file, framepos+packetlength-4, SEEK_SET);
return 0;
}
}
- fseek (buffer->file, framepos+packetlength-4, SEEK_SET);
+ deadbeef->fseek (buffer->file, framepos+packetlength-4, SEEK_SET);
got_xing_header = 1;
}
if (sample >= 0 && scansamples + samples_per_frame >= sample) {
- fseek (buffer->file, -4, SEEK_CUR);
+ deadbeef->fseek (buffer->file, -4, SEEK_CUR);
buffer->currentsample = sample;
buffer->skipsamples = sample - scansamples;
return 0;
@@ -459,7 +455,7 @@ cmp3_scan_stream (buffer_t *buffer, int sample) {
buffer->duration += dur;
nframe++;
if (packetlength > 0) {
- fseek (buffer->file, packetlength-4, SEEK_CUR);
+ deadbeef->fseek (buffer->file, packetlength-4, SEEK_CUR);
}
}
if (nframe == 0) {
@@ -474,33 +470,85 @@ cmp3_scan_stream (buffer_t *buffer, int sample) {
static int
cmp3_init (DB_playItem_t *it) {
memset (&buffer, 0, sizeof (buffer));
- buffer.file = fopen (it->fname, "rb");
+ buffer.file = plugin.info.file = deadbeef->fopen (it->fname);
if (!buffer.file) {
return -1;
}
- int skip = deadbeef->junk_get_leading_size (buffer.file);
- if (skip > 0) {
- fseek(buffer.file, skip, SEEK_SET);
- }
plugin.info.readpos = 0;
- cmp3_scan_stream (&buffer, -1); // scan entire stream, calc duration
- if (it->endsample > 0) {
- buffer.startsample = it->startsample;
- buffer.endsample = it->endsample;
- // that comes from cue, don't calc duration, just seek and play
- plugin.seek_sample (0);
+ if (!buffer.file->vfs->streaming) {
+ int skip = deadbeef->junk_get_leading_size (buffer.file);
+ if (skip > 0) {
+ deadbeef->fseek(buffer.file, skip, SEEK_SET);
+ }
+ cmp3_scan_stream (&buffer, -1); // scan entire stream, calc duration
+ if (it->endsample > 0) {
+ buffer.startsample = it->startsample;
+ buffer.endsample = it->endsample;
+ // that comes from cue, don't calc duration, just seek and play
+ plugin.seek_sample (0);
+ }
+ else {
+ deadbeef->pl_set_item_duration (it, buffer.duration);
+ buffer.startsample = 0;
+ buffer.endsample = buffer.totalsamples-1;
+ buffer.skipsamples = buffer.startdelay;
+ buffer.currentsample = buffer.startdelay;
+ deadbeef->fseek (buffer.file, buffer.startoffset, SEEK_SET);
+ }
}
else {
- it->duration = buffer.duration;
- buffer.startsample = 0;
- buffer.endsample = buffer.totalsamples-1;
- buffer.skipsamples = buffer.startdelay;
- buffer.currentsample = buffer.startdelay;
- fseek (buffer.file, buffer.startoffset, SEEK_SET);
+ int len = deadbeef->fgetlength (buffer.file);
+ const char *name = deadbeef->fget_content_name (buffer.file);
+ const char *genre = deadbeef->fget_content_genre (buffer.file);
+ if (len > 0) {
+ deadbeef->pl_delete_all_meta (it);
+ int v2err = deadbeef->junk_read_id3v2 (it, buffer.file);
+ deadbeef->pl_add_meta (it, "title", NULL);
+ if (v2err != 0) {
+ deadbeef->fseek (buffer.file, 0, SEEK_SET);
+ }
+ }
+ else {
+ deadbeef->pl_delete_all_meta (it);
+ if (name) {
+ deadbeef->pl_add_meta (it, "title", name);
+ }
+ else {
+ deadbeef->pl_add_meta (it, "title", NULL);
+ }
+ if (genre) {
+ deadbeef->pl_add_meta (it, "genre", genre);
+ }
+ }
+ int res = cmp3_scan_stream (&buffer, 0);
+ if (res < 0) {
+ trace ("mpgmad: cmp3_init: initial cmp3_scan_stream failed\n");
+ plugin.free ();
+ return -1;
+ }
+ deadbeef->pl_set_item_duration (it, buffer.duration);
+ if (buffer.duration >= 0) {
+ buffer.endsample = buffer.totalsamples - 1;
+ }
+ else {
+// buffer.duration = 200;
+// buffer.totalsamples = 10000000;
+// buffer.endsample = buffer.totalsamples-1;
+ buffer.endsample = -1;
+ buffer.totalsamples = -1;
+ }
+ buffer.skipsamples = 0;
+ buffer.currentsample = 0;
+ if (buffer.duration < 0) {
+ buffer.duration = -1;
+ buffer.totalsamples = -1;
+ buffer.endsample = -1;
+ }
+ trace ("duration=%f, endsample=%d, totalsamples=%d\n", buffer.duration, buffer.endsample, buffer.totalsamples);
}
if (buffer.samplerate == 0) {
trace ("bad mpeg file: %f\n", it->fname);
- fclose (buffer.file);
+ plugin.free ();
return -1;
}
plugin.info.bps = buffer.bitspersample;
@@ -559,21 +607,76 @@ MadFixedToFloat (mad_fixed_t Fixed) {
#define MadErrorString(x) mad_stream_errorstr(x)
+// cuts readsize if it's beyond boundaries
static int
-cmp3_decode (void) {
- if (buffer.currentsample + buffer.readsize / (4 * buffer.channels) > buffer.endsample) {
- buffer.readsize = (buffer.endsample - buffer.currentsample + 1) * 4 * buffer.channels;
- trace ("size truncated to %d bytes, cursample=%d, endsample=%d, totalsamples=%d\n", buffer.readsize, buffer.currentsample, buffer.endsample, buffer.totalsamples);
- if (buffer.readsize <= 0) {
- return 0;
+cmp3_decode_cut (int framesize) {
+ if (buffer.duration >= 0) {
+ if (buffer.currentsample + buffer.readsize / (framesize * buffer.channels) > buffer.endsample) {
+ buffer.readsize = (buffer.endsample - buffer.currentsample + 1) * framesize * buffer.channels;
+ trace ("size truncated to %d bytes, cursample=%d, endsample=%d, totalsamples=%d\n", buffer.readsize, buffer.currentsample, buffer.endsample, buffer.totalsamples);
+ if (buffer.readsize <= 0) {
+ return 1;
+ }
}
}
+ return 0;
+}
+
+static inline void
+cmp3_skip (void) {
+ if (buffer.skipsamples > 0) {
+ int skip = min (buffer.skipsamples, buffer.decode_remaining);
+ buffer.skipsamples -= skip;
+ buffer.decode_remaining -= skip;
+ }
+}
+
+// decoded requested number of samples to int16 format
+static void
+cmp3_decode_requested_int16 (void) {
+ cmp3_skip ();
+ // copy synthesized samples into readbuffer
+ int idx = synth.pcm.length-buffer.decode_remaining;
+ while (buffer.decode_remaining > 0 && buffer.readsize > 0) {
+ *((int16_t*)buffer.out) = MadFixedToSshort (synth.pcm.samples[0][idx]);
+ buffer.readsize -= 2;
+ buffer.out += 2;
+ if (MAD_NCHANNELS(&frame.header) == 2) {
+ *((int16_t*)buffer.out) = MadFixedToSshort (synth.pcm.samples[1][idx]);
+ buffer.readsize -= 2;
+ buffer.out += 2;
+ }
+ buffer.decode_remaining--;
+ idx++;
+ }
+ assert (buffer.readsize >= 0);
+}
+
+// decoded requested number of samples to int16 format
+static void
+cmp3_decode_requested_float32 (void) {
+ cmp3_skip ();
+ // copy synthesized samples into readbuffer
+ int idx = synth.pcm.length-buffer.decode_remaining;
+ while (buffer.decode_remaining > 0 && buffer.readsize > 0) {
+ *((float*)buffer.out) = MadFixedToFloat (synth.pcm.samples[0][idx]);
+ buffer.readsize -= 4;
+ buffer.out += 4;
+ if (MAD_NCHANNELS(&frame.header) == 2) {
+ *((float*)buffer.out) = MadFixedToFloat (synth.pcm.samples[1][idx]);
+ buffer.readsize -= 4;
+ buffer.out += 4;
+ }
+ buffer.decode_remaining--;
+ idx++;
+ }
+ assert (buffer.readsize >= 0);
+}
+
+static int
+cmp3_stream_frame (void) {
int eof = 0;
- for (;;) {
- if (eof) {
- break;
- }
- // FIXME: read single frame here
+ while (!eof && (stream.buffer == NULL || buffer.decode_remaining <= 0)) {
// read more MPEG data if needed
if(stream.buffer==NULL || stream.error==MAD_ERROR_BUFLEN) {
// copy part of last frame to beginning
@@ -584,7 +687,7 @@ cmp3_decode (void) {
int size = READBUFFER - buffer.remaining;
int bytesread = 0;
char *bytes = buffer.input + buffer.remaining;
- bytesread = fread (bytes, 1, size, buffer.file);
+ bytesread = deadbeef->fread (bytes, 1, size, buffer.file);
if (!bytesread) {
// add guard
eof = 1;
@@ -607,77 +710,72 @@ cmp3_decode (void) {
}
stream.error=0;
// decode next frame
- if(mad_frame_decode(&frame,&stream))
- {
- if(MAD_RECOVERABLE(stream.error))
- {
- if(stream.error!=MAD_ERROR_LOSTSYNC) {
- trace ("mpgmad: recoverable frame level error (%s)\n", MadErrorString(&stream));
- }
- continue;
- }
- else {
- if(stream.error==MAD_ERROR_BUFLEN) {
- continue;
+ if(mad_frame_decode(&frame,&stream))
+ {
+ if(MAD_RECOVERABLE(stream.error))
+ {
+#if 0
+ if(stream.error!=MAD_ERROR_LOSTSYNC) {
+ trace ("mpgmad: recoverable frame level error (%s)\n", MadErrorString(&stream));
}
- else
- {
- trace ("mpgmad: unrecoverable frame level error (%s).\n", MadErrorString(&stream));
- break;
- }
- }
- }
-
- plugin.info.samplerate = frame.header.samplerate;
- plugin.info.channels = MAD_NCHANNELS(&frame.header);
-
- mad_synth_frame(&synth,&frame);
-
- int cachepos = (buffer.cachefill + buffer.cachepos) & CACHE_MASK;
- int len = synth.pcm.length;
- if (buffer.currentsample + len > buffer.endsample) {
- len = buffer.endsample - buffer.currentsample + 1;
- }
- int i = min (synth.pcm.length, buffer.skipsamples);
- if (buffer.skipsamples > 0) {
- buffer.skipsamples -= i;
- trace ("skipped %d samples\n", i);
- }
- buffer.currentsample += len-i;
- for(;i<len;i++)
- {
- if (buffer.cachefill >= CACHE_SIZE) {
- printf ("cache overflow!\n");
- break;
+#endif
+ continue;
}
-// if (buffer.cachefill >= CACHE_SIZE - sizeof (mad_fixed_t)) {
-// printf ("readsize=%d, pcm.length=%d(%d)\n", buffer.readsize, synth.pcm.length, i);
-// }
- assert (buffer.cachefill < CACHE_SIZE - sizeof (mad_fixed_t));
- memcpy (buffer.cache+cachepos, &synth.pcm.samples[0][i], sizeof (mad_fixed_t));
- cachepos = (cachepos + sizeof (mad_fixed_t)) & CACHE_MASK;
- buffer.cachefill += sizeof (mad_fixed_t);
- buffer.readsize -= sizeof (mad_fixed_t);
- if (MAD_NCHANNELS(&frame.header) == 2) {
- if (buffer.cachefill >= CACHE_SIZE - sizeof (mad_fixed_t)) {
- trace ("mpgmad: readsize=%d, pcm.length=%d(%d), cachefill=%d, cachepos=%d(%d)\n", buffer.readsize, synth.pcm.length, i, buffer.cachefill, buffer.cachepos, cachepos);
- return 0;
+ else {
+ if(stream.error==MAD_ERROR_BUFLEN) {
+ continue;
+ }
+ else
+ {
+ trace ("mpgmad: unrecoverable frame level error (%s).\n", MadErrorString(&stream));
+ return -1; // fatal error
}
- assert (buffer.cachefill < CACHE_SIZE - sizeof (mad_fixed_t));
- memcpy (buffer.cache+cachepos, &synth.pcm.samples[1][i], sizeof (mad_fixed_t));
- cachepos = (cachepos + sizeof (mad_fixed_t)) & CACHE_MASK;
- buffer.cachefill += sizeof (mad_fixed_t);
- buffer.readsize -= sizeof (mad_fixed_t);
}
}
- if (buffer.currentsample > buffer.totalsamples) {
- trace ("mpgmad: warning: extra samples were read after end of stream\n");
+
+ plugin.info.samplerate = frame.header.samplerate;
+ plugin.info.channels = MAD_NCHANNELS(&frame.header);
+
+ // synthesize single frame
+ mad_synth_frame(&synth,&frame);
+ buffer.decode_remaining = synth.pcm.length;
+ deadbeef->playback_update_bitrate (frame.header.bitrate/1000);
+ break;
+ }
+ return eof;
+}
+
+static int
+cmp3_decode_int16 (void) {
+ if (cmp3_decode_cut (4)) {
+ return 0;
+ }
+ int eof = 0;
+ while (!eof) {
+ eof = cmp3_stream_frame ();
+ if (buffer.decode_remaining > 0) {
+ cmp3_decode_requested_int16 ();
+ if (buffer.readsize == 0) {
+ return 0;
+ }
}
- if (buffer.readsize <= 0 || eof || buffer.currentsample > buffer.endsample) {
- if (buffer.currentsample > buffer.endsample) {
- trace ("finished at sample %d (%d)\n", buffer.currentsample, buffer.totalsamples);
+ }
+ return 0;
+}
+
+static int
+cmp3_decode_float32 (void) {
+ if (cmp3_decode_cut (8)) {
+ return 0;
+ }
+ int eof = 0;
+ while (!eof) {
+ eof = cmp3_stream_frame ();
+ if (buffer.decode_remaining > 0) {
+ cmp3_decode_requested_float32 ();
+ if (buffer.readsize == 0) {
+ return 0;
}
- break;
}
}
return 0;
@@ -685,8 +783,9 @@ cmp3_decode (void) {
static void
cmp3_free (void) {
+ plugin.info.file = NULL;
if (buffer.file) {
- fclose (buffer.file);
+ deadbeef->fclose (buffer.file);
buffer.file = NULL;
mad_synth_finish (&synth);
mad_frame_finish (&frame);
@@ -695,86 +794,19 @@ cmp3_free (void) {
}
static int
-cmp3_read (char *bytes, int size) {
- int result;
- int ret = 0;
- int nsamples = size / 2 / plugin.info.channels;
- size *= 2; // convert to mad sample size
- if (buffer.cachefill < size) {
- buffer.readsize = (size - buffer.cachefill);
- cmp3_decode ();
- plugin.info.readpos = (float)(buffer.currentsample-buffer.startsample) / buffer.samplerate;
- }
- if (buffer.cachefill > 0) {
- int sz = min (size, buffer.cachefill);
- int cachepos = buffer.cachepos;
- for (int i = 0; i < nsamples; i++) {
- mad_fixed_t sample = *((mad_fixed_t*)(buffer.cache + cachepos));
- cachepos = (cachepos + sizeof (mad_fixed_t)) & CACHE_MASK;
- *((int16_t*)bytes) = MadFixedToSshort (sample);
- bytes += 2;
- size -= 2;
- ret += 2;
- if (plugin.info.channels == 2) {
- sample = *((mad_fixed_t*)(buffer.cache + cachepos));
- cachepos = (cachepos + sizeof (mad_fixed_t)) & CACHE_MASK;
- *((int16_t*)bytes) = MadFixedToSshort (sample);
- bytes += 2;
- size -= 2;
- ret += 2;
- }
- }
- if (buffer.cachefill > sz) {
- buffer.cachepos = (buffer.cachepos + sz) & CACHE_MASK;
- buffer.cachefill -= sz;
- }
- else {
- buffer.cachefill = 0;
- buffer.cachepos = 0;
- }
- }
- return ret;
+cmp3_read_int16 (char *bytes, int size) {
+ buffer.readsize = size;
+ buffer.out = bytes;
+ cmp3_decode_int16 ();
+ return size - buffer.readsize;
}
static int
cmp3_read_float32 (char *bytes, int size) {
- int result;
- int ret = 0;
- int nsamples = size / 4 / plugin.info.channels;
- if (buffer.cachefill < size) {
- buffer.readsize = (size - buffer.cachefill);
- //printf ("decoding %d bytes using read_float32\n", buffer.readsize);
- cmp3_decode ();
- plugin.info.readpos = (float)(buffer.currentsample - buffer.startsample) / buffer.samplerate;
- }
- if (buffer.cachefill > 0) {
- int sz = min (size, buffer.cachefill);
- int cachepos = buffer.cachepos;
- for (int i = 0; i < nsamples; i++) {
- mad_fixed_t sample = *((mad_fixed_t*)(buffer.cache + cachepos));
- cachepos = (cachepos + sizeof (mad_fixed_t)) & CACHE_MASK;
- *((float*)bytes) = MadFixedToFloat (sample);
- bytes += 4;
- size -= 4;
- ret += 4;
- if (plugin.info.channels == 2) {
- sample = *((mad_fixed_t*)(buffer.cache + cachepos));
- cachepos = (cachepos + sizeof (mad_fixed_t)) & CACHE_MASK;
- *((float*)bytes) = MadFixedToFloat (sample);
- bytes += 4;
- size -= 4;
- ret += 4;
- }
- }
- if (buffer.cachefill > sz) {
- buffer.cachepos = (buffer.cachepos + sz) & CACHE_MASK;
- buffer.cachefill -= sz;
- }
- else {
- buffer.cachefill = 0;
- }
- }
- return ret;
+ buffer.readsize = size;
+ buffer.out = bytes;
+ cmp3_decode_float32 ();
+ return size - buffer.readsize;
}
static int
@@ -782,24 +814,50 @@ cmp3_seek_sample (int sample) {
if (!buffer.file) {
return -1;
}
+
+ if (buffer.file->vfs->streaming) {
+ if (buffer.totalsamples > 0) {
+ // approximation
+ int64_t l = deadbeef->fgetlength (buffer.file);
+ l = l * sample / buffer.totalsamples;
+ int r = deadbeef->fseek (buffer.file, l, SEEK_SET);
+ if (!r) {
+ buffer.currentsample = sample;
+ plugin.info.readpos = (float)(buffer.currentsample - buffer.startsample) / buffer.samplerate;
+
+ mad_synth_finish (&synth);
+ mad_frame_finish (&frame);
+ mad_stream_finish (&stream);
+ buffer.remaining = 0;
+ buffer.decode_remaining = 0;
+ mad_stream_init(&stream);
+ mad_frame_init(&frame);
+ mad_synth_init(&synth);
+
+ return 0;
+ }
+ return -1;
+ }
+ return 0;
+ }
+
sample += buffer.startsample + buffer.startdelay;
if (sample > buffer.endsample) {
trace ("seek sample %d is beyond end of track (%d)\n", sample, buffer.endsample);
return -1; // eof
}
// restart file, and load until we hit required pos
- fseek (buffer.file, 0, SEEK_SET);
+ deadbeef->fseek (buffer.file, 0, SEEK_SET);
int skip = deadbeef->junk_get_leading_size (buffer.file);
if (skip > 0) {
- fseek(buffer.file, skip, SEEK_SET);
+ deadbeef->fseek(buffer.file, skip, SEEK_SET);
}
mad_synth_finish (&synth);
mad_frame_finish (&frame);
mad_stream_finish (&stream);
buffer.remaining = 0;
buffer.readsize = 0;
- buffer.cachefill = 0;
- buffer.cachepos = 0;
+ buffer.decode_remaining = 0;
if (sample == 0) {
plugin.info.readpos = 0;
@@ -832,21 +890,32 @@ static const char *filetypes[] = {
static DB_playItem_t *
cmp3_insert (DB_playItem_t *after, const char *fname) {
- FILE *fp = fopen (fname, "rb");
+ DB_FILE *fp = deadbeef->fopen (fname);
if (!fp) {
return NULL;
}
+ if (fp->vfs->streaming) {
+ DB_playItem_t *it = deadbeef->pl_item_alloc ();
+ it->decoder = &plugin;
+ it->fname = strdup (fname);
+ deadbeef->fclose (fp);
+ deadbeef->pl_add_meta (it, "title", NULL);
+ deadbeef->pl_set_item_duration (it, -1);
+ it->filetype = filetypes[0];
+ after = deadbeef->pl_insert_item (after, it);
+ return after;
+ }
buffer_t buffer;
memset (&buffer, 0, sizeof (buffer));
buffer.file = fp;
int skip = deadbeef->junk_get_leading_size (buffer.file);
if (skip > 0) {
- fseek(buffer.file, skip, SEEK_SET);
+ deadbeef->fseek(buffer.file, skip, SEEK_SET);
}
// calc approx. mp3 duration
int res = cmp3_scan_stream (&buffer, 0);
if (res < 0) {
- fclose (fp);
+ deadbeef->fclose (fp);
return NULL;
}
@@ -893,11 +962,11 @@ cmp3_insert (DB_playItem_t *after, const char *fname) {
// FIXME! bad numsamples passed to cue
DB_playItem_t *cue_after = deadbeef->pl_insert_cue (after, fname, &plugin, ftype, buffer.duration*buffer.samplerate, buffer.samplerate);
if (cue_after) {
- fclose (fp);
+ deadbeef->fclose (fp);
return cue_after;
}
- rewind (fp);
+ deadbeef->rewind (fp);
DB_playItem_t *it = deadbeef->pl_item_alloc ();
it->decoder = &plugin;
@@ -906,9 +975,9 @@ cmp3_insert (DB_playItem_t *after, const char *fname) {
int apeerr = deadbeef->junk_read_ape (it, fp);
int v2err = deadbeef->junk_read_id3v2 (it, fp);
int v1err = deadbeef->junk_read_id3v1 (it, fp);
- fclose (fp);
+ deadbeef->fclose (fp);
deadbeef->pl_add_meta (it, "title", NULL);
- it->duration = buffer.duration;
+ deadbeef->pl_set_item_duration (it, buffer.duration);
it->filetype = ftype;
after = deadbeef->pl_insert_item (after, it);
@@ -925,14 +994,14 @@ static DB_decoder_t plugin = {
.plugin.version_major = 0,
.plugin.version_minor = 1,
.plugin.type = DB_PLUGIN_DECODER,
- .plugin.name = "MPEG v1,2 layer1,2,3 decoder",
- .plugin.descr = "based on libmad",
+ .plugin.name = "MPEG decoder",
+ .plugin.descr = "MPEG v1/2 layer1/2/3 decoder based on libmad",
.plugin.author = "Alexey Yakovenko",
.plugin.email = "waker@users.sourceforge.net",
.plugin.website = "http://deadbeef.sf.net",
.init = cmp3_init,
.free = cmp3_free,
- .read_int16 = cmp3_read,
+ .read_int16 = cmp3_read_int16,
.read_float32 = cmp3_read_float32,
.seek = cmp3_seek,
.seek_sample = cmp3_seek_sample,
diff --git a/plugins/sndfile/Makefile.am b/plugins/sndfile/Makefile.am
index a92828f5..d5c7c9af 100644
--- a/plugins/sndfile/Makefile.am
+++ b/plugins/sndfile/Makefile.am
@@ -1,3 +1,4 @@
+if HAVE_SNDFILE
sndfiledir = $(libdir)/$(PACKAGE)
pkglib_LTLIBRARIES = sndfile.la
sndfile_la_SOURCES = sndfile.c
@@ -5,3 +6,4 @@ sndfile_la_LDFLAGS = -module
sndfile_la_LIBADD = $(LDADD) $(SNDFILE_LIBS)
AM_CFLAGS = $(CFLAGS) -std=c99
+endif
diff --git a/plugins/sndfile/sndfile.c b/plugins/sndfile/sndfile.c
index 9281890a..46e3a99f 100644
--- a/plugins/sndfile/sndfile.c
+++ b/plugins/sndfile/sndfile.c
@@ -138,7 +138,7 @@ sndfile_insert (DB_playItem_t *after, const char *fname) {
it->decoder = &plugin;
it->fname = strdup (fname);
it->filetype = "wav";
- it->duration = duration;
+ deadbeef->pl_set_item_duration (it, duration);
trace ("sndfile: totalsamples=%d, samplerate=%d, duration=%f\n", totalsamples, samplerate, duration);
@@ -148,7 +148,7 @@ sndfile_insert (DB_playItem_t *after, const char *fname) {
return after;
}
-static const char * exts[] = { "wav", NULL };
+static const char * exts[] = { "wav", "aif", "aiff", "snd", "au", "paf", "svx", "nist", "voc", "ircam", "w64", "mat4", "mat5", "pvf", "xi", "htk", "sds", "avr", "wavex", "sd2", "caf", "wve", NULL };
static const char *filetypes[] = { "wav", NULL };
// define plugin interface
@@ -157,7 +157,8 @@ static DB_decoder_t plugin = {
.plugin.version_major = 0,
.plugin.version_minor = 1,
.plugin.type = DB_PLUGIN_DECODER,
- .plugin.name = "SNDFILE decoder",
+ .plugin.name = "pcm player",
+ .plugin.descr = "wav/aiff player using libsndfile",
.plugin.author = "Alexey Yakovenko",
.plugin.email = "waker@users.sourceforge.net",
.plugin.website = "http://deadbeef.sf.net",
diff --git a/plugins/vfs_curl/Makefile.am b/plugins/vfs_curl/Makefile.am
new file mode 100644
index 00000000..3d7cc3a2
--- /dev/null
+++ b/plugins/vfs_curl/Makefile.am
@@ -0,0 +1,9 @@
+if HAVE_CURL
+vfs_curldir = $(libdir)/$(PACKAGE)
+pkglib_LTLIBRARIES = vfs_curl.la
+vfs_curl_la_SOURCES = vfs_curl.c
+vfs_curl_la_LDFLAGS = -module
+
+vfs_curl_la_LIBADD = $(LDADD) $(CURL_LIBS)
+AM_CFLAGS = $(CFLAGS) -std=c99
+endif
diff --git a/plugins/vfs_curl/vfs_curl.c b/plugins/vfs_curl/vfs_curl.c
new file mode 100644
index 00000000..42211fe3
--- /dev/null
+++ b/plugins/vfs_curl/vfs_curl.c
@@ -0,0 +1,711 @@
+/*
+ DeaDBeeF - ultimate music player for GNU/Linux systems with X11
+ Copyright (C) 2009 Alexey Yakovenko
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+#include <curl/curl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <assert.h>
+#include <curl/curlver.h>
+#include "../../deadbeef.h"
+
+//#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+#define trace(fmt,...)
+
+#define min(x,y) ((x)<(y)?(x):(y))
+#define max(x,y) ((x)>(y)?(x):(y))
+
+static DB_functions_t *deadbeef;
+
+#define BUFFER_SIZE (0x10000)
+#define BUFFER_MASK 0xffff
+
+#define STATUS_INITIAL 0
+#define STATUS_STARTING 1
+#define STATUS_READING 2
+#define STATUS_FINISHED 3
+#define STATUS_ABORTED 4
+#define STATUS_SEEK 5
+
+typedef struct {
+ DB_vfs_t *vfs;
+ char *url;
+ uint8_t buffer[BUFFER_SIZE];
+ long pos; // position in stream; use "& BUFFER_MASK" to make it index into ringbuffer
+ int64_t length;
+ int32_t remaining; // remaining bytes in buffer read from stream
+ int32_t skipbytes;
+ intptr_t tid; // thread id which does http requests
+ intptr_t mutex;
+ uint8_t nheaderpackets;
+ char *content_type;
+ char *content_name;
+ char *content_genre;
+ uint8_t status;
+ // flags (bitfields to save some space)
+ unsigned seektoend : 1; // indicates that next tell must return length
+ unsigned gotheader : 1; // tells that all headers (including ICY) were processed (to start reading body)
+ unsigned icyheader : 1; // tells that we're currently reading ICY headers
+ unsigned gotsomeheader : 1; // tells that we got some headers before body started
+} HTTP_FILE;
+
+static DB_vfs_t plugin;
+
+static char http_err[CURL_ERROR_SIZE];
+
+static size_t
+http_content_header_handler (void *ptr, size_t size, size_t nmemb, void *stream);
+
+static size_t
+http_curl_write (void *ptr, size_t size, size_t nmemb, void *stream) {
+// trace ("http_curl_write %d bytes\n", size * nmemb);
+ int avail = size * nmemb;
+ HTTP_FILE *fp = (HTTP_FILE *)stream;
+ if (fp->status == STATUS_ABORTED) {
+ trace ("vfs_curl STATUS_ABORTED at start of packet\n");
+ return 0;
+ }
+ if (fp->gotsomeheader) {
+ fp->gotheader = 1;
+ }
+ if (!fp->gotheader) {
+ // check if that's ICY
+ if (!fp->icyheader && avail >= 10 && !memcmp (ptr, "ICY 200 OK", 10)) {
+ trace ("icy headers in the stream\n");
+ fp->icyheader = 1;
+ }
+ if (fp->icyheader) {
+ if (fp->nheaderpackets > 10) {
+ fprintf (stderr, "vfs_curl: warning: seems like stream has unterminated ICY headers\n");
+ fp->gotheader = 1;
+ }
+ else {
+ trace ("parsing icy headers:\n%s\n", ptr);
+ fp->nheaderpackets++;
+ http_content_header_handler (ptr, size, nmemb, stream);
+ if (fp->gotheader) {
+ fp->gotheader = 0; // don't reset icy header
+ }
+ uint8_t *p = ptr;
+ int i;
+ for (i = 0; i < avail-3; i++) {
+ const char end[4] = { 0x0d, 0x0a, 0x0d, 0x0a };
+ if (!memcmp (p, end, 4)) {
+ trace ("icy headers end\n");
+ fp->gotheader = 1;
+ break;
+ }
+ p++;
+ }
+ avail = 0;
+ }
+ }
+ else {
+ fp->gotheader = 1;
+ }
+ }
+ while (avail > 0) {
+ deadbeef->mutex_lock (fp->mutex);
+ if (fp->status == STATUS_SEEK) {
+ trace ("vfs_curl seek request, aborting current request\n");
+ deadbeef->mutex_unlock (fp->mutex);
+ return 0;
+ }
+ if (fp->status == STATUS_ABORTED) {
+ trace ("vfs_curl STATUS_ABORTED in the middle of packet\n");
+ deadbeef->mutex_unlock (fp->mutex);
+ break;
+ }
+ int sz = BUFFER_SIZE/2 - fp->remaining; // number of bytes free in buffer
+ // don't allow to fill more than half -- used for seeking backwards
+ if (sz > 5000) { // wait until there are at least 5k bytes free
+ int cp = min (avail, sz);
+ int writepos = (fp->pos + fp->remaining) & BUFFER_MASK;
+ // copy 1st portion (before end of buffer
+ int part1 = BUFFER_SIZE - writepos;
+ // may not be more than total
+ part1 = min (part1, cp);
+ //trace ("part1=%d\n", part1);
+// trace ("writepos=%d, remaining=%d, avail=%d, free=%d, writing=%d, part1=%d, part2=%d\n", writepos, fp->remaining, avail, sz, cp, part1, cp-part1);
+ memcpy (fp->buffer+writepos, ptr, part1);
+ ptr += part1;
+ avail -= part1;
+ fp->remaining += part1;
+ cp -= part1;
+ if (cp > 0) {
+ memcpy (fp->buffer, ptr, cp);
+ ptr += cp;
+ avail -= cp;
+ fp->remaining += cp;
+ }
+ }
+ deadbeef->mutex_unlock (fp->mutex);
+ usleep (3000);
+ }
+
+// trace ("returning %d\n", nmemb * size - avail);
+ return nmemb * size - avail;
+}
+
+#if 0
+static size_t
+http_size_header_handler (void *ptr, size_t size, size_t nmemb, void *stream) {
+ assert (stream);
+ HTTP_FILE *fp = (HTTP_FILE *)stream;
+ // don't copy/allocate mem, just grep for "Content-Length: "
+ const char *cl = strcasestr (ptr, "Content-Length:");
+ if (cl) {
+ cl += 15;
+ while (*cl <= 0x20) {
+ cl++;
+ }
+ fp->length = atoi (cl);
+ trace ("vfs_curl: file size is %d bytes\n", fp->length);
+ return 0;
+ }
+ return size * nmemb;
+}
+#endif
+
+static const uint8_t *
+parse_header (const uint8_t *p, const uint8_t *e, uint8_t *key, int keysize, uint8_t *value, int valuesize) {
+ int sz; // will hold lenght of extracted string
+ const uint8_t *v; // pointer to current character
+ keysize--;
+ valuesize--;
+ *key = 0;
+ *value = 0;
+ v = p;
+ // find :
+ while (v < e && *v != 0x0d && *v != 0x0a && *v != ':') {
+ v++;
+ }
+ if (*v != ':') {
+ // skip linebreaks
+ while (v < e && (*v == 0x0d || *v == 0x0a)) {
+ v++;
+ }
+ return v;
+ }
+ // copy key
+ sz = v-p;
+ sz = min (keysize, sz);
+ memcpy (key, p, sz);
+ key[sz] = 0;
+
+ // skip whitespace
+ v++;
+ while (v < e && (*v == 0x20 || *v == 0x08)) {
+ v++;
+ }
+ if (*v == 0x0d || *v == 0x0a) {
+ // skip linebreaks
+ while (v < e && (*v == 0x0d || *v == 0x0a)) {
+ v++;
+ }
+ return v;
+ }
+ p = v;
+
+ // find linebreak
+ while (v < e && *v != 0x0d || *v == 0x0a) {
+ v++;
+ }
+
+ // copy value
+ sz = v-p;
+ sz = min (valuesize, sz);
+ memcpy (value, p, sz);
+ value[sz] = 0;
+
+ // skip linebreaks
+ while (v < e && (*v == 0x0d || *v == 0x0a)) {
+ v++;
+ }
+ return v;
+}
+
+static size_t
+http_content_header_handler (void *ptr, size_t size, size_t nmemb, void *stream) {
+ trace ("http_content_header_handler\n");
+ assert (stream);
+ HTTP_FILE *fp = (HTTP_FILE *)stream;
+ const uint8_t c_type_str[] ="Content-Type:";
+ const uint8_t icy_name_str[] ="icy-name:";
+ const uint8_t icy_genre_str[] ="icy-genre:";
+ const uint8_t *p = ptr;
+ const uint8_t *end = p + size*nmemb;
+ uint8_t key[256];
+ uint8_t value[256];
+ while (p < end) {
+ p = parse_header (p, end, key, sizeof (key), value, sizeof (value));
+ trace ("%skey=%s value=%s\n", fp->icyheader ? "[icy] " : "", key, value);
+ if (!strcasecmp (key, "Content-Type")) {
+ if (fp->content_type) {
+ free (fp->content_type);
+ }
+ fp->content_type = strdup (value);
+ }
+ else if (!strcasecmp (key, "Content-Length")) {
+ fp->length = atoi (value);
+ }
+ else if (!strcasecmp (key, "icy-name")) {
+ if (fp->content_name) {
+ free (fp->content_name);
+ }
+ fp->content_name = strdup (value);
+ }
+ else if (!strcasecmp (key, "icy-genre")) {
+ if (fp->content_genre) {
+ free (fp->content_genre);
+ }
+ fp->content_genre = strdup (value);
+ }
+ }
+ if (!fp->icyheader) {
+ fp->gotsomeheader = 1;
+ }
+ return size * nmemb;
+}
+
+static size_t
+http_curl_write_abort (void *ptr, size_t size, size_t nmemb, void *stream) {
+ return 0;
+}
+
+static int
+http_curl_control (void *stream, double dltotal, double dlnow, double ultotal, double ulnow) {
+ trace ("http_curl_control\n");
+ assert (stream);
+ HTTP_FILE *fp = (HTTP_FILE *)stream;
+ if (fp->status == STATUS_ABORTED) {
+ trace ("vfs_curl STATUS_ABORTED in progress callback\n");
+ return -1;
+ }
+ return 0;
+}
+
+static void
+http_thread_func (uintptr_t ctx) {
+ HTTP_FILE *fp = (HTTP_FILE *)ctx;
+ CURL *curl;
+ curl = curl_easy_init ();
+ fp->length = -1;
+ fp->status = STATUS_INITIAL;
+
+ int status;
+#if 0
+ // get filesize (once)
+ curl_easy_setopt (curl, CURLOPT_URL, fp->url);
+ curl_easy_setopt (curl, CURLOPT_NOBODY, 0);
+ curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1);
+ curl_easy_setopt (curl, CURLOPT_MAXREDIRS, 10);
+ curl_easy_setopt (curl, CURLOPT_HEADERFUNCTION, http_size_header_handler);
+ curl_easy_setopt (curl, CURLOPT_HEADERDATA, ctx);
+ curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, http_curl_write_abort);
+ curl_easy_setopt (curl, CURLOPT_PROGRESSFUNCTION, http_curl_control);
+ curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1);
+ curl_easy_setopt (curl, CURLOPT_MAXREDIRS, 10);
+ status = curl_easy_perform(curl);
+#if 0
+ if (status != 0) {
+ trace ("vfs_curl: curl_easy_perform failed while getting filesize, status %d\n", status);
+ }
+#endif
+#endif
+ fp->status = STATUS_STARTING;
+
+ trace ("vfs_curl: started loading data\n");
+ for (;;) {
+ curl_easy_reset (curl);
+ curl_easy_setopt (curl, CURLOPT_URL, fp->url);
+ curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 1);
+ curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, http_curl_write);
+ curl_easy_setopt (curl, CURLOPT_WRITEDATA, ctx);
+ curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, http_err);
+ curl_easy_setopt (curl, CURLOPT_BUFFERSIZE, BUFFER_SIZE/2);
+ curl_easy_setopt (curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_easy_setopt (curl, CURLOPT_HEADERFUNCTION, http_content_header_handler);
+ curl_easy_setopt (curl, CURLOPT_HEADERDATA, ctx);
+ curl_easy_setopt (curl, CURLOPT_PROGRESSFUNCTION, http_curl_control);
+ curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 0);
+ curl_easy_setopt (curl, CURLOPT_PROGRESSDATA, ctx);
+ // enable up to 10 redirects
+ curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1);
+ curl_easy_setopt (curl, CURLOPT_MAXREDIRS, 10);
+ if (fp->pos > 0) {
+ curl_easy_setopt (curl, CURLOPT_RESUME_FROM, fp->pos);
+ }
+ if (deadbeef->conf_get_int ("network.proxy", 0)) {
+ curl_easy_setopt (curl, CURLOPT_PROXY, deadbeef->conf_get_str ("network.proxy.address", ""));
+ curl_easy_setopt (curl, CURLOPT_PROXYPORT, deadbeef->conf_get_int ("network.proxy.port", 8080));
+ const char *type = deadbeef->conf_get_str ("network.proxy.type", "HTTP");
+ int curlproxytype = CURLPROXY_HTTP;
+ if (!strcasecmp (type, "HTTP")) {
+ curlproxytype = CURLPROXY_HTTP;
+ }
+#if LIBCURL_VERSION_MINOR >= 19 && LIBCURL_VERSION_PATCH >= 4
+ else if (!strcasecmp (type, "HTTP_1_0")) {
+ curlproxytype = CURLPROXY_HTTP_1_0;
+ }
+#endif
+#if LIBCURL_VERSION_MINOR >= 15 && LIBCURL_VERSION_PATCH >= 2
+ else if (!strcasecmp (type, "SOCKS4")) {
+ curlproxytype = CURLPROXY_SOCKS4;
+ }
+#endif
+ else if (!strcasecmp (type, "SOCKS5")) {
+ curlproxytype = CURLPROXY_SOCKS5;
+ }
+#if LIBCURL_VERSION_MINOR >= 18 && LIBCURL_VERSION_PATCH >= 0
+ else if (!strcasecmp (type, "SOCKS4A")) {
+ curlproxytype = CURLPROXY_SOCKS4A;
+ }
+ else if (!strcasecmp (type, "SOCKS5_HOSTNAME")) {
+ curlproxytype = CURLPROXY_SOCKS5_HOSTNAME;
+ }
+#endif
+ curl_easy_setopt (curl, CURLOPT_PROXYTYPE, curlproxytype);
+ }
+ deadbeef->mutex_lock (fp->mutex);
+ fp->status = STATUS_READING;
+ deadbeef->mutex_unlock (fp->mutex);
+ status = curl_easy_perform (curl);
+ trace ("vfs_curl: curl_easy_perform status=%d\n", status);
+ if (status != 0) {
+ trace ("curl error:\n%s\n", http_err);
+ }
+ deadbeef->mutex_lock (fp->mutex);
+ if (fp->status != STATUS_SEEK) {
+ deadbeef->mutex_unlock (fp->mutex);
+ break;
+ }
+ fp->status = STATUS_INITIAL;
+ trace ("seeking to %d\n", fp->pos);
+ deadbeef->mutex_unlock (fp->mutex);
+ }
+ curl_easy_cleanup (curl);
+
+ deadbeef->mutex_lock (fp->mutex);
+ fp->status = STATUS_FINISHED;
+ deadbeef->mutex_unlock (fp->mutex);
+ fp->tid = 0;
+}
+
+static void
+http_start_streamer (HTTP_FILE *fp) {
+ fp->mutex = deadbeef->mutex_create ();
+ fp->tid = deadbeef->thread_start (http_thread_func, (uintptr_t)fp);
+}
+
+static DB_FILE *
+http_open (const char *fname) {
+ trace ("http_open\n");
+ HTTP_FILE *fp = malloc (sizeof (HTTP_FILE));
+ memset (fp, 0, sizeof (HTTP_FILE));
+ fp->vfs = &plugin;
+ fp->url = strdup (fname);
+ return (DB_FILE*)fp;
+}
+
+static void
+http_close (DB_FILE *stream) {
+ trace ("http_close\n");
+ assert (stream);
+ HTTP_FILE *fp = (HTTP_FILE *)stream;
+ if (fp->tid) {
+ deadbeef->mutex_lock (fp->mutex);
+ fp->status = STATUS_ABORTED;
+ deadbeef->mutex_unlock (fp->mutex);
+ deadbeef->thread_join (fp->tid);
+ deadbeef->mutex_free (fp->mutex);
+ }
+ if (fp->content_type) {
+ free (fp->content_type);
+ }
+ if (fp->content_name) {
+ free (fp->content_name);
+ }
+ if (fp->content_genre) {
+ free (fp->content_genre);
+ }
+ if (fp->url) {
+ free (fp->url);
+ }
+ free (stream);
+}
+
+static size_t
+http_read (void *ptr, size_t size, size_t nmemb, DB_FILE *stream) {
+// trace ("http_read %d\n", size*nmemb);
+ assert (stream);
+ assert (ptr);
+ HTTP_FILE *fp = (HTTP_FILE *)stream;
+ fp->seektoend = 0;
+ int sz = size * nmemb;
+// assert (size * nmemb <= BUFFER_SIZE);
+// trace ("readpos=%d, readsize=%d\n", fp->pos & BUFFER_SIZE, sz);
+ if (!fp->tid) {
+ http_start_streamer (fp);
+ }
+ while (fp->status != STATUS_FINISHED && sz > 0)
+ {
+ // wait until data is available
+ while ((fp->remaining == 0 || fp->skipbytes > 0) && fp->status != STATUS_FINISHED) {
+ deadbeef->mutex_lock (fp->mutex);
+ int skip = min (fp->remaining, fp->skipbytes);
+ if (skip > 0) {
+// trace ("skipping %d bytes\n");
+ fp->pos += skip;
+ fp->remaining -= skip;
+ fp->skipbytes -= skip;
+ }
+ deadbeef->mutex_unlock (fp->mutex);
+ usleep (3000);
+ }
+ // trace ("buffer remaining: %d\n", fp->remaining);
+ deadbeef->mutex_lock (fp->mutex);
+ int cp = min (sz, fp->remaining);
+ int readpos = fp->pos & BUFFER_MASK;
+ int part1 = BUFFER_SIZE-readpos;
+ part1 = min (part1, cp);
+// trace ("readpos=%d, remaining=%d, req=%d, cp=%d, part1=%d, part2=%d\n", readpos, fp->remaining, sz, cp, part1, cp-part1);
+ memcpy (ptr, fp->buffer+readpos, part1);
+ fp->remaining -= part1;
+ fp->pos += part1;
+ sz -= part1;
+ ptr += part1;
+ cp -= part1;
+ if (cp > 0) {
+ memcpy (ptr, fp->buffer, cp);
+ fp->remaining -= cp;
+ fp->pos += cp;
+ sz -= cp;
+ ptr += cp;
+ }
+ deadbeef->mutex_unlock (fp->mutex);
+ }
+// if (size * nmemb == 1) {
+// trace ("%02x\n", (unsigned int)*((uint8_t*)ptr));
+// }
+ return size * nmemb - sz;
+}
+
+static int
+http_seek (DB_FILE *stream, int64_t offset, int whence) {
+ trace ("http_seek %x %d\n", offset, whence);
+ assert (stream);
+ HTTP_FILE *fp = (HTTP_FILE *)stream;
+ fp->seektoend = 0;
+ if (whence == SEEK_END) {
+ if (offset == 0) {
+ fp->seektoend = 1;
+ return 0;
+ }
+ trace ("vfs_curl: can't seek in curl stream relative to EOF\n");
+ return -1;
+ }
+ if (!fp->tid) {
+ if (offset == 0 && (whence == SEEK_SET || whence == SEEK_CUR)) {
+ return 0;
+ }
+ else {
+ trace ("vfs_curl: cannot do seek(%d,%d)\n", offset, whence);
+ return -1;
+ }
+ }
+ deadbeef->mutex_lock (fp->mutex);
+ if (whence == SEEK_CUR) {
+ whence = SEEK_SET;
+ offset = fp->pos + offset;
+ }
+ if (whence == SEEK_SET) {
+ if (fp->pos == offset) {
+ fp->skipbytes = 0;
+ deadbeef->mutex_unlock (fp->mutex);
+ return 0;
+ }
+ else if (fp->pos < offset && fp->pos + BUFFER_SIZE > offset) {
+ fp->skipbytes = offset - fp->pos;
+ deadbeef->mutex_unlock (fp->mutex);
+ return 0;
+ }
+ else if (fp->pos-offset >= 0 && fp->pos-offset <= BUFFER_SIZE-fp->remaining) {
+ fp->skipbytes = 0;
+ fp->remaining += fp->pos - offset;
+ fp->pos = offset;
+ deadbeef->mutex_unlock (fp->mutex);
+ return 0;
+ }
+ }
+ // reset stream, and start over
+ fp->skipbytes = 0;
+ fp->remaining = 0;
+ fp->pos = offset;
+ fp->status = STATUS_SEEK;
+
+ deadbeef->mutex_unlock (fp->mutex);
+ return 0;
+}
+
+static int64_t
+http_tell (DB_FILE *stream) {
+ assert (stream);
+ HTTP_FILE *fp = (HTTP_FILE *)stream;
+ if (fp->seektoend) {
+ return fp->length;
+ }
+ return fp->pos + fp->skipbytes;
+}
+
+static void
+http_rewind (DB_FILE *stream) {
+ trace ("http_rewind\n");
+ assert (stream);
+ HTTP_FILE *fp = (HTTP_FILE *)stream;
+ if (fp->tid) {
+ deadbeef->mutex_lock (fp->mutex);
+ fp->status = STATUS_SEEK;
+ fp->remaining = 0;
+ fp->pos = 0;
+ deadbeef->mutex_unlock (fp->mutex);
+ }
+}
+
+static int64_t
+http_getlength (DB_FILE *stream) {
+ trace ("http_getlength\n");
+ assert (stream);
+ HTTP_FILE *fp = (HTTP_FILE *)stream;
+ if (fp->status == STATUS_ABORTED) {
+ return -1;
+ }
+ if (!fp->tid) {
+ http_start_streamer (fp);
+ }
+ while (fp->status == STATUS_INITIAL) {
+ usleep (3000);
+ }
+ //trace ("length: %d\n", fp->length);
+ return fp->length;
+}
+
+static const char *
+http_get_content_type (DB_FILE *stream) {
+ trace ("http_get_content_type\n");
+ assert (stream);
+ HTTP_FILE *fp = (HTTP_FILE *)stream;
+ if (fp->status == STATUS_ABORTED) {
+ return NULL;
+ }
+ if (fp->gotheader) {
+ return fp->content_type;
+ }
+ if (!fp->tid) {
+ http_start_streamer (fp);
+ }
+ trace ("http_get_content_type waiting for response...\n");
+ while (fp->status != STATUS_FINISHED && fp->status != STATUS_ABORTED && !fp->gotheader) {
+ usleep (3000);
+ }
+ return fp->content_type;
+}
+
+static const char *
+http_get_content_name (DB_FILE *stream) {
+ trace ("http_get_content_name\n");
+ assert (stream);
+ HTTP_FILE *fp = (HTTP_FILE *)stream;
+ if (fp->status == STATUS_ABORTED) {
+ return NULL;
+ }
+ if (fp->gotheader) {
+ return fp->content_name;
+ }
+ if (!fp->tid) {
+ http_start_streamer (fp);
+ }
+ trace ("http_get_content_name waiting for response...\n");
+ while (fp->status != STATUS_FINISHED && fp->status != STATUS_ABORTED && !fp->gotheader) {
+ usleep (3000);
+ }
+ return fp->content_name;
+}
+
+static const char *
+http_get_content_genre (DB_FILE *stream) {
+ trace ("http_get_content_genre\n");
+ assert (stream);
+ HTTP_FILE *fp = (HTTP_FILE *)stream;
+ if (fp->status == STATUS_ABORTED) {
+ return NULL;
+ }
+ if (fp->gotheader) {
+ return fp->content_genre;
+ }
+ if (!fp->tid) {
+ http_start_streamer (fp);
+ }
+ trace ("http_get_content_genre waiting for response...\n");
+ while (fp->status != STATUS_FINISHED && fp->status != STATUS_ABORTED && !fp->gotheader) {
+ usleep (3000);
+ }
+ return fp->content_genre;
+}
+
+static void
+http_stop (DB_FILE *stream) {
+ trace ("http_stop\n");
+ assert (stream);
+ HTTP_FILE *fp = (HTTP_FILE *)stream;
+ fp->status = STATUS_ABORTED;
+}
+
+static const char *scheme_names[] = { "http://", "ftp://", NULL };
+
+// standard stdio vfs
+static DB_vfs_t plugin = {
+ DB_PLUGIN_SET_API_VERSION
+ .plugin.version_major = 0,
+ .plugin.version_minor = 1,
+ .plugin.type = DB_PLUGIN_VFS,
+ .plugin.name = "cURL vfs",
+ .plugin.descr = "http and ftp streaming module using libcurl, with ICY protocol support",
+ .plugin.author = "Alexey Yakovenko",
+ .plugin.email = "waker@users.sourceforge.net",
+ .plugin.website = "http://deadbeef.sf.net",
+ .open = http_open,
+ .close = http_close,
+ .read = http_read,
+ .seek = http_seek,
+ .tell = http_tell,
+ .rewind = http_rewind,
+ .getlength = http_getlength,
+ .get_content_type = http_get_content_type,
+ .get_content_name = http_get_content_name,
+ .get_content_genre = http_get_content_type,
+ .stop = http_stop,
+ .scheme_names = scheme_names,
+ .streaming = 1
+};
+
+DB_plugin_t *
+vfs_curl_load (DB_functions_t *api) {
+ deadbeef = api;
+ return DB_PLUGIN (&plugin);
+}
diff --git a/plugins/vorbis/Makefile.am b/plugins/vorbis/Makefile.am
index 09b30479..a54da1b5 100644
--- a/plugins/vorbis/Makefile.am
+++ b/plugins/vorbis/Makefile.am
@@ -1,3 +1,5 @@
+if HAVE_VORBISFILE
+if HAVE_VORBIS
vorbisdir = $(libdir)/$(PACKAGE)
pkglib_LTLIBRARIES = vorbis.la
vorbis_la_SOURCES = vorbis.c
@@ -5,3 +7,5 @@ vorbis_la_LDFLAGS = -module
vorbis_la_LIBADD = $(LDADD) $(VORBIS_LIBS)
AM_CFLAGS = $(CFLAGS) -std=c99
+endif
+endif
diff --git a/plugins/vorbis/vorbis.c b/plugins/vorbis/vorbis.c
index 26cd3051..31e252eb 100644
--- a/plugins/vorbis/vorbis.c
+++ b/plugins/vorbis/vorbis.c
@@ -18,7 +18,6 @@
#include <vorbis/codec.h>
#include <vorbis/vorbisfile.h>
#include <string.h>
-#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_CONFIG_H
# include <config.h>
@@ -28,39 +27,135 @@
#define min(x,y) ((x)<(y)?(x):(y))
#define max(x,y) ((x)>(y)?(x):(y))
-//#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+//#define trace(...) { fprintf (stderr, __VA_ARGS__); }
#define trace(fmt,...)
static DB_decoder_t plugin;
static DB_functions_t *deadbeef;
-static FILE *file;
+static DB_FILE *file;
static OggVorbis_File vorbis_file;
static vorbis_info *vi;
static int cur_bit_stream;
static int startsample;
static int endsample;
static int currentsample;
+static int last_comment_update;
+static DB_playItem_t *ptrack;
static void
cvorbis_free (void);
+static size_t
+cvorbis_fread (void *ptr, size_t size, size_t nmemb, void *datasource) {
+ return deadbeef->fread (ptr, size, nmemb, datasource);
+}
+
+static int
+cvorbis_fseek (void *datasource, ogg_int64_t offset, int whence) {
+ DB_FILE *f = (DB_FILE *)datasource;
+ return deadbeef->fseek (datasource, offset, whence);
+}
+
+static int
+cvorbis_fclose (void *datasource) {
+ deadbeef->fclose (datasource);
+ return 0;
+}
+
+static long
+cvorbis_ftell (void *datasource) {
+ return deadbeef->ftell (datasource);
+}
+
+static void
+update_vorbis_comments (DB_playItem_t *it, vorbis_comment *vc) {
+ if (vc) {
+ deadbeef->pl_delete_all_meta (it);
+ deadbeef->pl_add_meta (it, "vendor", vc->vendor);
+ for (int i = 0; i < vc->comments; i++) {
+ if (!strncasecmp (vc->user_comments[i], "artist=", 7)) {
+ deadbeef->pl_add_meta (it, "artist", vc->user_comments[i] + 7);
+ }
+ else if (!strncasecmp (vc->user_comments[i], "album=", 6)) {
+ deadbeef->pl_add_meta (it, "album", vc->user_comments[i] + 6);
+ }
+ else if (!strncasecmp (vc->user_comments[i], "title=", 6)) {
+ deadbeef->pl_add_meta (it, "title", vc->user_comments[i] + 6);
+ }
+ else if (!strncasecmp (vc->user_comments[i], "date=", 5)) {
+ deadbeef->pl_add_meta (it, "date", vc->user_comments[i] + 5);
+ }
+ else if (!strncasecmp (vc->user_comments[i], "replaygain_album_gain=", 22)) {
+ it->replaygain_album_gain = atof (vc->user_comments[i] + 22);
+ }
+ else if (!strncasecmp (vc->user_comments[i], "replaygain_album_peak=", 22)) {
+ it->replaygain_album_peak = atof (vc->user_comments[i] + 22);
+ }
+ else if (!strncasecmp (vc->user_comments[i], "replaygain_track_gain=", 22)) {
+ it->replaygain_track_gain = atof (vc->user_comments[i] + 22);
+ }
+ else if (!strncasecmp (vc->user_comments[i], "replaygain_track_peak=", 22)) {
+ it->replaygain_track_peak = atof (vc->user_comments[i] + 22);
+ }
+ }
+ }
+ deadbeef->pl_add_meta (it, "title", NULL);
+}
+
static int
cvorbis_init (DB_playItem_t *it) {
file = NULL;
vi = NULL;
cur_bit_stream = -1;
+ ptrack = it;
- file = fopen (it->fname, "rb");
+ file = plugin.info.file = deadbeef->fopen (it->fname);
if (!file) {
return -1;
}
-
memset (&plugin.info, 0, sizeof (plugin.info));
- ov_open (file, &vorbis_file, NULL, 0);
+
+ int ln = deadbeef->fgetlength (file);
+ if (file->vfs->streaming && ln == -1) {
+ ov_callbacks ovcb = {
+ .read_func = cvorbis_fread,
+ .seek_func = NULL,
+ .close_func = cvorbis_fclose,
+ .tell_func = NULL
+ };
+
+ int err;
+ trace ("calling ov_open_callbacks\n");
+ if (err = ov_open_callbacks (file, &vorbis_file, NULL, 0, ovcb) != 0) {
+ trace ("ov_open_callbacks returned %d\n", err);
+ plugin.free ();
+ return -1;
+ }
+ deadbeef->pl_set_item_duration (it, -1);
+ }
+ else
+ {
+ ov_callbacks ovcb = {
+ .read_func = cvorbis_fread,
+ .seek_func = cvorbis_fseek,
+ .close_func = cvorbis_fclose,
+ .tell_func = cvorbis_ftell
+ };
+
+ trace ("calling ov_open_callbacks\n");
+ int err;
+ if (err = ov_open_callbacks (file, &vorbis_file, NULL, 0, ovcb) != 0) {
+ trace ("ov_open_callbacks returned %d\n", err);
+ plugin.free ();
+ return -1;
+ }
+ deadbeef->pl_set_item_duration (it, ov_time_total (&vorbis_file, -1));
+ }
vi = ov_info (&vorbis_file, -1);
if (!vi) { // not a vorbis stream
cvorbis_free ();
+ trace ("not a vorbis stream\n");
return -1;
}
plugin.info.bps = 16;
@@ -69,21 +164,36 @@ cvorbis_init (DB_playItem_t *it) {
plugin.info.samplerate = vi->rate;
plugin.info.readpos = 0;
currentsample = 0;
- if (it->endsample > 0) {
- startsample = it->startsample;
- endsample = it->endsample;
- plugin.seek_sample (0);
+ if (!file->vfs->streaming) {
+ if (it->endsample > 0) {
+ startsample = it->startsample;
+ endsample = it->endsample;
+ plugin.seek_sample (0);
+ }
+ else {
+ startsample = 0;
+ endsample = ov_pcm_total (&vorbis_file, -1)-1;
+ }
}
else {
startsample = 0;
- endsample = ov_pcm_total (&vorbis_file, -1)-1;
+ if (deadbeef->pl_get_item_duration (it) < 0) {
+ endsample = -1;
+ }
+ else {
+ endsample = ov_pcm_total (&vorbis_file, -1)-1;
+ }
+ vorbis_comment *vc = ov_comment (&vorbis_file, -1);
+ update_vorbis_comments (it, vc);
}
return 0;
}
static void
cvorbis_free (void) {
+ plugin.info.file = NULL;
if (file) {
+ ptrack = NULL;
ov_clear (&vorbis_file);
//fclose (file); //-- ov_clear closes it
file = NULL;
@@ -93,13 +203,31 @@ cvorbis_free (void) {
static int
cvorbis_read (char *bytes, int size) {
- if (currentsample + size / (2 * plugin.info.channels) > endsample) {
- size = (endsample - currentsample + 1) * 2 * plugin.info.channels;
- trace ("size truncated to %d bytes, cursample=%d, endsample=%d, totalsamples=%d\n", size, currentsample, endsample, ov_pcm_total (&vorbis_file, -1));
- if (size <= 0) {
- return 0;
+// trace ("cvorbis_read %d bytes\n", size);
+ if (!file->vfs->streaming) {
+ if (currentsample + size / (2 * plugin.info.channels) > endsample) {
+ size = (endsample - currentsample + 1) * 2 * plugin.info.channels;
+// trace ("size truncated to %d bytes, cursample=%d, endsample=%d, totalsamples=%d\n", size, currentsample, endsample, ov_pcm_total (&vorbis_file, -1));
+ if (size <= 0) {
+ return 0;
+ }
+ }
+ }
+ else {
+ if (ptrack && currentsample - last_comment_update > 5 * plugin.info.samplerate) {
+ int idx = deadbeef->pl_get_idx_of (ptrack);
+ if (idx >= 0) {
+ last_comment_update = currentsample;
+ vorbis_comment *vc = ov_comment (&vorbis_file, -1);
+ update_vorbis_comments (ptrack, vc);
+ deadbeef->sendmessage (M_TRACKCHANGED, 0, idx, 0);
+ }
+ else {
+ ptrack = NULL;
+ }
}
}
+// trace ("cvorbis_read %d bytes[2]\n", size);
int initsize = size;
for (;;)
{
@@ -111,6 +239,23 @@ cvorbis_read (char *bytes, int size) {
long ret=ov_read (&vorbis_file, bytes, size, endianess, 2, 1, &cur_bit_stream);
if (ret <= 0)
{
+ if (ret < 0) {
+ trace ("ov_read returned %d\n", ret);
+ switch (ret) {
+ case OV_HOLE:
+ trace ("OV_HOLE\n");
+ break;
+ case OV_EBADLINK:
+ trace ("OV_EBADLINK\n");
+ break;
+ case OV_EINVAL:
+ trace ("OV_EINVAL\n");
+ break;
+ }
+ }
+ if (ret == OV_HOLE) {
+ continue;
+ }
// error or eof
break;
}
@@ -127,6 +272,7 @@ cvorbis_read (char *bytes, int size) {
}
}
plugin.info.readpos = (float)(ov_pcm_tell(&vorbis_file)-startsample)/vi->rate;
+// trace ("cvorbis_read got %d bytes\n", initsize-size);
return initsize - size;
}
@@ -141,7 +287,7 @@ cvorbis_seek_sample (int sample) {
return -1;
int tell = ov_pcm_tell (&vorbis_file);
if (tell != sample) {
- fprintf (stderr, "oggvorbis: failed to do sample-accurate seek (%d->%d)\n", sample, tell);
+ trace ("oggvorbis: failed to do sample-accurate seek (%d->%d)\n", sample, tell);
}
currentsample = sample;
plugin.info.readpos = (float)(ov_pcm_tell(&vorbis_file) - startsample)/vi->rate;
@@ -156,15 +302,33 @@ cvorbis_seek (float time) {
static DB_playItem_t *
cvorbis_insert (DB_playItem_t *after, const char *fname) {
// check for validity
- FILE *fp = fopen (fname, "rb");
+ DB_FILE *fp = deadbeef->fopen (fname);
if (!fp) {
+ trace ("vorbis: failed to fopen %s\n", fname);
return NULL;
}
+ if (fp->vfs->streaming) {
+ DB_playItem_t *it = deadbeef->pl_item_alloc ();
+ it->decoder = &plugin;
+ it->fname = strdup (fname);
+ it->filetype = "OggVorbis";
+ deadbeef->pl_set_item_duration (it, -1);
+ deadbeef->pl_add_meta (it, "title", NULL);
+ after = deadbeef->pl_insert_item (after, it);
+ return after;
+ }
+ ov_callbacks ovcb = {
+ .read_func = cvorbis_fread,
+ .seek_func = cvorbis_fseek,
+ .close_func = cvorbis_fclose,
+ .tell_func = cvorbis_ftell
+ };
OggVorbis_File vorbis_file;
vorbis_info *vi;
- ov_open (fp, &vorbis_file, NULL, 0);
+ ov_open_callbacks (fp, &vorbis_file, NULL, 0, ovcb);
vi = ov_info (&vorbis_file, -1);
if (!vi) { // not a vorbis stream
+ trace ("vorbis: failed to ov_open %s\n", fname);
return NULL;
}
float duration = ov_time_total (&vorbis_file, -1);
@@ -179,49 +343,34 @@ cvorbis_insert (DB_playItem_t *after, const char *fname) {
it->decoder = &plugin;
it->fname = strdup (fname);
it->filetype = "OggVorbis";
- it->duration = duration;
+ deadbeef->pl_set_item_duration (it, duration);
// metainfo
- int title_added = 0;
vorbis_comment *vc = ov_comment (&vorbis_file, -1);
- if (vc) {
- deadbeef->pl_add_meta (it, "vendor", vc->vendor);
- for (int i = 0; i < vc->comments; i++) {
- if (!strncasecmp (vc->user_comments[i], "artist=", 7)) {
- deadbeef->pl_add_meta (it, "artist", vc->user_comments[i] + 7);
- }
- else if (!strncasecmp (vc->user_comments[i], "album=", 6)) {
- deadbeef->pl_add_meta (it, "album", vc->user_comments[i] + 6);
- }
- else if (!strncasecmp (vc->user_comments[i], "title=", 6)) {
- deadbeef->pl_add_meta (it, "title", vc->user_comments[i] + 6);
- title_added = 1;
- }
- else if (!strncasecmp (vc->user_comments[i], "date=", 5)) {
- deadbeef->pl_add_meta (it, "date", vc->user_comments[i] + 5);
- }
- else if (!strncasecmp (vc->user_comments[i], "replaygain_album_gain=", 22)) {
- it->replaygain_album_gain = atof (vc->user_comments[i] + 22);
- }
- else if (!strncasecmp (vc->user_comments[i], "replaygain_album_peak=", 22)) {
- it->replaygain_album_peak = atof (vc->user_comments[i] + 22);
- }
- else if (!strncasecmp (vc->user_comments[i], "replaygain_track_gain=", 22)) {
- it->replaygain_track_gain = atof (vc->user_comments[i] + 22);
- }
- else if (!strncasecmp (vc->user_comments[i], "replaygain_track_peak=", 22)) {
- it->replaygain_track_peak = atof (vc->user_comments[i] + 22);
- }
- }
- }
- if (!title_added) {
- deadbeef->pl_add_meta (it, "title", NULL);
- }
+ update_vorbis_comments (it, vc);
ov_clear (&vorbis_file);
after = deadbeef->pl_insert_item (after, it);
return after;
}
+static int
+vorbis_trackdeleted (DB_event_song_t *ev, uintptr_t data) {
+ if (ev->song == ptrack) {
+ ptrack = NULL;
+ }
+ return 0;
+}
+
+static int
+vorbis_start (void) {
+ deadbeef->ev_subscribe (DB_PLUGIN (&plugin), DB_EV_TRACKDELETED, DB_CALLBACK (vorbis_trackdeleted), 0);
+}
+
+static int
+vorbis_stop (void) {
+ deadbeef->ev_unsubscribe (DB_PLUGIN (&plugin), DB_EV_TRACKDELETED, DB_CALLBACK (vorbis_trackdeleted), 0);
+}
+
static const char * exts[] = { "ogg", NULL };
static const char *filetypes[] = { "OggVorbis", NULL };
@@ -232,9 +381,12 @@ static DB_decoder_t plugin = {
.plugin.version_minor = 1,
.plugin.type = DB_PLUGIN_DECODER,
.plugin.name = "OggVorbis decoder",
+ .plugin.descr = "OggVorbis decoder using standard xiph.org libraries",
.plugin.author = "Alexey Yakovenko",
.plugin.email = "waker@users.sourceforge.net",
.plugin.website = "http://deadbeef.sf.net",
+ .plugin.start = vorbis_start,
+ .plugin.stop = vorbis_stop,
.init = cvorbis_init,
.free = cvorbis_free,
.read_int16 = cvorbis_read,
diff --git a/plugins/wavpack/Makefile.am b/plugins/wavpack/Makefile.am
index 9a86351f..10f44cf1 100644
--- a/plugins/wavpack/Makefile.am
+++ b/plugins/wavpack/Makefile.am
@@ -1,3 +1,4 @@
+if HAVE_WAVPACK
wavpackdir = $(libdir)/$(PACKAGE)
pkglib_LTLIBRARIES = wavpack.la
wavpack_la_SOURCES = wavpack.c
@@ -5,3 +6,4 @@ wavpack_la_LDFLAGS = -module
wavpack_la_LIBADD = $(LDADD) $(WAVPACK_LIBS)
AM_CFLAGS = $(CFLAGS) -std=c99
+endif
diff --git a/plugins/wavpack/wavpack.c b/plugins/wavpack/wavpack.c
index 4fdbfc14..5400a600 100644
--- a/plugins/wavpack/wavpack.c
+++ b/plugins/wavpack/wavpack.c
@@ -19,6 +19,7 @@
#include <string.h>
#include <wavpack/wavpack.h>
+#include <stdio.h>
#include "../../deadbeef.h"
#define min(x,y) ((x)<(y)?(x):(y))
@@ -31,6 +32,7 @@ static DB_decoder_t plugin;
static DB_functions_t *deadbeef;
typedef struct {
+ DB_FILE *file;
WavpackContext *ctx;
int startsample;
int endsample;
@@ -38,11 +40,68 @@ typedef struct {
static wvctx_t wvctx;
+int32_t wv_read_bytes(void *id, void *data, int32_t bcount) {
+ trace ("wv_read_bytes\n");
+ return deadbeef->fread (data, 1, bcount, id);
+}
+
+uint32_t wv_get_pos(void *id) {
+ trace ("wv_get_pos\n");
+ return deadbeef->ftell (id);
+}
+
+int wv_set_pos_abs(void *id, uint32_t pos) {
+ trace ("wv_set_pos_abs\n");
+ return deadbeef->fseek (id, pos, SEEK_SET);
+}
+int wv_set_pos_rel(void *id, int32_t delta, int mode) {
+ trace ("wv_set_pos_rel\n");
+ return deadbeef->fseek (id, delta, SEEK_CUR);
+}
+int wv_push_back_byte(void *id, int c) {
+ trace ("wv_push_back_byte\n");
+ deadbeef->fseek (id, -1, SEEK_CUR);
+ return deadbeef->ftell (id);
+}
+uint32_t wv_get_length(void *id) {
+ trace ("wv_get_length\n");
+ size_t pos = deadbeef->ftell (id);
+ deadbeef->fseek (id, 0, SEEK_END);
+ size_t sz = deadbeef->ftell (id);
+ deadbeef->fseek (id, pos, SEEK_SET);
+ return sz;
+}
+int wv_can_seek(void *id) {
+ trace ("wv_can_seek\n");
+ return 1;
+}
+
+int32_t wv_write_bytes (void *id, void *data, int32_t bcount) {
+ return 0;
+}
+
+static WavpackStreamReader wsr = {
+ .read_bytes = wv_read_bytes,
+ .get_pos = wv_get_pos,
+ .set_pos_abs = wv_set_pos_abs,
+ .set_pos_rel = wv_set_pos_rel,
+ .push_back_byte = wv_push_back_byte,
+ .get_length = wv_get_length,
+ .can_seek = wv_can_seek,
+ .write_bytes = wv_write_bytes
+};
+
static int
wv_init (DB_playItem_t *it) {
memset (&wvctx, 0, sizeof (wvctx));
- wvctx.ctx = WavpackOpenFileInput (it->fname, NULL, OPEN_2CH_MAX|OPEN_WVC, 0);
+
+ wvctx.file = deadbeef->fopen (it->fname);
+ if (!wvctx.file) {
+ return -1;
+ }
+ wvctx.ctx = WavpackOpenFileInputEx (&wsr, wvctx.file, NULL, NULL, OPEN_2CH_MAX/*|OPEN_WVC*/, 0);
if (!wvctx.ctx) {
+ plugin.free ();
return -1;
}
plugin.info.bps = WavpackGetBitsPerSample (wvctx.ctx);
@@ -66,8 +125,13 @@ wv_init (DB_playItem_t *it) {
static void
wv_free (void) {
+ if (wvctx.file) {
+ deadbeef->fclose (wvctx.file);
+ wvctx.file = NULL;
+ }
if (wvctx.ctx) {
WavpackCloseFile (wvctx.ctx);
+ wvctx.ctx = NULL;
}
memset (&wvctx, 0, sizeof (wvctx));
}
@@ -141,9 +205,15 @@ wv_seek (float sec) {
static DB_playItem_t *
wv_insert (DB_playItem_t *after, const char *fname) {
- WavpackContext *ctx = WavpackOpenFileInput (fname, NULL, 0, 0);
+
+ DB_FILE *fp = deadbeef->fopen (fname);
+ if (!fp) {
+ return NULL;
+ }
+ WavpackContext *ctx = WavpackOpenFileInputEx (&wsr, fp, NULL, NULL, 0, 0);
if (!ctx) {
trace ("WavpackOpenFileInput failed");
+ deadbeef->fclose (fp);
return NULL;
}
int totalsamples = WavpackGetNumSamples (ctx);
@@ -152,6 +222,7 @@ wv_insert (DB_playItem_t *after, const char *fname) {
float duration = (float)totalsamples / samplerate;
DB_playItem_t *cue_after = deadbeef->pl_insert_cue (after, fname, &plugin, "wv", totalsamples, samplerate);
if (cue_after) {
+ deadbeef->fclose (fp);
return cue_after;
}
@@ -159,22 +230,19 @@ wv_insert (DB_playItem_t *after, const char *fname) {
it->decoder = &plugin;
it->fname = strdup (fname);
it->filetype = "wv";
- it->duration = duration;
+ deadbeef->pl_set_item_duration (it, duration);
trace ("wv: totalsamples=%d, samplerate=%d, duration=%f\n", totalsamples, samplerate, duration);
- FILE *fp = fopen (fname, "rb");
- if (fp) {
- int apeerr = deadbeef->junk_read_ape (it, fp);
- if (!apeerr) {
- trace ("wv: ape tag found\n");
- }
- int v1err = deadbeef->junk_read_id3v1 (it, fp);
- if (!v1err) {
- trace ("wv: id3v1 tag found\n");
- }
- fclose (fp);
+ int apeerr = deadbeef->junk_read_ape (it, fp);
+ if (!apeerr) {
+ trace ("wv: ape tag found\n");
+ }
+ int v1err = deadbeef->junk_read_id3v1 (it, fp);
+ if (!v1err) {
+ trace ("wv: id3v1 tag found\n");
}
+ deadbeef->fclose (fp);
deadbeef->pl_add_meta (it, "title", NULL);
after = deadbeef->pl_insert_item (after, it);
@@ -191,6 +259,7 @@ static DB_decoder_t plugin = {
.plugin.version_minor = 1,
.plugin.type = DB_PLUGIN_DECODER,
.plugin.name = "WavPack decoder",
+ .plugin.descr = ".wv player using libwavpack",
.plugin.author = "Alexey Yakovenko",
.plugin.email = "waker@users.sourceforge.net",
.plugin.website = "http://deadbeef.sf.net",
diff --git a/search.c b/search.c
index a1bf4665..1ce99edd 100644
--- a/search.c
+++ b/search.c
@@ -33,9 +33,8 @@
#include "search.h"
#include "gtkplaylist.h"
#include "messagepump.h"
-#include "messages.h"
-
#include "utf8.h"
+#include "deadbeef.h"
extern GtkWidget *searchwin;
struct playItem_s *search_current = NULL;
diff --git a/session.c b/session.c
index 01f4a6f1..d99e3c4a 100644
--- a/session.c
+++ b/session.c
@@ -19,50 +19,24 @@
#include <stdio.h>
#include <string.h>
#include <stdint.h>
+#include <unistd.h>
#include "session.h"
#include "common.h"
+#include "deadbeef.h"
+#include "conf.h"
-#define SESS_CURRENT_VER 3
+#define SESS_CURRENT_VER 4
// changelog:
+// version 5 everything moved to common config
+// version 4 column settings moved to common config
// version 3 adds column widths
// NOTE: dont forget to update session_reset when changing that
-char session_dir[2048];
-float session_volume;
-int8_t session_playlist_order;
-int8_t session_playlist_looping;
-int8_t session_scroll_follows_playback = 1;
-int session_win_attrs[5] = { 40, 40, 500, 300, 0 };
-#define PL_MAX_COLUMNS 5
-static int session_main_colwidths[PL_MAX_COLUMNS] = { 50, 150, 50, 150, 50 };
-static int session_main_numcols = 5;
-static int session_search_colwidths[PL_MAX_COLUMNS] = { 0, 150, 50, 150, 50 };;
-static int session_search_numcols = 5;
-static uint8_t sessfile_magic[] = { 0xdb, 0xef, 0x5e, 0x55 }; // dbefsess in hexspeak
+static const uint8_t sessfile_magic[] = { 0xdb, 0xef, 0x5e, 0x55 }; // dbefsess in hexspeak
void
session_reset (void) {
- session_volume = 0;
- session_dir[0] = 0;
- session_playlist_looping = 0;
- session_playlist_order = 0;
- session_scroll_follows_playback = 1;
- session_win_attrs[0] = 40;
- session_win_attrs[1] = 40;
- session_win_attrs[2] = 500;
- session_win_attrs[3] = 300;
- session_win_attrs[4] = 0;
- {
- session_main_numcols = 5;
- int colwidths[] = { 50, 150, 50, 150, 50 };
- memcpy (session_main_colwidths, colwidths, sizeof (colwidths));
- }
- {
- session_search_numcols = 5;
- int colwidths[] = { 0, 150, 50, 150, 50 };
- memcpy (session_search_colwidths, colwidths, sizeof (colwidths));
- }
}
static int
@@ -143,6 +117,8 @@ read_i32_be (uint32_t *pval, FILE *fp) {
int
session_save (const char *fname) {
+ unlink (fname);
+#if 0
FILE *fp = fopen (fname, "w+b");
if (!fp) {
fprintf (stderr, "failed to save session, file %s could not be opened\n");
@@ -180,42 +156,35 @@ session_save (const char *fname) {
goto session_save_fail;
}
}
- {
- // main column widths
- uint8_t cl = session_main_numcols;
- if (fwrite (&cl, 1, 1, fp) != 1) {
- goto session_save_fail;
- }
- for (int i = 0; i < cl; i++) {
- int16_t w = session_main_colwidths[i];
- if (fwrite (&w, 1, sizeof (w), fp) != sizeof (w)) {
- goto session_save_fail;
- }
- }
- }
- {
- // search column widths
- uint8_t cl = session_search_numcols;
- if (fwrite (&cl, 1, 1, fp) != 1) {
- goto session_save_fail;
- }
- for (int i = 0; i < cl; i++) {
- int16_t w = session_search_colwidths[i];
- if (fwrite (&w, 1, sizeof (w), fp) != sizeof (w)) {
- goto session_save_fail;
- }
- }
- }
fclose (fp);
return 0;
session_save_fail:
fprintf (stderr, "failed to save session, seems to be a disk error\n");
fclose (fp);
return -1;
+#endif
}
int
session_load (const char *fname) {
+ char session_dir[2048];
+ float session_volume;
+ int8_t session_playlist_order;
+ int8_t session_playlist_looping;
+ int8_t session_scroll_follows_playback = 1;
+ int session_win_attrs[5] = { 40, 40, 500, 300, 0 };
+
+ session_volume = 0;
+ session_dir[0] = 0;
+ session_playlist_looping = 0;
+ session_playlist_order = 0;
+ session_scroll_follows_playback = 1;
+ session_win_attrs[0] = 40;
+ session_win_attrs[1] = 40;
+ session_win_attrs[2] = 500;
+ session_win_attrs[3] = 300;
+ session_win_attrs[4] = 0;
+
FILE *fp = fopen (fname, "r+b");
if (!fp) {
return -1;
@@ -269,7 +238,13 @@ session_load (const char *fname) {
goto session_load_fail;
}
}
- if (version >= 3) {
+ if (version == 3) {
+ // import playlist and search columns to new common config
+#define PL_MAX_COLUMNS 5
+ int session_main_colwidths[PL_MAX_COLUMNS] = { 50, 150, 50, 150, 50 };
+ int session_main_numcols = 5;
+ int session_search_colwidths[PL_MAX_COLUMNS] = { 0, 150, 50, 150, 50 };;
+ int session_search_numcols = 5;
{
// main column widths
uint8_t l;
@@ -306,75 +281,59 @@ session_load (const char *fname) {
session_search_colwidths[i] = w;
}
}
+ // convert to common config
+ const char *colnames[] = {
+ "Playing",
+ "Artist / Album",
+ "Track №",
+ "Title / Track Artist",
+ "Duration"
+ };
+ int colids[] = {
+ DB_COLUMN_PLAYING,
+ DB_COLUMN_ARTIST_ALBUM,
+ DB_COLUMN_TRACK,
+ DB_COLUMN_TITLE,
+ DB_COLUMN_DURATION
+ };
+ int i;
+ for (i = 0; i < 5; i++) {
+ char key[128];
+ char value[128];
+ snprintf (key, sizeof (key), "playlist.column.%d", i);
+ snprintf (value, sizeof (value), "\"%s\" \"%s\" %d %d %d", colnames[i], "", colids[i], session_main_colwidths[i], i == 2 ? 1 : 0);
+ conf_set_str (key, value);
+ }
+ for (i = 1; i < 5; i++) {
+ char key[128];
+ char value[128];
+ snprintf (key, sizeof (key), "search.column.%d", i-1);
+ snprintf (value, sizeof (value), "\"%s\" \"%s\" %d %d %d", colnames[i], "", colids[i], session_main_colwidths[i], i == 2 ? 1 : 0);
+ conf_set_str (key, value);
+ }
}
// printf ("dir: %s\n", session_dir);
// printf ("volume: %f\n", session_volume);
// printf ("win: %d %d %d %d %d\n", session_win_attrs[0], session_win_attrs[1], session_win_attrs[2], session_win_attrs[3], session_win_attrs[4]);
fclose (fp);
+
+ if (version <= 4) {
+ // move everything to common config
+ conf_set_str ("filechooser.lastdir", session_dir);
+ conf_set_int ("playback.loop", session_playlist_looping);
+ conf_set_int ("playback.order", session_playlist_order);
+ conf_set_float ("playback.volume", session_volume);
+ conf_set_int ("playlist.scroll.followplayback", session_scroll_follows_playback);
+ conf_set_int ("mainwin.geometry.x", session_win_attrs[0]);
+ conf_set_int ("mainwin.geometry.y", session_win_attrs[1]);
+ conf_set_int ("mainwin.geometry.w", session_win_attrs[2]);
+ conf_set_int ("mainwin.geometry.h", session_win_attrs[3]);
+ }
+
return 0;
session_load_fail:
fprintf (stderr, "failed to load session, session file is corrupt\n");
fclose (fp);
- session_reset ();
+// session_reset ();
return -1;
}
-
-void
-session_set_directory (const char *path) {
- strncpy (session_dir, path, 2048);
-}
-
-void
-session_set_volume (float vol) {
- session_volume = vol;
-}
-
-const char *
-session_get_directory (void) {
- return session_dir;
-}
-
-float
-session_get_volume (void) {
- return session_volume;
-}
-
-void
-session_set_playlist_order (int order) {
- session_playlist_order = order;
-}
-
-int
-session_get_playlist_order (void) {
- return session_playlist_order;
-}
-
-void
-session_set_playlist_looping (int looping) {
- session_playlist_looping = looping;
-}
-
-int
-session_get_playlist_looping (void) {
- return session_playlist_looping;
-}
-
-void
-session_set_scroll_follows_playback (int on) {
- session_scroll_follows_playback = on;
-}
-
-int
-session_get_scroll_follows_playback (void) {
- return session_scroll_follows_playback;
-}
-
-int *
-session_get_main_colwidths_ptr (void) {
- return session_main_colwidths;
-}
-
-int *
-session_get_search_colwidths_ptr (void) {
- return session_search_colwidths;
-}
diff --git a/session.h b/session.h
index 7a8ceea7..06d0e407 100644
--- a/session.h
+++ b/session.h
@@ -21,9 +21,6 @@
#include <stdint.h>
-void
-session_reset (void);
-
int
session_save (const char *fname);
@@ -34,42 +31,7 @@ void
session_capture_window_attrs (uintptr_t window);
void
-session_set_directory (const char *path);
-
-void
-session_set_volume (float vol);
-
-void
session_restore_window_attrs (uintptr_t window);
-const char *
-session_get_directory (void);
-
-float
-session_get_volume (void);
-
-void
-session_set_playlist_order (int order);
-
-int
-session_get_playlist_order (void);
-
-void
-session_set_scroll_follows_playback (int on);
-
-int
-session_get_scroll_follows_playback (void);
-
-void
-session_set_playlist_looping (int looping);
-
-int
-session_get_playlist_looping (void);
-
-int *
-session_get_main_colwidths_ptr (void);
-
-int *
-session_get_search_colwidths_ptr (void);
#endif // __SESSION_H
diff --git a/streamer.c b/streamer.c
index 25236676..97d5154e 100644
--- a/streamer.c
+++ b/streamer.c
@@ -34,13 +34,19 @@
#include "plugins.h"
#include "optmath.h"
#include "volume.h"
-#include "messages.h"
+#include "vfs.h"
+
+//#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+#define trace(fmt,...)
static intptr_t streamer_tid;
+static int src_quality;
static SRC_STATE *src;
static SRC_DATA srcdata;
static int codecleft;
+static int conf_replaygain_mode = 0;
+static int conf_replaygain_scale = 1;
// that's buffer for resampling.
// our worst case is 192KHz downsampling to 22050Hz with 2048 sample output buffer
#define INPUT_BUFFER_SIZE (2048*192000/22050*8)
@@ -51,6 +57,7 @@ static float g_fbuffer[INPUT_BUFFER_SIZE];
#define SRC_BUFFER_SIZE (INPUT_BUFFER_SIZE*2)
static float g_srcbuffer[SRC_BUFFER_SIZE];
static int streaming_terminate;
+static playItem_t *streamer_initializing_item;
// buffer up to 3 seconds at 44100Hz stereo
#define STREAM_BUFFER_SIZE 0x80000 // slightly more than 3 seconds of 44100 stereo
@@ -70,6 +77,9 @@ static int badsong = -1;
static float seekpos = -1;
static float playpos = 0; // play position of current song
+static float avg_bitrate = -1; // avg bitrate of current song
+
+static int prevtrack_samplerate = -1;
playItem_t str_playing_song;
playItem_t str_streaming_song;
@@ -77,12 +87,20 @@ playItem_t str_streaming_song;
static playItem_t *orig_playing_song;
static playItem_t *orig_streaming_song;
-//#define trace(...) { fprintf(stderr, __VA_ARGS__); }
-#define trace(fmt,...)
+static int streamer_buffering;
+
+// to allow interruption of stall file requests
+static DB_FILE *streamer_file;
+
+playItem_t *
+streamer_get_streaming_track (void) {
+ return orig_streaming_song;
+}
// playlist must call that whenever item was removed
void
streamer_song_removed_notify (playItem_t *it) {
+ plug_trigger_event (DB_EV_TRACKDELETED, (uintptr_t)it);
if (it == orig_playing_song) {
orig_playing_song = NULL;
}
@@ -99,26 +117,75 @@ streamer_song_removed_notify (playItem_t *it) {
// that must be called after last sample from str_playing_song was done reading
static int
streamer_set_current (playItem_t *it) {
+ int from, to;
+ streamer_buffering = 1;
+ from = orig_playing_song ? pl_get_idx_of (orig_playing_song) : -1;
+ to = it ? pl_get_idx_of (it) : -1;
+ if (!orig_playing_song || p_isstopped ()) {
+ playlist_current_ptr = it;
+ }
+ trace ("from=%d, to=%d\n", from, to);
+ trace ("sending songchanged\n");
+ messagepump_push (M_SONGCHANGED, 0, from, to);
trace ("streamer_set_current %p, buns=%d\n", it);
-// if (str_streaming_song.decoder) {
-// trace ("sending songfinished to plugins [1]\n");
-// plug_trigger_event (DB_EV_SONGFINISHED);
-// str_streaming_song.decoder->free ();
-// }
if(str_streaming_song.decoder) {
str_streaming_song.decoder->free ();
pl_item_free (&str_streaming_song);
}
orig_streaming_song = it;
if (!it) {
- return 0;
+ goto success;
+ }
+ if (!it->decoder && it->filetype && !strcmp (it->filetype, "content")) {
+ // try to get content-type
+ DB_FILE *fp = streamer_file = vfs_fopen (it->fname);
+ const char *ext = NULL;
+ if (fp) {
+ const char *ct = vfs_get_content_type (fp);
+ if (ct) {
+ fprintf (stderr, "got content-type: %s\n", ct);
+ if (!strcmp (ct, "audio/mpeg")) {
+ ext = "mp3";
+ }
+ else if (!strcmp (ct, "application/ogg")) {
+ ext = "ogg";
+ }
+ }
+ streamer_file = NULL;
+ vfs_fclose (fp);
+ }
+ if (ext) {
+ DB_decoder_t **decoders = plug_get_decoder_list ();
+ // match by decoder
+ for (int i = 0; decoders[i]; i++) {
+ if (decoders[i]->exts) {
+ const char **exts = decoders[i]->exts;
+ for (int e = 0; exts[e]; e++) {
+ if (!strcasecmp (exts[e], ext)) {
+ it->decoder = decoders[i];
+ it->filetype = decoders[i]->filetypes[0];
+ }
+ }
+ }
+ }
+ }
}
if (it->decoder) {
+ streamer_lock ();
+ streamer_initializing_item = it;
+ streamer_unlock ();
int ret = it->decoder->init (DB_PLAYITEM (it));
- trace ("input samplerate: %d\n", it->decoder->info.samplerate);
+ streamer_lock ();
+ streamer_initializing_item = NULL;
+ streamer_unlock ();
pl_item_copy (&str_streaming_song, it);
if (ret < 0) {
trace ("decoder->init returned %d\n", ret);
+ trace ("orig_playing_song = %p\n", orig_playing_song);
+ if (playlist_current_ptr == it) {
+ playlist_current_ptr = NULL;
+ messagepump_push (M_TRACKCHANGED, 0, to, 0);
+ }
return ret;
}
else {
@@ -134,52 +201,26 @@ streamer_set_current (playItem_t *it) {
if (bytes_until_next_song == -1) {
bytes_until_next_song = 0;
}
+success:
+ messagepump_push (M_TRACKCHANGED, 0, to, 0);
return 0;
}
-#if 0
-static int
-str_set_current (playItem_t *it) {
- int ret = 0;
- int from = pl_get_idx_of (playlist_current_ptr);
- int to = it ? pl_get_idx_of (it) : -1;
- if (str_playing_song.decoder) {
- plug_trigger_event (DB_EV_SONGFINISHED);
- }
- codec_lock ();
- if (str_playing_song.decoder) {
- str_playing_song.decoder->free ();
- }
- pl_item_free (&str_playing_song);
- playlist_current_ptr = it;
- if (it && it->decoder) {
- // don't do anything on fail, streamer will take care
- ret = it->decoder->init (DB_PLAYITEM (it));
-// if (ret < 0) {
-// }
- }
- if (playlist_current_ptr) {
- streamer_reset (0);
- }
- if (it) {
- it->played = 1;
- it->started_timestamp = time (NULL);
- pl_item_copy (&str_playing_song, it);
- }
- codec_unlock ();
- if (it) {
- plug_trigger_event (DB_EV_SONGSTARTED);
- }
- messagepump_push (M_SONGCHANGED, 0, from, to);
- return ret;
-}
-#endif
-
float
streamer_get_playpos (void) {
return playpos;
}
+float
+streamer_get_bitrate (void) {
+ return avg_bitrate;
+}
+
+void
+streamer_update_bitrate (float bitrate) {
+ avg_bitrate = bitrate;
+}
+
void
streamer_set_nextsong (int song, int pstate) {
trace ("streamer_set_nextsong %d %d\n", song, pstate);
@@ -188,6 +229,21 @@ streamer_set_nextsong (int song, int pstate) {
if (p_isstopped ()) {
// no sense to wait until end of previous song, reset buffer
bytes_until_next_song = 0;
+ playpos = 0;
+ // try to interrupt file operation
+ streamer_lock ();
+ if (streamer_file && streamer_file->vfs->streaming) {
+ trace ("interrupting streamer file access...\n");
+ vfs_fstop (streamer_file);
+ }
+ else if (streamer_initializing_item) {
+ playItem_t *it = streamer_initializing_item;
+ if (it->decoder->info.file && it->decoder->info.file->vfs->streaming) {
+ trace ("interrupting plugin stream %p...\n", it->decoder->info.file);
+ vfs_fstop (it->decoder->info.file);
+ }
+ }
+ streamer_unlock ();
}
}
@@ -221,15 +277,17 @@ streamer_thread (uintptr_t ctx) {
badsong = -1;
continue;
}
- int ret = streamer_set_current (pl_get_for_idx (sng));
+ playItem_t *try = pl_get_for_idx (sng);
+ int ret = streamer_set_current (try);
if (ret < 0) {
- trace ("bad file in playlist, skipping...\n");
+ trace ("failed to play track %s, skipping...\n", try->fname);
// remember bad song number in case of looping
if (badsong == -1) {
badsong = sng;
}
// try jump to next song
pl_nextsong (0);
+ trace ("pl_nextsong switched to track %d\n", nextsong);
usleep (50000);
continue;
}
@@ -250,9 +308,9 @@ streamer_thread (uintptr_t ctx) {
p_stop ();
if (str_playing_song.decoder) {
trace ("sending songfinished to plugins [1]\n");
- plug_trigger_event (DB_EV_SONGFINISHED);
+ plug_trigger_event (DB_EV_SONGFINISHED, 0);
}
- messagepump_push (M_SONGCHANGED, 0, pl_get_idx_of (orig_playing_song), -1);
+// messagepump_push (M_SONGCHANGED, 0, pl_get_idx_of (orig_playing_song), -1);
streamer_set_current (NULL);
pl_item_free (&str_playing_song);
orig_playing_song = NULL;
@@ -268,24 +326,26 @@ streamer_thread (uintptr_t ctx) {
// means last song was deleted during final drain
nextsong = -1;
p_stop ();
- messagepump_push (M_SONGCHANGED, 0, pl_get_idx_of (playlist_current_ptr), -1);
+// messagepump_push (M_SONGCHANGED, 0, pl_get_idx_of (playlist_current_ptr), -1);
streamer_set_current (NULL);
continue;
}
trace ("bytes_until_next_song=0, starting playback of new song\n");
+ int from = orig_playing_song ? pl_get_idx_of (orig_playing_song) : -1;
+ int to = orig_streaming_song ? pl_get_idx_of (orig_streaming_song) : -1;
+ trace ("from=%d, to=%d\n", from, to);
+ trace ("sending songchanged\n");
+ messagepump_push (M_SONGCHANGED, 0, from, to);
bytes_until_next_song = -1;
// plugin will get pointer to str_playing_song
if (str_playing_song.decoder) {
trace ("sending songfinished to plugins [2]\n");
- plug_trigger_event (DB_EV_SONGFINISHED);
+ plug_trigger_event (DB_EV_SONGFINISHED, 0);
}
// free old copy of playing
pl_item_free (&str_playing_song);
// copy streaming into playing
pl_item_copy (&str_playing_song, &str_streaming_song);
- int from = orig_playing_song ? pl_get_idx_of (orig_playing_song) : -1;
- int to = orig_streaming_song ? pl_get_idx_of (orig_streaming_song) : -1;
- trace ("from=%d, to=%d\n", from, to);
orig_playing_song = orig_streaming_song;
if (orig_playing_song) {
orig_playing_song->played = 1;
@@ -294,12 +354,16 @@ streamer_thread (uintptr_t ctx) {
}
playlist_current_ptr = orig_playing_song;
// that is needed for playlist drawing
- trace ("sending songchanged\n");
- messagepump_push (M_SONGCHANGED, 0, from, to);
// plugin will get pointer to new str_playing_song
trace ("sending songstarted to plugins\n");
- plug_trigger_event (DB_EV_SONGSTARTED);
+ plug_trigger_event (DB_EV_SONGSTARTED, 0);
playpos = 0;
+ avg_bitrate = -1;
+ // change samplerate
+ if (prevtrack_samplerate != str_playing_song.decoder->info.samplerate) {
+ palsa_change_rate (str_playing_song.decoder->info.samplerate);
+ prevtrack_samplerate = str_playing_song.decoder->info.samplerate;
+ }
}
if (seekpos >= 0) {
@@ -318,15 +382,22 @@ streamer_thread (uintptr_t ctx) {
bytes_until_next_song = -1;
}
- if (str_playing_song.decoder && str_playing_song.decoder->seek (pos) >= 0) {
+ streamer_buffering = 1;
+ int trk = pl_get_idx_of (orig_streaming_song);
+ if (trk != -1) {
+ messagepump_push (M_TRACKCHANGED, 0, trk, 0);
+ }
+ if (str_playing_song.decoder && str_playing_song._duration > 0) {
streamer_lock ();
- playpos = str_playing_song.decoder->info.readpos;
streambuffer_fill = 0;
streambuffer_pos = 0;
- streamer_unlock ();
codec_lock ();
codecleft = 0;
codec_unlock ();
+ if (str_playing_song.decoder->seek (pos) >= 0) {
+ playpos = str_playing_song.decoder->info.readpos;
+ }
+ streamer_unlock();
}
}
@@ -350,26 +421,6 @@ streamer_thread (uintptr_t ctx) {
streamer_lock ();
memcpy (streambuffer+streambuffer_fill, buf, sz);
streambuffer_fill += bytesread;
-
-
-#if 0
- int writepos = (streambuffer_pos + streambuffer_fill) & STREAM_BUFFER_MASK;
- int part1 = STREAM_BUFFER_SIZE-writepos;
- part1 = min (part1, sz);
- if (part1 > 0) {
- streamer_unlock ();
- int bytesread = streamer_read_async (streambuffer + writepos, part1);
- streamer_lock ();
- streambuffer_fill += bytesread;
- }
- sz -= part1;
- if (sz > 0) {
- streamer_unlock ();
- int bytesread = streamer_read_async (streambuffer, sz);
- streamer_lock ();
- streambuffer_fill += bytesread;
- }
-#endif
}
streamer_unlock ();
struct timeval tm2;
@@ -383,6 +434,12 @@ streamer_thread (uintptr_t ctx) {
// trace ("fill: %d/%d\n", streambuffer_fill, STREAM_BUFFER_SIZE);
}
+ // stop streaming song
+ if(str_streaming_song.decoder) {
+ str_streaming_song.decoder->free ();
+ }
+ pl_item_free (&str_streaming_song);
+ pl_item_free (&str_playing_song);
if (src) {
src_delete (src);
src = NULL;
@@ -392,9 +449,10 @@ streamer_thread (uintptr_t ctx) {
int
streamer_init (void) {
mutex = mutex_create ();
-// src = src_new (SRC_SINC_BEST_QUALITY, 2, NULL);
-// src = src_new (SRC_LINEAR, 2, NULL);
- src = src_new (conf_src_quality, 2, NULL);
+ src_quality = conf_get_int ("src_quality", 2);
+ src = src_new (src_quality, 2, NULL);
+ conf_replaygain_mode = conf_get_int ("replaygain_mode", 0);
+ conf_replaygain_scale = conf_get_int ("replaygain_scale", 1);
if (!src) {
return -1;
}
@@ -772,10 +830,22 @@ streamer_get_fill (void) {
int
streamer_ok_to_read (int len) {
- if (bytes_until_next_song > 0) {
+ if (len >= 0 && (bytes_until_next_song > 0 || streambuffer_fill >= (len*2))) {
+ if (streamer_buffering) {
+ streamer_buffering = 0;
+ if (orig_streaming_song) {
+ int trk = pl_get_idx_of (orig_streaming_song);
+ if (trk != -1) {
+ messagepump_push (M_TRACKCHANGED, 0, trk, 0);
+ }
+ }
+ }
return 1;
}
- return streambuffer_fill >= (len*2);
+ else {
+ return 1-streamer_buffering;
+ }
+ return 0;
}
int
@@ -787,3 +857,21 @@ streamer_is_buffering (void) {
return 0;
}
}
+
+void
+streamer_configchanged (void) {
+ conf_replaygain_mode = conf_get_int ("replaygain_mode", 0);
+ conf_replaygain_scale = conf_get_int ("replaygain_scale", 1);
+ int q = conf_get_int ("src_quality", 2);
+ if (q != src_quality && q >= SRC_SINC_BEST_QUALITY && q <= SRC_LINEAR) {
+ fprintf (stderr, "changing src_quality from %d to %d\n", src_quality, q);
+ src_quality = q;
+ streamer_lock ();
+ if (src) {
+ src_delete (src);
+ src = NULL;
+ }
+ src = src_new (src_quality, 2, NULL);
+ streamer_unlock ();
+ }
+}
diff --git a/streamer.h b/streamer.h
index 4f434223..9544c755 100644
--- a/streamer.h
+++ b/streamer.h
@@ -60,10 +60,22 @@ streamer_ok_to_read (int len);
float
streamer_get_playpos (void);
+float
+streamer_get_bitrate (void);
+
+void
+streamer_update_bitrate (float bitrate);
+
int
streamer_is_buffering (void);
void
streamer_song_removed_notify (playItem_t *it);
+playItem_t *
+streamer_get_streaming_track (void);
+
+void
+streamer_configchanged (void);
+
#endif // __STREAMER_H
diff --git a/timeline.c b/timeline.c
new file mode 100644
index 00000000..c9a40fb5
--- /dev/null
+++ b/timeline.c
@@ -0,0 +1,122 @@
+/*
+ DeaDBeeF - ultimate music player for GNU/Linux systems with X11
+ Copyright (C) 2009 Alexey Yakovenko
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+#include <time.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <assert.h>
+#include "timeline.h"
+#include "threading.h"
+
+timeline_t *
+timeline_create (void) {
+ timeline_t *tl = malloc (sizeof (timeline_t));
+ memset (tl, 0, sizeof (timeline_t));
+ return tl;
+}
+
+void
+timeline_free (timeline_t *tl, int wait) {
+ if (tl->tid && wait) {
+ int tid = tl->tid;
+ tl->destroy = 1;
+ thread_join (tid);
+ }
+ else {
+ tl->destroy = 1;
+ }
+}
+
+void
+timeline_init (timeline_t *tl, float seconds, float fps, int (*callback)(float _progress, int _last, void *_ctx), void *ctx) {
+ tl->fps = fps;
+ tl->duration = seconds;
+ tl->progress = 0;
+ tl->callback = callback;
+ tl->callback_ctx = ctx;
+ tl->destroy = 0;
+ tl->stop = 0;
+}
+
+void
+timeline_stop (timeline_t *tl, int wait) {
+ int tid = tl->tid;
+ if (tid) {
+ tl->stop = 1;
+ if (wait) {
+ thread_join (tid);
+ }
+ }
+}
+
+void
+timeline_thread_func (uintptr_t ctx) {
+ printf ("timeline thread started\n");
+ timeline_t *tl = (timeline_t *)ctx;
+
+ for (;;) {
+ if (tl->stop || tl->destroy) {
+ tl->callback (1, 1, tl->callback_ctx);
+ break;
+ }
+ struct timeval tm;
+ gettimeofday (&tm, NULL);
+ float dt = (tm.tv_sec - tl->time.tv_sec) + (tm.tv_usec - tl->time.tv_usec) / 1000000.0;
+ float t = tl->progress;
+ tl->progress += dt;
+ memcpy (&tl->time, &tm, sizeof (tm));
+ if (t > tl->duration) {
+ tl->callback (1, 1, tl->callback_ctx);
+ break;
+ }
+ else {
+ if (tl->callback (t/tl->duration, 0, tl->callback_ctx) < 0) {
+ break;
+ }
+ }
+ printf ("progress: %f\n", tl->progress);
+
+ // sleep until next frame
+ usleep (1000000 / tl->fps);
+ }
+ tl->tid = 0;
+ if (tl->destroy) {
+ printf ("timeline %p destroyed\n", tl);
+ free (tl);
+ }
+ printf ("timeline %p thread terminated\n", tl);
+}
+
+
+void
+timeline_start (timeline_t *tl) {
+ gettimeofday (&tl->time, NULL);
+ tl->progress = 0;
+ tl->stop = 0;
+ tl->destroy = 0;
+ if (!tl->tid) {
+ tl->tid = thread_start (timeline_thread_func, (uintptr_t)tl);
+ }
+ else {
+ printf ("reusing existing thread\n");
+ }
+}
+
diff --git a/timeline.h b/timeline.h
new file mode 100644
index 00000000..68c372a0
--- /dev/null
+++ b/timeline.h
@@ -0,0 +1,52 @@
+/*
+ DeaDBeeF - ultimate music player for GNU/Linux systems with X11
+ Copyright (C) 2009 Alexey Yakovenko
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+#ifndef __TIMELINE_H
+#define __TIMELINE_H
+
+#include <stdint.h>
+
+typedef struct {
+ float fps;
+ float duration;
+ float progress;
+ struct timeval time;
+ intptr_t tid;
+ int stop;
+ int destroy;
+ int (*callback)(float _progress, int _last, void *_ctx);
+ void *callback_ctx;
+} timeline_t;
+
+// callback must return 0 to continue, or -1 to abort
+timeline_t *
+timeline_create (void);
+
+void
+timeline_free (timeline_t *timeline, int wait);
+
+void
+timeline_stop (timeline_t *tl, int wait);
+
+void
+timeline_init (timeline_t *timeline, float seconds, float fps, int (*callback)(float _progress, int _last, void *_ctx), void *ctx);
+
+void
+timeline_start (timeline_t *timeline);
+
+#endif // __TIMELINE_H
diff --git a/vfs.c b/vfs.c
new file mode 100644
index 00000000..23b1628d
--- /dev/null
+++ b/vfs.c
@@ -0,0 +1,108 @@
+/*
+ DeaDBeeF - ultimate music player for GNU/Linux systems with X11
+ Copyright (C) 2009 Alexey Yakovenko
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+#include <string.h>
+#include <stdio.h>
+#include "vfs.h"
+#include "plugins.h"
+
+#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+//#define trace(fmt,...)
+
+DB_FILE *
+vfs_fopen (const char *fname) {
+ trace ("vfs_open %s\n", fname);
+ DB_vfs_t **plugs = plug_get_vfs_list ();
+ DB_vfs_t *fallback = NULL;
+ int i;
+ for (i = 0; plugs[i]; i++) {
+ DB_vfs_t *p = plugs[i];
+ if (!p->scheme_names) {
+ fallback = p;
+ continue;
+ }
+ int n;
+ for (n = 0; p->scheme_names[n]; n++) {
+ size_t l = strlen (p->scheme_names[n]);
+ if (!strncasecmp (p->scheme_names[n], fname, l)) {
+ return p->open (fname);
+ }
+ }
+ }
+ if (fallback) {
+ return fallback->open (fname);
+ }
+ return NULL;
+}
+
+void
+vfs_fclose (DB_FILE *stream) {
+ return stream->vfs->close (stream);
+}
+
+size_t
+vfs_fread (void *ptr, size_t size, size_t nmemb, DB_FILE *stream) {
+ return stream->vfs->read (ptr, size, nmemb, stream);
+}
+
+int
+vfs_fseek (DB_FILE *stream, int64_t offset, int whence) {
+ return stream->vfs->seek (stream, offset, whence);
+}
+
+int64_t
+vfs_ftell (DB_FILE *stream) {
+ return stream->vfs->tell (stream);
+}
+
+void
+vfs_rewind (DB_FILE *stream) {
+ stream->vfs->rewind (stream);
+}
+
+int64_t
+vfs_fgetlength (DB_FILE *stream) {
+ return stream->vfs->getlength (stream);
+}
+
+const char *
+vfs_get_content_type (DB_FILE *stream) {
+ return stream->vfs->get_content_type (stream);
+}
+
+const char *
+vfs_get_content_name (DB_FILE *stream) {
+ if (stream->vfs->get_content_name) {
+ return stream->vfs->get_content_name (stream);
+ }
+ return NULL;
+}
+const char *
+vfs_get_content_genre (DB_FILE *stream) {
+ if (stream->vfs->get_content_genre) {
+ return stream->vfs->get_content_genre (stream);
+ }
+ return NULL;
+}
+
+void
+vfs_fstop (DB_FILE *stream) {
+ if (stream->vfs->stop) {
+ stream->vfs->stop (stream);
+ }
+}
diff --git a/vfs.h b/vfs.h
new file mode 100644
index 00000000..65610cf2
--- /dev/null
+++ b/vfs.h
@@ -0,0 +1,37 @@
+/*
+ DeaDBeeF - ultimate music player for GNU/Linux systems with X11
+ Copyright (C) 2009 Alexey Yakovenko
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#ifndef __VFS_H
+#define __VFS_H
+
+#include "deadbeef.h"
+
+DB_FILE* vfs_fopen (const char *fname);
+void vfs_fclose (DB_FILE *f);
+size_t vfs_fread (void *ptr, size_t size, size_t nmemb, DB_FILE *stream);
+int vfs_fseek (DB_FILE *stream, int64_t offset, int whence);
+int64_t vfs_ftell (DB_FILE *stream);
+void vfs_rewind (DB_FILE *stream);
+int64_t vfs_fgetlength (DB_FILE *stream);
+const char *vfs_get_content_type (DB_FILE *stream);
+const char *vfs_get_content_name (DB_FILE *stream);
+const char *vfs_get_content_genre (DB_FILE *stream);
+void vfs_fstop (DB_FILE *stream);
+
+#endif // __VFS_H
diff --git a/vfs_stdio.c b/vfs_stdio.c
new file mode 100644
index 00000000..d98002bd
--- /dev/null
+++ b/vfs_stdio.c
@@ -0,0 +1,123 @@
+/*
+ DeaDBeeF - ultimate music player for GNU/Linux systems with X11
+ Copyright (C) 2009 Alexey Yakovenko
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+#include "deadbeef.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+static DB_functions_t *deadbeef;
+typedef struct {
+ DB_vfs_t *vfs;
+ FILE *stream;
+} STDIO_FILE;
+
+static DB_vfs_t plugin;
+
+static DB_FILE *
+stdio_open (const char *fname) {
+ if (!memcmp (fname, "file://", 7)) {
+ fname += 7;
+ }
+ FILE *file = fopen (fname, "rb");
+ if (!file) {
+ return NULL;
+ }
+ STDIO_FILE *fp = malloc (sizeof (STDIO_FILE));
+ fp->vfs = &plugin;
+ fp->stream = file;
+ return (DB_FILE*)fp;
+}
+
+static void
+stdio_close (DB_FILE *stream) {
+ assert (stream);
+ fclose (((STDIO_FILE *)stream)->stream);
+ free (stream);
+}
+
+static size_t
+stdio_read (void *ptr, size_t size, size_t nmemb, DB_FILE *stream) {
+ assert (stream);
+ assert (ptr);
+ return fread (ptr, size, nmemb, ((STDIO_FILE *)stream)->stream);
+}
+
+static int
+stdio_seek (DB_FILE *stream, int64_t offset, int whence) {
+ assert (stream);
+ return fseek (((STDIO_FILE *)stream)->stream, offset, whence);
+}
+
+static int64_t
+stdio_tell (DB_FILE *stream) {
+ assert (stream);
+ return ftell (((STDIO_FILE *)stream)->stream);
+}
+
+static void
+stdio_rewind (DB_FILE *stream) {
+ assert (stream);
+ rewind (((STDIO_FILE *)stream)->stream);
+}
+
+static int64_t
+stdio_getlength (DB_FILE *stream) {
+ assert (stream);
+ STDIO_FILE *f = (STDIO_FILE *)stream;
+ size_t pos = ftell (f->stream);
+ fseek (f->stream, 0, SEEK_END);
+ size_t sz = ftell (f->stream);
+ fseek (f->stream, pos, SEEK_SET);
+ return sz;
+}
+
+const char *
+stdio_get_content_type (DB_FILE *stream) {
+ return NULL;
+}
+
+// standard stdio vfs
+static DB_vfs_t plugin = {
+ DB_PLUGIN_SET_API_VERSION
+ .plugin.version_major = 0,
+ .plugin.version_minor = 1,
+ .plugin.type = DB_PLUGIN_VFS,
+ .plugin.name = "stdio vfs",
+ .plugin.descr = "Standard IO plugin",
+ .plugin.author = "Alexey Yakovenko",
+ .plugin.email = "waker@users.sourceforge.net",
+ .plugin.website = "http://deadbeef.sf.net",
+ .open = stdio_open,
+ .close = stdio_close,
+ .read = stdio_read,
+ .seek = stdio_seek,
+ .tell = stdio_tell,
+ .rewind = stdio_rewind,
+ .getlength = stdio_getlength,
+ .get_content_type = stdio_get_content_type,
+ .scheme_names = NULL // this is NULL because that's a fallback vfs, used when no other matching vfs plugin found
+};
+
+DB_plugin_t *
+stdio_load (DB_functions_t *api) {
+ deadbeef = api;
+ return DB_PLUGIN (&plugin);
+}
+
diff --git a/volume.c b/volume.c
index 24e876ff..c1d372c5 100644
--- a/volume.c
+++ b/volume.c
@@ -19,7 +19,7 @@
#include <math.h>
#include <stdio.h>
#include "volume.h"
-#include "session.h"
+#include "conf.h"
#define VOLUME_MIN (-50.f)
@@ -34,7 +34,7 @@ volume_set_db (float dB) {
if (dB > 0) {
dB = 0;
}
- session_set_volume (dB);
+ conf_set_float ("playback.volume", dB);
volume_db = dB;
volume_amp = dB > VOLUME_MIN ? db_to_amp (dB) : 0;
}
@@ -54,7 +54,7 @@ volume_set_amp (float amp) {
}
volume_amp = amp;
volume_db = amp > 0 ? amp_to_db (amp) : VOLUME_MIN;
- session_set_volume (volume_db);
+ conf_set_float ("playback.volume", volume_db);
}
float