/* This file is part of Deadbeef Player source code http://deadbeef.sourceforge.net plugin management Copyright (C) 2009-2013 Alexey Yakovenko This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Alexey Yakovenko waker@users.sourceforge.net */ #include #include #include #include #include #include #include #ifndef __linux__ #define _POSIX_C_SOURCE 1 #endif #include #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "gettext.h" #include "plugins.h" #include "md5/md5.h" #include "messagepump.h" #include "threading.h" #include "playlist.h" #include "volume.h" #include "streamer.h" #include "common.h" #include "conf.h" #include "junklib.h" #include "vfs.h" #include "premix.h" #include "dsppreset.h" #include "pltmeta.h" #include "metacache.h" #define trace(...) { fprintf(stderr, __VA_ARGS__); } //#define trace(fmt,...) //#define DISABLE_VERSIONCHECK 1 // deadbeef api static DB_functions_t deadbeef_api = { .vmajor = DB_API_VERSION_MAJOR, .vminor = DB_API_VERSION_MINOR, .md5 = plug_md5, .md5_to_str = plug_md5_to_str, .md5_init = (void (*)(DB_md5_t *s))md5_init, .md5_append = (void (*)(DB_md5_t *s, const uint8_t *daya, int nbytes))md5_append, .md5_finish = (void (*)(DB_md5_t *s, uint8_t digest[16]))md5_finish, .get_output = plug_get_output, .playback_get_pos = plug_playback_get_pos, .playback_set_pos = plug_playback_set_pos, // streamer access .streamer_get_playing_track = (DB_playItem_t *(*) (void))streamer_get_playing_track, .streamer_get_streaming_track = (DB_playItem_t *(*) (void))streamer_get_streaming_track, .streamer_get_playpos = streamer_get_playpos, .streamer_ok_to_read = streamer_ok_to_read, .streamer_reset = streamer_reset, .streamer_read = streamer_read, .streamer_set_bitrate = streamer_set_bitrate, .streamer_get_apx_bitrate = streamer_get_apx_bitrate, .streamer_get_current_fileinfo = streamer_get_current_fileinfo, .streamer_get_current_playlist = streamer_get_current_playlist, .streamer_get_dsp_chain = streamer_get_dsp_chain, .streamer_set_dsp_chain = streamer_set_dsp_chain, .streamer_dsp_refresh = streamer_dsp_refresh, // folders .get_config_dir = plug_get_config_dir, .get_prefix = plug_get_prefix, .get_doc_dir = plug_get_doc_dir, .get_plugin_dir = plug_get_plugin_dir, .get_pixmap_dir = plug_get_pixmap_dir, // threading .thread_start = thread_start, .thread_start_low_priority = thread_start_low_priority, .thread_join = thread_join, .thread_detach = thread_detach, .thread_exit = thread_exit, .mutex_create = mutex_create, .mutex_create_nonrecursive = mutex_create_nonrecursive, .mutex_free = mutex_free, .mutex_lock = mutex_lock, .mutex_unlock = mutex_unlock, .cond_create = cond_create, .cond_free = cond_free, .cond_wait = cond_wait, .cond_signal = cond_signal, .cond_broadcast = cond_broadcast, // playlist management .plt_ref = (void (*) (ddb_playlist_t *plt))plt_ref, .plt_unref = (void (*) (ddb_playlist_t *plt))plt_unref, .plt_get_count = plt_get_count, .plt_get_head = (DB_playItem_t * (*) (int plt))plt_get_head, .plt_get_sel_count = plt_get_sel_count, .plt_add = plt_add, .plt_remove = plt_remove, .plt_clear = (void (*)(ddb_playlist_t *))plt_clear, .pl_clear = pl_clear, .plt_set_curr = (void (*) (ddb_playlist_t *plt))plt_set_curr, .plt_get_curr = (ddb_playlist_t *(*) (void))plt_get_curr, .plt_set_curr_idx = (void (*) (int plt))plt_set_curr_idx, .plt_get_curr_idx = (int (*) (void))plt_get_curr_idx, .plt_move = plt_move, // playlist saving and loading .plt_load = (DB_playItem_t * (*) (ddb_playlist_t *plt, DB_playItem_t *after, const char *fname, int *pabort, int (*cb)(DB_playItem_t *it, void *data), void *user_data))plt_load, .plt_save = (int (*)(ddb_playlist_t *plt, DB_playItem_t *first, DB_playItem_t *last, const char *fname, int *pabort, int (*cb)(DB_playItem_t *it, void *data), void *user_data))plt_save, // getting and working with a handle must be guarded using plt_lock/unlock .plt_get_for_idx = (ddb_playlist_t *(*)(int idx))plt_get_for_idx, .plt_get_title = (int (*)(ddb_playlist_t *handle, char *buffer, int sz))plt_get_title, .plt_set_title = (int (*)(ddb_playlist_t *handle, const char *buffer))plt_set_title, .plt_modified = (void (*) (ddb_playlist_t *handle))plt_modified, .plt_get_modification_idx = (int (*) (ddb_playlist_t *handle))plt_get_modification_idx, .plt_get_item_idx = (int (*) (ddb_playlist_t *plt, DB_playItem_t *it, int iter))plt_get_item_idx, // playlist metadata .plt_add_meta = (void (*) (ddb_playlist_t *handle, const char *key, const char *value))plt_add_meta, .plt_replace_meta = (void (*) (ddb_playlist_t *handle, const char *key, const char *value))plt_replace_meta, .plt_append_meta = (void (*) (ddb_playlist_t *handle, const char *key, const char *value))plt_append_meta, .plt_set_meta_int = (void (*) (ddb_playlist_t *handle, const char *key, int value))plt_set_meta_int, .plt_set_meta_float = (void (*) (ddb_playlist_t *handle, const char *key, float value))plt_set_meta_float, .plt_find_meta = (const char *(*) (ddb_playlist_t *handle, const char *key))plt_find_meta, .plt_get_metadata_head = (DB_metaInfo_t * (*) (ddb_playlist_t *handle))plt_get_metadata_head, .plt_delete_metadata = (void (*) (ddb_playlist_t *handle, DB_metaInfo_t *meta))plt_delete_metadata, .plt_find_meta_int = (int (*) (ddb_playlist_t *handle, const char *key, int def))plt_find_meta_int, .plt_find_meta_float = (float (*) (ddb_playlist_t *handle, const char *key, float def))plt_find_meta_float, .plt_delete_all_meta = (void (*) (ddb_playlist_t *handle))plt_delete_all_meta, // operating on playlist items .plt_insert_item = (DB_playItem_t *(*) (ddb_playlist_t *playlist, DB_playItem_t *after, DB_playItem_t *it))plt_insert_item, .plt_insert_file = (DB_playItem_t *(*) (ddb_playlist_t *playlist, DB_playItem_t *after, const char *fname, int *pabort, int (*cb)(DB_playItem_t *it, void *data), void *user_data))plt_insert_file, .plt_insert_dir = (DB_playItem_t *(*) (ddb_playlist_t *plt, DB_playItem_t *after, const char *dirname, int *pabort, int (*cb)(DB_playItem_t *it, void *data), void *user_data))plt_insert_dir, .plt_set_item_duration = (void (*) (ddb_playlist_t *plt, DB_playItem_t *it, float duration))plt_set_item_duration, .plt_remove_item = (int (*) (ddb_playlist_t *playlist, DB_playItem_t *it))plt_remove_item, .plt_getselcount = (int (*) (ddb_playlist_t *playlist))plt_getselcount, // playlist access .pl_lock = pl_lock, .pl_unlock = pl_unlock, //.plt_lock = plt_lock, //.plt_unlock = plt_unlock, .pl_item_alloc = (DB_playItem_t* (*)(void))pl_item_alloc, .pl_item_alloc_init = (DB_playItem_t* (*)(const char *fname, const char *decoder_id))pl_item_alloc_init, .pl_item_ref = (void (*)(DB_playItem_t *))pl_item_ref, .pl_item_unref = (void (*)(DB_playItem_t *))pl_item_unref, .pl_item_copy = (void (*)(DB_playItem_t *, DB_playItem_t *))pl_item_copy, .plt_add_file = (int (*) (ddb_playlist_t *plt, const char *, int (*cb)(DB_playItem_t *it, void *data), void *))plt_add_file, .plt_add_dir = (int (*) (ddb_playlist_t *plt, const char *dirname, int (*cb)(DB_playItem_t *it, void *data), void *user_data))plt_add_dir, .pl_add_files_begin = (int (*) (ddb_playlist_t *plt))pl_add_files_begin, .pl_add_files_end = pl_add_files_end, .pl_get_idx_of = (int (*) (DB_playItem_t *it))pl_get_idx_of, .pl_get_idx_of_iter = (int (*) (DB_playItem_t *it, int iter))pl_get_idx_of_iter, .pl_get_for_idx = (DB_playItem_t * (*)(int))pl_get_for_idx, .pl_get_for_idx_and_iter = (DB_playItem_t * (*) (int idx, int iter))pl_get_for_idx_and_iter, .pl_get_item_duration = (float (*) (DB_playItem_t *it))pl_get_item_duration, .pl_get_item_flags = (uint32_t (*) (DB_playItem_t *it))pl_get_item_flags, .pl_set_item_flags = (void (*) (DB_playItem_t *it, uint32_t flags))pl_set_item_flags, .plt_sort = (void (*) (ddb_playlist_t *plt, int iter, int id, const char *format, int order))plt_sort, .pl_items_copy_junk = (void (*)(DB_playItem_t *from, DB_playItem_t *first, DB_playItem_t *last))pl_items_copy_junk, .pl_set_item_replaygain = (void (*)(DB_playItem_t *it, int idx, float value))pl_set_item_replaygain, .pl_get_item_replaygain = (float (*)(DB_playItem_t *it, int idx))pl_get_item_replaygain, .plt_get_totaltime = (float (*) (ddb_playlist_t *plt))plt_get_totaltime, .plt_get_item_idx = (int (*) (ddb_playlist_t *playlist, DB_playItem_t *it, int iter))plt_get_item_idx, .plt_get_item_for_idx = (DB_playItem_t * (*) (ddb_playlist_t *playlist, int idx, int iter))plt_get_item_for_idx, .pl_get_totaltime = pl_get_totaltime, .pl_getcount = pl_getcount, .plt_get_item_count = (int (*)(ddb_playlist_t *plt, int iter))plt_get_item_count, .plt_delete_selected = (int (*) (ddb_playlist_t *plt))plt_delete_selected, .pl_delete_selected = pl_delete_selected, .plt_set_cursor = (void (*)(ddb_playlist_t *plt, int iter, int cursor))plt_set_cursor, .pl_set_cursor = pl_set_cursor, .plt_get_cursor = (int (*)(ddb_playlist_t *plt, int iter))plt_get_cursor, .pl_get_cursor = pl_get_cursor, .pl_set_selected = (void (*) (DB_playItem_t *, int))pl_set_selected, .pl_is_selected = (int (*) (DB_playItem_t *))pl_is_selected, .pl_save_current = pl_save_current, .pl_save_all = pl_save_all, .plt_select_all = (void (*) (ddb_playlist_t *plt))plt_select_all, .pl_select_all = pl_select_all, .plt_crop_selected = (void (*) (ddb_playlist_t *plt))plt_crop_selected, .pl_crop_selected = pl_crop_selected, .pl_getselcount = pl_getselcount, .plt_get_first = (DB_playItem_t *(*) (ddb_playlist_t *plt, int))plt_get_first, .pl_get_first = (DB_playItem_t *(*) (int))pl_get_first, .plt_get_last = (DB_playItem_t *(*) (ddb_playlist_t *plt, int))plt_get_last, .pl_get_last = (DB_playItem_t *(*) (int))pl_get_last, .pl_get_next = (DB_playItem_t *(*) (DB_playItem_t *, int))pl_get_next, .pl_get_prev = (DB_playItem_t *(*) (DB_playItem_t *, int))pl_get_prev, .pl_format_title = (int (*) (DB_playItem_t *it, int idx, char *s, int size, int id, const char *fmt))pl_format_title, .pl_format_title_escaped = (int (*) (DB_playItem_t *it, int idx, char *s, int size, int id, const char *fmt))pl_format_title_escaped, .pl_format_time = pl_format_time, .plt_move_items = (void (*) (ddb_playlist_t *to, int iter, ddb_playlist_t *from, DB_playItem_t *drop_before, uint32_t *indexes, int count))plt_move_items, .plt_copy_items = (void (*) (ddb_playlist_t *to, int iter, ddb_playlist_t *from, DB_playItem_t *before, uint32_t *indices, int cnt))plt_copy_items, .plt_search_reset = (void (*)(ddb_playlist_t *plt))plt_search_reset, .plt_search_process = (void (*)(ddb_playlist_t *plt, const char *t))plt_search_process, .pl_get_playlist = (ddb_playlist_t * (*) (DB_playItem_t *it))pl_get_playlist, // metainfo .pl_add_meta = (void (*) (DB_playItem_t *, const char *, const char *))pl_add_meta, .pl_append_meta = (void (*) (DB_playItem_t *, const char *, const char *))pl_append_meta, .pl_set_meta_int = (void (*) (DB_playItem_t *it, const char *key, int value))pl_set_meta_int, .pl_set_meta_float = (void (*) (DB_playItem_t *it, const char *key, float value))pl_set_meta_float, .pl_delete_meta = (void (*)(DB_playItem_t *, const char *key))pl_delete_meta, .pl_find_meta = (const char *(*) (DB_playItem_t *, const char *))pl_find_meta, .pl_find_meta_int = (int (*) (DB_playItem_t *it, const char *key, int def))pl_find_meta_int, .pl_find_meta_float = (float (*) (DB_playItem_t *it, const char *key, float def))pl_find_meta_float, .pl_replace_meta = (void (*) (DB_playItem_t *, const char *, const char *))pl_replace_meta, .pl_delete_all_meta = (void (*) (DB_playItem_t *it))pl_delete_all_meta, .pl_get_metadata_head = (DB_metaInfo_t *(*)(DB_playItem_t *it))pl_get_metadata_head, .pl_delete_metadata = (void (*)(DB_playItem_t *, DB_metaInfo_t *))pl_delete_metadata, // cuesheet support .plt_insert_cue_from_buffer = (DB_playItem_t *(*) (ddb_playlist_t *plt, DB_playItem_t *after, DB_playItem_t *origin, const uint8_t *buffer, int buffersize, int numsamples, int samplerate))plt_insert_cue_from_buffer, .plt_insert_cue = (DB_playItem_t *(*)(ddb_playlist_t *plt, DB_playItem_t *after, DB_playItem_t *origin, int numsamples, int samplerate))plt_insert_cue, // playqueue support .pl_playqueue_push = (int (*) (DB_playItem_t *))pl_playqueue_push, .pl_playqueue_clear = pl_playqueue_clear, .pl_playqueue_pop = pl_playqueue_pop, .pl_playqueue_remove = (void (*) (DB_playItem_t *))pl_playqueue_remove, .pl_playqueue_test = (int (*) (DB_playItem_t *))pl_playqueue_test, // volume control .volume_set_db = plug_volume_set_db, .volume_get_db = volume_get_db, .volume_set_amp = plug_volume_set_amp, .volume_get_amp = volume_get_amp, .volume_get_min_db = volume_get_min_db, // junk reading .junk_id3v1_read = (int (*)(DB_playItem_t *it, DB_FILE *fp))junk_id3v1_read, .junk_id3v1_find = junk_id3v1_find, .junk_id3v1_write = (int (*) (FILE *, DB_playItem_t *, const char *))junk_id3v1_write, .junk_id3v2_find = junk_id3v2_find, .junk_id3v2_read = (int (*)(DB_playItem_t *it, DB_FILE *fp))junk_id3v2_read, .junk_id3v2_read_full = (int (*)(DB_playItem_t *, DB_id3v2_tag_t *tag, DB_FILE *fp))junk_id3v2_read_full, .junk_id3v2_convert_24_to_23 = junk_id3v2_convert_24_to_23, .junk_id3v2_convert_23_to_24 = junk_id3v2_convert_23_to_24, .junk_id3v2_convert_22_to_24 = junk_id3v2_convert_22_to_24, .junk_id3v2_free = junk_id3v2_free, .junk_id3v2_write = junk_id3v2_write, .junk_id3v2_remove_frames = junk_id3v2_remove_frames, .junk_id3v2_add_text_frame = junk_id3v2_add_text_frame, .junk_apev2_read = (int (*)(DB_playItem_t *it, DB_FILE *fp))junk_apev2_read, .junk_apev2_read_mem = (int (*) (DB_playItem_t *it, char *mem, int size))junk_apev2_read_mem, .junk_apev2_read_full = (int (*) (DB_playItem_t *it, DB_apev2_tag_t *tag_store, DB_FILE *fp))junk_apev2_read_full, .junk_apev2_read_full_mem = (int (*) (DB_playItem_t *it, DB_apev2_tag_t *tag_store, char *mem, int memsize))junk_apev2_read_full_mem, .junk_apev2_find = junk_apev2_find, .junk_apev2_remove_frames = junk_apev2_remove_frames, .junk_apev2_add_text_frame = junk_apev2_add_text_frame, .junk_apev2_free = junk_apev2_free, .junk_apev2_write = junk_apev2_write, .junk_get_leading_size = junk_get_leading_size, .junk_get_leading_size_stdio = junk_get_leading_size_stdio, .junk_detect_charset = junk_detect_charset, .junk_recode = junk_recode, .junk_iconv = junk_iconv, .junk_rewrite_tags = (int (*) (DB_playItem_t *it, uint32_t flags, int id3v2_version, const char *id3v1_encoding))junk_rewrite_tags, // 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, .fset_track = (void (*) (DB_FILE *stream, DB_playItem_t *it))vfs_set_track, .fabort = vfs_fabort, // message passing .sendmessage = messagepump_push, .event_alloc = messagepump_event_alloc, .event_free = messagepump_event_free, .event_send = messagepump_push_event, // configuration access .conf_lock = conf_lock, .conf_unlock = conf_unlock, .conf_get_str_fast = conf_get_str_fast, .conf_get_str = conf_get_str, .conf_get_float = conf_get_float, .conf_get_int = conf_get_int, .conf_get_int64 = conf_get_int64, .conf_set_str = conf_set_str, .conf_set_int = conf_set_int, .conf_set_int64 = conf_set_int64, .conf_set_float = conf_set_float, .conf_find = conf_find, .conf_remove_items = conf_remove_items, .conf_save = conf_save, // plugin communication .plug_get_decoder_list = plug_get_decoder_list, .plug_get_vfs_list = plug_get_vfs_list, .plug_get_output_list = plug_get_output_list, .plug_get_dsp_list = plug_get_dsp_list, .plug_get_playlist_list = plug_get_playlist_list, .plug_get_list = plug_get_list, .plug_get_gui_names = plug_get_gui_names, .plug_get_decoder_id = plug_get_decoder_id, .plug_remove_decoder_id = plug_remove_decoder_id, .plug_get_for_id = plug_get_for_id, // misc utilities .is_local_file = plug_is_local_file, // pcm utilities .pcm_convert = pcm_convert, // dsp preset management .dsp_preset_load = dsp_preset_load, .dsp_preset_save = dsp_preset_save, .dsp_preset_free = dsp_preset_free, // new 1.2 APIs .plt_alloc = (ddb_playlist_t *(*)(const char *title))plt_alloc, .plt_free = (void (*)(ddb_playlist_t *plt))plt_free, //.plt_insert = plt_insert, .plt_set_fast_mode = (void (*)(ddb_playlist_t *plt, int fast))plt_set_fast_mode, .plt_is_fast_mode = (int (*)(ddb_playlist_t *plt))plt_is_fast_mode, .metacache_add_string = metacache_add_string, .metacache_remove_string = metacache_remove_string, .metacache_ref = metacache_ref, .metacache_unref = metacache_unref, .pl_find_meta_raw = (const char *(*) (DB_playItem_t *it, const char *key))pl_find_meta_raw, // ******* new 1.3 APIs ******** .streamer_dsp_chain_save = streamer_dsp_chain_save, // ******* new 1.4 APIs ******** .pl_get_meta = (int (*) (DB_playItem_t *it, const char *key, char *val, int size))pl_get_meta, .pl_get_meta_raw = (int (*) (DB_playItem_t *it, const char *key, char *val, int size))pl_get_meta_raw, .plt_get_meta = (int (*) (ddb_playlist_t *handle, const char *key, char *val, int size))plt_get_meta, .pl_meta_exists = (int (*) (DB_playItem_t *it, const char *key))pl_meta_exists, // ******* new 1.5 APIs ******** .vis_waveform_listen = vis_waveform_listen, .vis_waveform_unlisten = vis_waveform_unlisten, .vis_spectrum_listen = vis_spectrum_listen, .vis_spectrum_unlisten = vis_spectrum_unlisten, .audio_set_mute = audio_set_mute, .audio_is_mute = audio_is_mute, .background_job_increment = background_job_increment, .background_job_decrement = background_job_decrement, .have_background_jobs = have_background_jobs, .plt_get_idx = (int (*)(ddb_playlist_t *))plt_get_idx, .plt_save_n = plt_save_n, .plt_save_config = (int (*)(ddb_playlist_t *))plt_save_config, .listen_file_added = listen_file_added, .unlisten_file_added = unlisten_file_added, .listen_file_add_beginend = listen_file_add_beginend, .unlisten_file_add_beginend = unlisten_file_add_beginend, .plt_load2 = (DB_playItem_t * (*) (int visibility, ddb_playlist_t *plt, ddb_playItem_t *after, const char *fname, int *pabort, int (*callback)(DB_playItem_t *it, void *user_data), void *user_data))plt_load2, .plt_add_file2 = (int (*) (int visibility, ddb_playlist_t *plt, const char *fname, int (*callback)(DB_playItem_t *it, void *user_data), void *user_data))plt_add_file2, .plt_add_dir2 = (int (*) (int visibility, ddb_playlist_t *plt, const char *dirname, int (*callback)(DB_playItem_t *it, void *user_data), void *user_data))plt_add_dir2, .plt_insert_file2 = (ddb_playItem_t * (*) (int visibility, ddb_playlist_t *playlist, ddb_playItem_t *after, const char *fname, int *pabort, int (*callback)(DB_playItem_t *it, void *user_data), void *user_data))plt_insert_file2, .plt_insert_dir2 = (ddb_playItem_t *(*) (int visibility, ddb_playlist_t *plt, ddb_playItem_t *after, const char *dirname, int *pabort, int (*callback)(DB_playItem_t *it, void *user_data), void *user_data))plt_insert_dir2, .plt_add_files_begin = (int (*) (ddb_playlist_t *plt, int visibility))plt_add_files_begin, .plt_add_files_end = (void (*) (ddb_playlist_t *plt, int visibility))plt_add_files_end, .plt_deselect_all = (void (*) (ddb_playlist_t *plt))plt_deselect_all, // ******* new 1.6 APIs ******** .plt_set_scroll = (void (*) (ddb_playlist_t *plt, int scroll))plt_set_scroll, .plt_get_scroll = (int (*) (ddb_playlist_t *plt))plt_get_scroll, }; DB_functions_t *deadbeef = &deadbeef_api; const char * plug_get_config_dir (void) { return dbconfdir; } const char * plug_get_prefix (void) { return dbinstalldir; } const char * plug_get_doc_dir (void) { return dbdocdir; } const char * plug_get_plugin_dir (void) { return dbplugindir; } const char * plug_get_pixmap_dir (void) { return dbpixmapdir; } void plug_volume_set_db (float db) { volume_set_db (db); messagepump_push (DB_EV_VOLUMECHANGED, 0, 0, 0); } void plug_volume_set_amp (float amp) { volume_set_amp (amp); messagepump_push (DB_EV_VOLUMECHANGED, 0, 0, 0); } #define MAX_PLUGINS 100 DB_plugin_t *g_plugins[MAX_PLUGINS+1]; #define MAX_GUI_PLUGINS 10 char *g_gui_names[MAX_GUI_PLUGINS+1]; int g_num_gui_names; #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]; #define MAX_DSP_PLUGINS 10 DB_dsp_t *g_dsp_plugins[MAX_DSP_PLUGINS+1]; #define MAX_OUTPUT_PLUGINS 10 DB_output_t *g_output_plugins[MAX_OUTPUT_PLUGINS+1]; DB_output_t *output_plugin = NULL; #define MAX_PLAYLIST_PLUGINS 10 DB_playlist_t *g_playlist_plugins[MAX_PLAYLIST_PLUGINS+1]; static uintptr_t background_jobs_mutex; static int num_background_jobs; void plug_md5 (uint8_t sig[16], const char *in, int len) { md5_state_t st; md5_init (&st); md5_append (&st, in, len); md5_finish (&st, sig); } void plug_md5_to_str (char *str, const uint8_t sig[16]) { int i = 0; static const char hex[] = "0123456789abcdef"; for (i = 0; i < 16; i++) { *str++ = hex[(sig[i]&0xf0)>>4]; *str++ = hex[sig[i]&0xf]; } *str = 0; } // plugin control structures typedef struct plugin_s { void *handle; DB_plugin_t *plugin; struct plugin_s *next; } plugin_t; plugin_t *plugins; plugin_t *plugins_tail; float plug_playback_get_pos (void) { playItem_t *trk = streamer_get_playing_track (); float dur = trk ? pl_get_item_duration (trk) : -1; if (!trk || dur <= 0) { if (trk) { pl_item_unref (trk); } return 0; } if (trk) { pl_item_unref (trk); } return streamer_get_playpos () * 100 / dur; } void plug_playback_set_pos (float pos) { playItem_t *trk = streamer_get_playing_track (); float dur = trk ? pl_get_item_duration (trk) : -1; if (!trk || dur <= 0) { if (trk) { pl_item_unref (trk); } return; } float t = pos * dur / 100.f; if (trk) { pl_item_unref (trk); } streamer_set_seek (t); } int plug_init_plugin (DB_plugin_t* (*loadfunc)(DB_functions_t *), void *handle) { DB_plugin_t *plugin_api = loadfunc (&deadbeef_api); if (!plugin_api) { return -1; } // check if same plugin with the same or bigger version is loaded already plugin_t *prev = NULL; for (plugin_t *p = plugins; p; prev = p, p = p->next) { int same_id = p->plugin->id && plugin_api->id && !strcmp (p->plugin->id, plugin_api->id); int same_name = (!p->plugin->id || !plugin_api->id) && p->plugin->name && plugin_api->name && !strcmp (p->plugin->name, plugin_api->name); if (same_id || same_name) { if (plugin_api->version_major > p->plugin->version_major || (plugin_api->version_major == p->plugin->version_major && plugin_api->version_minor > p->plugin->version_minor)) { trace ("found newer version of plugin \"%s\" (%s), replacing\n", plugin_api->id, plugin_api->name); // unload older plugin before replacing dlclose (p->handle); if (prev) { prev->next = p->next; } else { plugins = p->next; } if (p->handle) { dlclose (p->handle); } free (p); } else { trace ("found copy of plugin \"%s\" (%s), but newer version is already loaded\n", plugin_api->id, plugin_api->name) return -1; } } } #if !DISABLE_VERSIONCHECK if (plugin_api->api_vmajor != 0 || plugin_api->api_vminor != 0) { // version check enabled if (DB_API_VERSION_MAJOR != 9 && DB_API_VERSION_MINOR != 9) { if (plugin_api->api_vmajor != DB_API_VERSION_MAJOR || plugin_api->api_vminor > DB_API_VERSION_MINOR) { trace ("\033[0;31mWARNING: plugin \"%s\" wants API v%d.%d (got %d.%d), will not be loaded\033[0;m\n", plugin_api->name, plugin_api->api_vmajor, plugin_api->api_vminor, DB_API_VERSION_MAJOR, DB_API_VERSION_MINOR); return -1; } } } else { trace ("\033[0;31mWARNING: plugin \"%s\" has disabled version check. please don't distribute it!\033[0;m\n", plugin_api->name); } #endif plugin_t *plug = malloc (sizeof (plugin_t)); memset (plug, 0, sizeof (plugin_t)); plug->plugin = plugin_api; plug->handle = handle; if (plugins_tail) { plugins_tail->next = plug; plugins_tail = plug; } else { plugins = plugins_tail = plug; } return 0; } static int dirent_alphasort (const struct dirent **a, const struct dirent **b) { return strcmp ((*a)->d_name, (*b)->d_name); } void plug_remove_plugin (void *p) { int i; for (i = 0; g_plugins[i]; i++) { if (g_plugins[i] == p) { memmove (&g_plugins[i], &g_plugins[i+1], (MAX_PLUGINS+1-i-1) * sizeof (void*)); } } for (i = 0; g_decoder_plugins[i]; i++) { if (g_decoder_plugins[i] == p) { memmove (&g_decoder_plugins[i], &g_decoder_plugins[i+1], (MAX_DECODER_PLUGINS+1-i-1) * sizeof (void*)); break; } } for (i = 0; g_vfs_plugins[i]; i++) { if (g_vfs_plugins[i] == p) { memmove (&g_vfs_plugins[i], &g_vfs_plugins[i+1], (MAX_VFS_PLUGINS+1-i-1) * sizeof (void*)); break; } } for (i = 0; g_dsp_plugins[i]; i++) { if (g_dsp_plugins[i] == p) { memmove (&g_dsp_plugins[i], &g_dsp_plugins[i+1], (MAX_DSP_PLUGINS+1-i-1) * sizeof (void*)); break; } } for (i = 0; g_output_plugins[i]; i++) { if (g_output_plugins[i] == p) { memmove (&g_output_plugins[i], &g_output_plugins[i+1], (MAX_OUTPUT_PLUGINS+1-i-1) * sizeof (void*)); break; } } for (i = 0; g_playlist_plugins[i]; i++) { if (g_playlist_plugins[i] == p) { memmove (&g_playlist_plugins[i], &g_playlist_plugins[i+1], (MAX_PLAYLIST_PLUGINS+1-i-1) * sizeof (void*)); break; } } } // d_name must be writable w/o sideeffects; contain valid .so name // l must be strlen(d_name) static int load_plugin (const char *plugdir, char *d_name, int l) { // hack for osx to skip *.0.so files if (strstr (d_name, ".0.so")) { return -1; } char fullname[PATH_MAX]; snprintf (fullname, PATH_MAX, "%s/%s", plugdir, d_name); // check if the file exists, to avoid printing bogus errors struct stat s; if (0 != stat (fullname, &s)) { return -1; } trace ("loading plugin %s/%s\n", plugdir, d_name); void *handle = dlopen (fullname, RTLD_NOW); if (!handle) { trace ("dlopen error: %s\n", dlerror ()); #ifdef ANDROID return -1; #else strcpy (fullname + strlen(fullname) - 3, ".fallback.so"); trace ("trying %s...\n", fullname); handle = dlopen (fullname, RTLD_NOW); if (!handle) { //trace ("dlopen error: %s\n", dlerror ()); return -1; } else { fprintf (stderr, "successfully started fallback plugin %s\n", fullname); } #endif } d_name[l-3] = 0; strcat (d_name, "_load"); #ifndef ANDROID DB_plugin_t *(*plug_load)(DB_functions_t *api) = dlsym (handle, d_name); #else DB_plugin_t *(*plug_load)(DB_functions_t *api) = dlsym (handle, d_name+3); #endif if (!plug_load) { trace ("dlsym error: %s\n", dlerror ()); dlclose (handle); return -1; } if (plug_init_plugin (plug_load, handle) < 0) { d_name[l-3] = 0; dlclose (handle); return -1; } return 0; } static int load_gui_plugin (const char **plugdirs) { char conf_gui_plug[100]; conf_get_str ("gui_plugin", "GTK2", conf_gui_plug, sizeof (conf_gui_plug)); char name[100]; // try to load selected plugin for (int i = 0; g_gui_names[i]; i++) { trace ("checking GUI plugin: %s\n", g_gui_names[i]); if (!strcmp (g_gui_names[i], conf_gui_plug)) { trace ("found selected GUI plugin: %s\n", g_gui_names[i]); for (int n = 0; plugdirs[n]; n++) { snprintf (name, sizeof (name), "ddb_gui_%s.so", conf_gui_plug); if (!load_plugin (plugdirs[n], name, strlen (name))) { return 0; } } break; } } trace ("selected GUI plugin not found or failed to load, trying to find another GUI plugin\n"); // try any plugin for (int i = 0; g_gui_names[i]; i++) { for (int n = 0; plugdirs[n]; n++) { snprintf (name, sizeof (name), "ddb_gui_%s.so", g_gui_names[i]); if (!load_plugin (plugdirs[n], name, strlen (name))) { return 0; } else { trace ("plugin not found or failed to load\n"); } } } return -1; } static int load_plugin_dir (const char *plugdir, int gui_scan) { int n = 0; char conf_blacklist_plugins[1000]; conf_get_str ("blacklist_plugins", "", conf_blacklist_plugins, sizeof (conf_blacklist_plugins)); if (gui_scan) { trace ("searching for GUI plugins in %s\n", plugdir); } else { trace ("loading plugins from %s\n", plugdir); } struct dirent **namelist = NULL; n = scandir (plugdir, &namelist, NULL, dirent_alphasort); if (n < 0) { if (namelist) { free (namelist); } return 0; } else { trace ("plug_load_all: scandir found %d files\n", n); int i; for (i = 0; i < n; i++) { // skip hidden files and fallback plugins while (namelist[i]->d_name[0] != '.' #ifndef ANDROID && !strstr (namelist[i]->d_name, ".fallback.") #else && !strstr (namelist[i]->d_name, "libdeadbeef") #endif ) { int l = strlen (namelist[i]->d_name); if (l < 3) { break; } if (strcasecmp (&namelist[i]->d_name[l-3], ".so")) { break; } char d_name[256]; memcpy (d_name, namelist[i]->d_name, l+1); #ifndef ANDROID // no blacklisted const uint8_t *p = conf_blacklist_plugins; while (*p) { const uint8_t *e = p; while (*e && *e > 0x20) { e++; } if (l-3 == e-p) { if (!strncmp (p, d_name, e-p)) { p = NULL; break; } } p = e; while (*p && *p <= 0x20) { p++; } } if (!p) { trace ("plugin %s is blacklisted in config file\n", d_name); break; } #endif // add gui plugin names if (!strncmp (d_name, "ddb_gui_", 8)) { if (gui_scan) { trace ("found gui plugin %s\n", d_name); if (g_num_gui_names >= MAX_GUI_PLUGINS) { fprintf (stderr, "too many gui plugins\n"); break; // no more gui plugins allowed } char *nm = d_name + 8; char *e = strrchr (nm, '.'); if (!e) { break; } if (strcmp (e, ".so")) { break; } *e = 0; // ignore fallbacks e = strrchr (nm, '.'); if (e && !strcasecmp (e, ".fallback")) { break; } // add to list // FIXME check for gui plugins dupes g_gui_names[g_num_gui_names++] = strdup (nm); g_gui_names[g_num_gui_names] = NULL; trace ("added %s gui plugin\n", nm); } break; } if (!gui_scan) { if (0 != load_plugin (plugdir, d_name, l)) { trace ("plugin not found or failed to load\n"); } } break; } free (namelist[i]); } free (namelist); } return 0; } int plug_load_all (void) { #if DISABLE_VERSIONCHECK trace ("\033[0;31mDISABLE_VERSIONCHECK=1! do not distribute!\033[0;m\n"); #endif background_jobs_mutex = mutex_create (); const char *dirname = deadbeef->get_plugin_dir (); #ifndef ANDROID char *xdg_local_home = getenv ("XDG_LOCAL_HOME"); char xdg_plugin_dir[1024]; char xdg_plugin_dir_explicit_arch[1024]; if (xdg_local_home) { strncpy (xdg_plugin_dir, xdg_local_home, sizeof (xdg_plugin_dir)); xdg_plugin_dir[sizeof(xdg_plugin_dir)-1] = 0; } else { char *homedir = getenv ("HOME"); if (!homedir) { trace ("plug_load_all: warning: unable to find home directory\n"); xdg_plugin_dir[0] = 0; } else { // multilib support: // 1. load from lib$ARCH if present // 2. load from lib if present int written = snprintf (xdg_plugin_dir, sizeof (xdg_plugin_dir), "%s/.local/lib/deadbeef", homedir); if (written > sizeof (xdg_plugin_dir)) { trace ("warning: XDG_LOCAL_HOME value is too long: %s. Ignoring.", xdg_local_home); xdg_plugin_dir[0] = 0; } written = snprintf (xdg_plugin_dir_explicit_arch, sizeof (xdg_plugin_dir_explicit_arch), "%s/.local/lib%d/deadbeef", homedir, (int)(sizeof (long) * 8)); if (written > sizeof (xdg_plugin_dir_explicit_arch)) { trace ("warning: XDG_LOCAL_HOME value is too long: %s. Ignoring.", xdg_local_home); xdg_plugin_dir_explicit_arch[0] = 0; } } } // load from HOME 1st, than replace from installdir if needed const char *plugins_dirs[] = { xdg_plugin_dir_explicit_arch, xdg_plugin_dir, dirname, NULL }; // If xdg_plugin_dir and dirname is the same, we should avoid each plugin // to be load twice. // XXX: Here absolute path is assumed, however if dirname is a relative // path it won't work. if (strcmp(xdg_plugin_dir, dirname) == 0) { plugins_dirs[1] = NULL; } #else const char *plugins_dirs[] = { dirname, NULL }; #endif int k = 0; #ifndef ANDROID // load gui plugin before others while (plugins_dirs[k]) { const char *plugdir = plugins_dirs[k++]; if (!(*plugdir)) { continue; } load_plugin_dir (plugdir, 1); } printf ("load gui plugin\n"); load_gui_plugin (plugins_dirs); #endif k = 0; while (plugins_dirs[k]) { const char *plugdir = plugins_dirs[k++]; if (!(*plugdir)) { continue; } load_plugin_dir (plugdir, 0); } #ifdef ANDROID char plugin_path[1000]; strncpy (plugin_path, conf_get_str_fast ("android.plugin_path", ""), sizeof (plugin_path)-1); plugin_path[sizeof(plugin_path)-1] = 0; char *p = plugin_path; while (*p) { while (*p == ':') { p++; } if (!(*p)) { break; } char *e = strchr (p, ':'); if (e) { *e = 0; } char path[PATH_MAX]; snprintf (path, sizeof (path), "/data/data/%s/lib", p); load_plugin_dir (path); if (!e) { break; } p = e+1; } #endif // load all compiled-in modules #define PLUG(n) extern DB_plugin_t * n##_load (DB_functions_t *api); #include "moduleconf.h" #undef PLUG #define PLUG(n) plug_init_plugin (n##_load, NULL); #include "moduleconf.h" #undef PLUG #ifdef ANDROID #define PLUG(n) extern DB_plugin_t * n##_load (DB_functions_t *api); #include "moduleconf-android.h" #undef PLUG #define PLUG(n) plug_init_plugin (n##_load, NULL); #include "moduleconf-android.h" #undef PLUG #endif plugin_t *plug; // categorize plugins int numplugins = 0; int numdecoders = 0; int numvfs = 0; int numoutput = 0; int numdsp = 0; int numplaylist = 0; for (plug = plugins; plug; plug = plug->next) { g_plugins[numplugins++] = plug->plugin; if (plug->plugin->type == DB_PLUGIN_DECODER) { // trace ("found decoder plugin %s\n", plug->plugin->name); if (numdecoders >= MAX_DECODER_PLUGINS) { break; } g_decoder_plugins[numdecoders++] = (DB_decoder_t *)plug->plugin; } else if (plug->plugin->type == DB_PLUGIN_VFS) { // trace ("found vfs plugin %s\n", plug->plugin->name); if (numvfs >= MAX_VFS_PLUGINS) { break; } g_vfs_plugins[numvfs++] = (DB_vfs_t *)plug->plugin; } else if (plug->plugin->type == DB_PLUGIN_OUTPUT) { // trace ("found output plugin %s\n", plug->plugin->name); if (numoutput >= MAX_OUTPUT_PLUGINS) { break; } g_output_plugins[numoutput++] = (DB_output_t *)plug->plugin; } else if (plug->plugin->type == DB_PLUGIN_DSP) { // trace ("found dsp plugin %s\n", plug->plugin->name); if (numdsp >= MAX_DSP_PLUGINS) { break; } g_dsp_plugins[numdsp++] = (DB_dsp_t *)plug->plugin; } else if (plug->plugin->type == DB_PLUGIN_PLAYLIST) { if (numplaylist >= MAX_PLAYLIST_PLUGINS) { break; } g_playlist_plugins[numplaylist++] = (DB_playlist_t *)plug->plugin; } } // start plugins plugin_t *prev = NULL; for (plug = plugins; plug;) { if (plug->plugin->type != DB_PLUGIN_GUI && plug->plugin->start) { if (plug->plugin->start () < 0) { fprintf (stderr, "plugin %s failed to start, deactivated.\n", plug->plugin->name); if (plug->plugin->stop) { plug->plugin->stop (); } if (plug->handle) { dlclose (plug->handle); } plug_remove_plugin (plug->plugin); if (prev) { prev->next = plug->next; } else { plugins = plug->next; } plugin_t *next = plug->next; free (plug); plug = next; continue; } } prev = plug; plug = plug->next; } // trace ("numplugins: %d, numdecoders: %d, numvfs: %d\n", numplugins, numdecoders, numvfs); g_plugins[numplugins] = NULL; g_decoder_plugins[numdecoders] = NULL; g_vfs_plugins[numvfs] = NULL; g_output_plugins[numoutput] = NULL; g_dsp_plugins[numdsp] = NULL; g_playlist_plugins[numplaylist] = NULL; // select output plugin if (plug_select_output () < 0) { trace ("failed to find output plugin!\n"); return -1; } return 0; } void plug_connect_all (void) { plugin_t *plug; plugin_t *prev = NULL; for (plug = plugins; plug;) { if (plug->plugin->connect) { if (plug->plugin->connect () < 0) { fprintf (stderr, "plugin %s failed to connect to dependencies, deactivated.\n", plug->plugin->name); if (plug->plugin->disconnect) { plug->plugin->disconnect (); } if (plug->plugin->type != DB_PLUGIN_GUI && plug->plugin->stop) { plug->plugin->stop (); } if (plug->handle) { dlclose (plug->handle); } plug_remove_plugin (plug->plugin); if (prev) { prev->next = plug->next; } else { plugins = plug->next; } plugin_t *next = plug->next; free (plug); plug = next; continue; } } prev = plug; plug = plug->next; } } void plug_disconnect_all (void) { trace ("plug_disconnect_all\n"); plugin_t *plug; plugin_t *prev = NULL; for (plug = plugins; plug;) { if (plug->plugin->disconnect) { if (plug->plugin->disconnect () < 0) { trace ("plugin %s failed to disconnect\n", plug->plugin->name); } } prev = plug; plug = plug->next; } } void plug_unload_all (void) { trace ("plug_unload_all\n"); plugin_t *p; for (p = plugins; p; p = p->next) { if (p->plugin->stop) { trace ("stopping %s...\n", p->plugin->name); fflush (stderr); p->plugin->stop (); } } trace ("stopped all plugins\n"); while (plugins) { plugin_t *next = plugins->next; if (plugins->handle) { dlclose (plugins->handle); } free (plugins); plugins = next; } for (int i = 0; g_gui_names[i]; i++) { free (g_gui_names[i]); g_gui_names[i] = NULL; } plugins_tail = NULL; memset (g_plugins, 0, sizeof (g_plugins)); memset (g_gui_names, 0, sizeof (g_gui_names)); g_num_gui_names = 0; memset (g_decoder_plugins, 0, sizeof (g_decoder_plugins)); memset (g_vfs_plugins, 0, sizeof (g_vfs_plugins)); memset (g_dsp_plugins, 0, sizeof (g_dsp_plugins)); memset (g_output_plugins, 0, sizeof (g_output_plugins)); output_plugin = NULL; memset (g_playlist_plugins, 0, sizeof (g_playlist_plugins)); trace ("all plugins had been unloaded\n"); if (background_jobs_mutex) { mutex_free (background_jobs_mutex); background_jobs_mutex = 0; } } void plug_cleanup (void) { plug_free_decoder_ids (); } struct DB_decoder_s ** plug_get_decoder_list (void) { return g_decoder_plugins; } struct DB_vfs_s ** plug_get_vfs_list (void) { return g_vfs_plugins; } struct DB_output_s ** plug_get_output_list (void) { return g_output_plugins; } struct DB_dsp_s ** plug_get_dsp_list (void) { return g_dsp_plugins; } struct DB_playlist_s ** plug_get_playlist_list (void) { return g_playlist_plugins; } struct DB_plugin_s ** plug_get_list (void) { return g_plugins; } const char ** plug_get_gui_names (void) { return (const char **)g_gui_names; } DB_output_t * plug_get_output (void) { return output_plugin; } int plug_select_output (void) { #ifdef ANDROID return 0; #else char outplugname[100]; conf_get_str ("output_plugin", "ALSA output plugin", outplugname, sizeof (outplugname)); for (int i = 0; g_output_plugins[i]; i++) { DB_output_t *p = g_output_plugins[i]; if (!strcmp (p->plugin.name, outplugname)) { trace ("selected output plugin: %s\n", outplugname); output_plugin = p; break; } } if (!output_plugin) { output_plugin = g_output_plugins[0]; if (output_plugin) { trace ("selected output plugin: %s\n", output_plugin->plugin.name); conf_set_str ("output_plugin", output_plugin->plugin.name); } } if (!output_plugin) { return -1; } messagepump_push (DB_EV_OUTPUTCHANGED, 0, 0, 0); return 0; #endif } void plug_reinit_sound (void) { DB_output_t *prev = plug_get_output (); int state = OUTPUT_STATE_STOPPED; ddb_waveformat_t fmt = {0}; streamer_get_output_format (&fmt); if (prev) { state = prev->state (); if (!fmt.channels) { memcpy (&fmt, &prev->fmt, sizeof (fmt)); } prev->free (); } if (plug_select_output () < 0) { char outplugname[100]; conf_get_str ("output_plugin", "ALSA output plugin", outplugname, sizeof (outplugname)); trace ("failed to select output plugin %s\nreverted to %s\n", outplugname, prev->plugin.name); output_plugin = prev; } DB_output_t *output = plug_get_output (); if (fmt.channels) { output->setformat (&fmt); } if (output->init () < 0) { streamer_reset (1); streamer_set_nextsong (-2, 0); return; } if (state != OUTPUT_STATE_PAUSED && state != OUTPUT_STATE_STOPPED) { if (output->play () < 0) { trace ("failed to reinit sound output\n"); streamer_set_nextsong (-2, 0); } } } // list of all unique decoder ids used in current session static char *decoder_ids[MAX_DECODER_PLUGINS]; const char * plug_get_decoder_id (const char *id) { int i; char **lastnull = NULL; for (i = 0; i < MAX_DECODER_PLUGINS; i++) { if (decoder_ids[i] && !strcmp (id, decoder_ids[i])) { return decoder_ids[i]; } else if (!lastnull && !decoder_ids[i]) { lastnull = &decoder_ids[i]; } } if (!lastnull) { return NULL; } char *newid = strdup (id); *lastnull = newid; return newid; } void plug_remove_decoder_id (const char *id) { int i; for (i = 0; i < MAX_DECODER_PLUGINS; i++) { if (decoder_ids[i] && !strcmp (decoder_ids[i], id)) { free (decoder_ids[i]); decoder_ids[i] = NULL; } } } void plug_free_decoder_ids (void) { int i; for (i = 0; i < MAX_DECODER_PLUGINS; i++) { if (decoder_ids[i]) { free (decoder_ids[i]); decoder_ids[i] = NULL; } } } DB_decoder_t * plug_get_decoder_for_id (const char *id) { DB_decoder_t **plugins = plug_get_decoder_list (); for (int c = 0; plugins[c]; c++) { if (!strcmp (id, plugins[c]->plugin.id)) { return plugins[c]; } } return NULL; } DB_plugin_t * plug_get_for_id (const char *id) { DB_plugin_t **plugins = plug_get_list (); for (int c = 0; plugins[c]; c++) { if (plugins[c]->id && !strcmp (id, plugins[c]->id)) { return plugins[c]; } } return NULL; } int plug_is_local_file (const char *fname) { if (!strncasecmp (fname, "file://", 7)) { return 1; } const char *f = fname; for (; *f; f++) { if (!strncmp (f, "://", 3)) { DB_vfs_t **plug = plug_get_vfs_list (); for (int i = 0; plug[i]; i++) { if (plug[i]->get_schemes && plug[i]->is_streaming && plug[i]->is_streaming()) { const char **sch = plug[i]->get_schemes (); for (int k = 0; sch[k]; k++) { if (!strncmp (sch[k], fname, strlen (sch[k]))) { return 0; } } } } break; } } return 1; } void background_job_increment (void) { mutex_lock (background_jobs_mutex); num_background_jobs++; mutex_unlock (background_jobs_mutex); } void background_job_decrement (void) { mutex_lock (background_jobs_mutex); num_background_jobs--; mutex_unlock (background_jobs_mutex); } int have_background_jobs (void) { return num_background_jobs; }