res_musiconhold: Add new 'playlist' mode
authorSean Bright <sean.bright@gmail.com>
Wed, 18 Sep 2019 11:56:05 +0000 (07:56 -0400)
committerSean Bright <sean.bright@gmail.com>
Wed, 25 Sep 2019 11:24:07 +0000 (06:24 -0500)
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

configs/samples/extconfig.conf.sample
configs/samples/musiconhold.conf.sample
contrib/ast-db-manage/config/versions/fbb7766f17bc_add_playlist_to_moh.py [new file with mode: 0644]
doc/CHANGES-staging/moh-playlist.txt [new file with mode: 0644]
res/res_musiconhold.c

index 9e13cac..b633faf 100644 (file)
@@ -95,6 +95,7 @@
 ;queue_rules => odbc,asterisk
 ;acls => odbc,asterisk
 ;musiconhold => mysql,general
+;musiconhold_entry => mysql,general
 ;queue_log => mysql,general
 ;
 ;
index 741bde6..1090bbe 100644 (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
 ; 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 (file)
index 0000000..0cb5ddb
--- /dev/null
@@ -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 (file)
index 0000000..14cb022
--- /dev/null
@@ -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.
index f770075..1bacb11 100644 (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);