res_musiconhold: Add new 'playlist' mode

Allow the list of files to be played to be provided explicitly in the
music class's configuration. The primary driver for this change is to
allow URLs to be used for MoH.

Change-Id: I9f43b80b43880980b18b2bee26ec09429d0b92fa
This commit is contained in:
Sean Bright 2019-09-18 07:56:05 -04:00
parent 51e315765b
commit 9f304170f6
5 changed files with 139 additions and 2 deletions

View File

@ -95,6 +95,7 @@
;queue_rules => odbc,asterisk
;acls => odbc,asterisk
;musiconhold => mysql,general
;musiconhold_entry => mysql,general
;queue_log => mysql,general
;
;

View File

@ -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
; =========

View File

@ -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')

View File

@ -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.

View File

@ -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);