diff --git a/configs/samples/extconfig.conf.sample b/configs/samples/extconfig.conf.sample index 9e13cacda41d8e7cf5a3ed51ea2e71db9b0fc063..b633fafa61feb512c9740f29af67c6894771532d 100644 --- a/configs/samples/extconfig.conf.sample +++ b/configs/samples/extconfig.conf.sample @@ -95,6 +95,7 @@ ;queue_rules => odbc,asterisk ;acls => odbc,asterisk ;musiconhold => mysql,general +;musiconhold_entry => mysql,general ;queue_log => mysql,general ; ; diff --git a/configs/samples/musiconhold.conf.sample b/configs/samples/musiconhold.conf.sample index 741bde60379df90a9933a9f39340bbd2f56fb271..1090bbef16d1bffb5d95bfa2d11d8c8f171a6044 100644 --- a/configs/samples/musiconhold.conf.sample +++ b/configs/samples/musiconhold.conf.sample @@ -13,6 +13,7 @@ ; valid mode options: ; files -- read files from a directory in any Asterisk supported ; media format +; playlist -- provide a fixed list of filenames or URLs to play ; quietmp3 -- default ; mp3 -- loud ; mp3nb -- unbuffered @@ -44,6 +45,22 @@ ; this, res_musiconhold will skip the files it is not able to ; understand when it loads. ; +; ========= +; Playlist (native) music on hold +; ========= +; +; This mode is similar to 'files' mode in that it plays through a list +; of files, but instead of scanning a directory the files are +; explicitly configured using one or more 'entry' options. +; +; Each entry must be one of: +; +; * An absolute path to the file to be played, without an extension. +; * A URL +; +; The entries are played in the order in which they appear in the +; configuration. The 'sort' option is not used for this mode. +; [default] mode=files @@ -71,6 +88,12 @@ directory=moh ;directory=moh ;sort=alpha ; Sort the files in alphabetical order. +;[sales-queue-hold] +;mode=playlist +;entry=/var/lib/asterisk/sounds/en/yourcallisimportant +;entry=http://example.local/sales-queue-hold-music.ulaw +;entry=/var/lib/asterisk/moh/macroform-robot_dity + ; ========= ; Other (non-native) playback methods ; ========= diff --git a/contrib/ast-db-manage/config/versions/fbb7766f17bc_add_playlist_to_moh.py b/contrib/ast-db-manage/config/versions/fbb7766f17bc_add_playlist_to_moh.py new file mode 100644 index 0000000000000000000000000000000000000000..0cb5ddb8316f860797dbdb2b7409d53058b734e5 --- /dev/null +++ b/contrib/ast-db-manage/config/versions/fbb7766f17bc_add_playlist_to_moh.py @@ -0,0 +1,54 @@ +"""add playlist to moh + +Revision ID: fbb7766f17bc +Revises: 3a094a18e75b +Create Date: 2019-09-18 10:24:18.731798 + +""" + +# revision identifiers, used by Alembic. +revision = 'fbb7766f17bc' +down_revision = '3a094a18e75b' + +from alembic import op +import sqlalchemy as sa + + +def enum_update(table_name, column_name, enum_name, enum_values): + if op.get_context().bind.dialect.name != 'postgresql': + if op.get_context().bind.dialect.name == 'mssql': + op.drop_constraint('ck_musiconhold_mode_moh_mode_values', 'musiconhold') + op.alter_column(table_name, column_name, + type_=sa.Enum(*enum_values, name=enum_name)) + return + + # Postgres requires a few more steps + tmp = enum_name + '_tmp' + + op.execute('ALTER TYPE ' + enum_name + ' RENAME TO ' + tmp) + + updated = sa.Enum(*enum_values, name=enum_name) + updated.create(op.get_bind(), checkfirst=False) + + op.execute('ALTER TABLE ' + table_name + ' ALTER COLUMN ' + column_name + + ' TYPE ' + enum_name + ' USING mode::text::' + enum_name) + + op.execute('DROP TYPE ' + tmp) + + +def upgrade(): + op.create_table( + 'musiconhold_entry', + sa.Column('name', sa.String(80), primary_key=True, nullable=False), + sa.Column('position', sa.Integer, primary_key=True, nullable=False), + sa.Column('entry', sa.String(1024), nullable=False) + ) + op.create_foreign_key('fk_musiconhold_entry_name_musiconhold', 'musiconhold_entry', 'musiconhold', ['name'], ['name']) + enum_update('musiconhold', 'mode', 'moh_mode_values', + ['custom', 'files', 'mp3nb', 'quietmp3nb', 'quietmp3', 'playlist']) + + +def downgrade(): + enum_update('musiconhold', 'mode', 'moh_mode_values', + ['custom', 'files', 'mp3nb', 'quietmp3nb', 'quietmp3']) + op.drop_table('musiconhold_entry') diff --git a/doc/CHANGES-staging/moh-playlist.txt b/doc/CHANGES-staging/moh-playlist.txt new file mode 100644 index 0000000000000000000000000000000000000000..14cb0224accdbe2be8756ece7f0c90b0e6af10d4 --- /dev/null +++ b/doc/CHANGES-staging/moh-playlist.txt @@ -0,0 +1,5 @@ +Subject: res_musiconhold + +A new mode - playlist - has been added to res_musiconhold. This mode allows the +user to specify the files (or URLs) to play explicitly by putting them directly +in musiconhold.conf. diff --git a/res/res_musiconhold.c b/res/res_musiconhold.c index f7700758731fbaa89f3f384810f2adcb295519de..1bacb116a1fe5a4d827ef359bc24823e3bf5ecb3 100644 --- a/res/res_musiconhold.c +++ b/res/res_musiconhold.c @@ -1081,6 +1081,20 @@ static void moh_parse_options(struct ast_variable *var, struct mohclass *mohclas ast_copy_string(mohclass->name, var->value, sizeof(mohclass->name)); } else if (!strcasecmp(var->name, "mode")) { ast_copy_string(mohclass->mode, var->value, sizeof(mohclass->mode)); + } else if (!strcasecmp(var->name, "entry")) { + if (ast_begins_with(var->value, "/") || ast_begins_with(var->value, "http://") || ast_begins_with(var->value, "https://")) { + char *dup = ast_strdup(var->value); + if (!dup) { + continue; + } + if (ast_begins_with(dup, "/") && strrchr(dup, '.')) { + ast_log(LOG_WARNING, "The playlist entry '%s' may include an extension, which could prevent it from playing.\n", + dup); + } + AST_VECTOR_APPEND(&mohclass->files, dup); + } else { + ast_log(LOG_ERROR, "Playlist entries must be a URL or absolute path, '%s' provided.\n", var->value); + } } else if (!strcasecmp(var->name, "directory")) { ast_copy_string(mohclass->dir, var->value, sizeof(mohclass->dir)); } else if (!strcasecmp(var->name, "application")) { @@ -1130,6 +1144,8 @@ static void moh_parse_options(struct ast_variable *var, struct mohclass *mohclas } } } + + AST_VECTOR_COMPACT(&mohclass->files); } static int moh_scan_files(struct mohclass *class) { @@ -1333,6 +1349,13 @@ static int _moh_register(struct mohclass *moh, int reload, int unref, const char } return -1; } + } else if (!strcasecmp(moh->mode, "playlist")) { + if (!AST_VECTOR_SIZE(&moh->files)) { + if (unref) { + moh = mohclass_unref(moh, "unreffing potential new moh class (no playlist entries)"); + } + return -1; + } } else if (!strcasecmp(moh->mode, "mp3") || !strcasecmp(moh->mode, "mp3nb") || !strcasecmp(moh->mode, "quietmp3") || !strcasecmp(moh->mode, "quietmp3nb") || !strcasecmp(moh->mode, "httpmp3") || !strcasecmp(moh->mode, "custom")) { @@ -1485,6 +1508,32 @@ static struct mohclass *_moh_class_malloc(const char *file, int line, const char static struct ast_variable *load_realtime_musiconhold(const char *name) { struct ast_variable *var = ast_load_realtime("musiconhold", "name", name, SENTINEL); + + if (var) { + const char *mode = ast_variable_find_in_list(var, "mode"); + if (ast_strings_equal(mode, "playlist")) { + struct ast_variable *entries = ast_load_realtime("musiconhold_entry", "name", name, SENTINEL); + struct ast_variable *cur = entries; + size_t entry_count = 0; + for (; cur; cur = cur->next) { + if (!strcmp(cur->name, "entry")) { + struct ast_variable *dup = ast_variable_new(cur->name, cur->value, ""); + if (dup) { + entry_count++; + ast_variable_list_append(&var, dup); + } + } + } + ast_variables_destroy(entries); + + if (entry_count == 0) { + /* Behave as though this class doesn't exist */ + ast_variables_destroy(var); + var = NULL; + } + } + } + if (!var) { ast_log(LOG_WARNING, "Music on Hold class '%s' not found in memory/database. " @@ -1551,7 +1600,7 @@ static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, con ast_variables_destroy(var); if (ast_strlen_zero(mohclass->dir)) { - if (!strcasecmp(mohclass->mode, "custom")) { + if (!strcasecmp(mohclass->mode, "custom") || !strcasecmp(mohclass->mode, "playlist")) { strcpy(mohclass->dir, "nodir"); } else { ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", mohclass->name); @@ -1605,6 +1654,11 @@ static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, con } ast_set_flag(mohclass, MOH_RANDOMIZE); } + } else if (!strcasecmp(mohclass->mode, "playlist")) { + if (!AST_VECTOR_SIZE(&mohclass->files)) { + mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (no playlist entries)"); + return -1; + } } else if (!strcasecmp(mohclass->mode, "mp3") || !strcasecmp(mohclass->mode, "mp3nb") || !strcasecmp(mohclass->mode, "quietmp3") || !strcasecmp(mohclass->mode, "quietmp3nb") || !strcasecmp(mohclass->mode, "httpmp3") || !strcasecmp(mohclass->mode, "custom")) { if (!strcasecmp(mohclass->mode, "custom")) @@ -1846,7 +1900,7 @@ static int load_moh_classes(int reload) ast_copy_string(class->name, cat, sizeof(class->name)); if (ast_strlen_zero(class->dir)) { - if (!strcasecmp(class->mode, "custom")) { + if (!strcasecmp(class->mode, "custom") || !strcasecmp(class->mode, "playlist")) { strcpy(class->dir, "nodir"); } else { ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", class->name);