diff options
author | Alexey Yakovenko <wakeroid@gmail.com> | 2009-11-08 15:10:02 +0100 |
---|---|---|
committer | Alexey Yakovenko <wakeroid@gmail.com> | 2009-11-08 15:10:02 +0100 |
commit | e84871a4c4d8828239d8501273481cdcd05769c2 (patch) | |
tree | ae8bb4e5a0627127c4aac1a00f0ea971bfc0e1e4 | |
parent | 2ea97385dba1a30d50e034ef8793b9a8baefaa19 (diff) | |
parent | af6c7af6784b1e1b720077f7814cb07b0127b4bb (diff) |
Merge branch 'vfs'
Conflicts:
configure.ac
main.c
palsa.c
playlist.c
plugins/flac/flac.c
streamer.c
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 @@ -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); @@ -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", @@ -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", @@ -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; + } } - @@ -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 ]) @@ -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 <drepper@gnu.ai.mit.edu>, 1995. -Modified by Gray Watson <http://256.com/gray/>, 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 <ben@geexbox.org></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 -<waker@users.sourceforge.net> - -Hotkeys plugin: -Viktor Semykin -<thesame.ml@gmail.com> - -Desktop file and Debian packages: -Alexey A. Smirnov -<alexey.smirnov@gmx.com></property> - <property name="artists">Button artwork: -Stas "uncle lag" Akimushkin <uncle.lag@gmail.com></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 "uncle lag" Akimushkin <uncle.lag@gmail.com></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 "uncle lag" Akimushkin <uncle.lag@gmail.com></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 "uncle lag" Akimushkin <uncle.lag@gmail.com></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 "uncle lag" Akimushkin <uncle.lag@gmail.com></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> @@ -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); @@ -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; @@ -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); @@ -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) @@ -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); +} @@ -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 Binary files differnew file mode 100644 index 00000000..bf5b8887 --- /dev/null +++ b/pixmaps/buffering_16.png @@ -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) { +} + @@ -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 @@ -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; +} @@ -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", @@ -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; @@ -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; -} @@ -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 @@ -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 (); + } +} @@ -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 @@ -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); + } +} @@ -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); +} + @@ -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 |