; 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
; 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
;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
; =========
--- /dev/null
+"""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')
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")) {
}
}
}
+
+ AST_VECTOR_COMPACT(&mohclass->files);
}
static int moh_scan_files(struct mohclass *class) {
}
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")) {
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. "
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);
}
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"))
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);