diff --git a/CHANGES b/CHANGES
index ec44e30fc4b3c0580306368d20ea322d222d912b..15b4d0c670d0509819900f921db9443412b28ef1 100644
--- a/CHANGES
+++ b/CHANGES
@@ -23,6 +23,15 @@ ARI
  * To complement the "create" method, a "dial" method has been added to the channels
  resource in order to place a call to a created channel.
 
+ * All operations that initiate playback of media on a resource now support
+   a list of media URIs. The list of URIs are played in the order they are
+   presented to the resource. A new event, "PlaybackContinuing", is raised when
+   a media URI finishes but before the next media URI starts. When a list is
+   played, the "Playback" model will contain the optional attribute
+   "next_media_uri", which specifies the next media URI in the list to be played
+   back to the resource. The "PlaybackFinished" event is raised when all media
+   URIs are done.
+
 Applications
 ------------------
 
diff --git a/include/asterisk/stasis_app_playback.h b/include/asterisk/stasis_app_playback.h
index b35299581cfe53824b20a225cd4858c569b21f26..0038fd6d0810bbead8249570d16e6ae523ae49fd 100644
--- a/include/asterisk/stasis_app_playback.h
+++ b/include/asterisk/stasis_app_playback.h
@@ -41,6 +41,8 @@ enum stasis_app_playback_state {
 	STASIS_PLAYBACK_STATE_PLAYING,
 	/*! The media is currently playing */
 	STASIS_PLAYBACK_STATE_PAUSED,
+	/*! The media is transitioning to the next in the list */
+	STASIS_PLAYBACK_STATE_CONTINUING,
 	/*! The media has stopped playing */
 	STASIS_PLAYBACK_STATE_COMPLETE,
 	/*! The playback was canceled. */
@@ -84,7 +86,8 @@ enum stasis_app_playback_target_type {
  * available codecs for the channel.
  *
  * \param control Control for \c res_stasis.
- * \param file Base filename for the file to play.
+ * \param media Array of const char * media files to play.
+ * \param media_count The number of media files in \c media.
  * \param language Selects the file based on language.
  * \param target_id ID of the target bridge or channel.
  * \param target_type What the target type is
@@ -95,8 +98,8 @@ enum stasis_app_playback_target_type {
  * \return \c NULL on error.
  */
 struct stasis_app_playback *stasis_app_control_play_uri(
-	struct stasis_app_control *control, const char *file,
-	const char *language, const char *target_id,
+	struct stasis_app_control *control, const char **media,
+	size_t media_count, const char *language, const char *target_id,
 	enum stasis_app_playback_target_type target_type,
 	int skipms, long offsetms, const char *id);
 
@@ -128,6 +131,14 @@ const char *stasis_app_playback_get_id(
  */
 struct stasis_app_playback *stasis_app_playback_find_by_id(const char *id);
 
+/*!
+ * \brief Convert a playback to its JSON representation
+ *
+ * \param playback The playback object to convert to JSON
+ *
+ * \retval \c NULL on error
+ * \retval A JSON object on success
+ */
 struct ast_json *stasis_app_playback_to_json(
 	const struct stasis_app_playback *playback);
 
diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c
index 623d5b54162be5f3c637bf0b84c60c79112633b2..8f05db03561aacbc4cab17c38a479a6faf2fbf81 100644
--- a/res/ari/ari_model_validators.c
+++ b/res/ari/ari_model_validators.c
@@ -1744,6 +1744,15 @@ int ast_ari_validate_playback(struct ast_json *json)
 				res = 0;
 			}
 		} else
+		if (strcmp("next_media_uri", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ast_ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Playback field next_media_uri failed validation\n");
+				res = 0;
+			}
+		} else
 		if (strcmp("state", ast_json_object_iter_key(iter)) == 0) {
 			int prop_is_valid;
 			has_state = 1;
@@ -4741,6 +4750,9 @@ int ast_ari_validate_event(struct ast_json *json)
 	if (strcmp("PeerStatusChange", discriminator) == 0) {
 		return ast_ari_validate_peer_status_change(json);
 	} else
+	if (strcmp("PlaybackContinuing", discriminator) == 0) {
+		return ast_ari_validate_playback_continuing(json);
+	} else
 	if (strcmp("PlaybackFinished", discriminator) == 0) {
 		return ast_ari_validate_playback_finished(json);
 	} else
@@ -4930,6 +4942,9 @@ int ast_ari_validate_message(struct ast_json *json)
 	if (strcmp("PeerStatusChange", discriminator) == 0) {
 		return ast_ari_validate_peer_status_change(json);
 	} else
+	if (strcmp("PlaybackContinuing", discriminator) == 0) {
+		return ast_ari_validate_playback_continuing(json);
+	} else
 	if (strcmp("PlaybackFinished", discriminator) == 0) {
 		return ast_ari_validate_playback_finished(json);
 	} else
@@ -5216,6 +5231,85 @@ ari_validator ast_ari_validate_peer_status_change_fn(void)
 	return ast_ari_validate_peer_status_change;
 }
 
+int ast_ari_validate_playback_continuing(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_type = 0;
+	int has_application = 0;
+	int has_playback = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ast_ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI PlaybackContinuing field type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ast_ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI PlaybackContinuing field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ast_ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI PlaybackContinuing field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("playback", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_playback = 1;
+			prop_is_valid = ast_ari_validate_playback(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI PlaybackContinuing field playback failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI PlaybackContinuing has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI PlaybackContinuing missing required field type\n");
+		res = 0;
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI PlaybackContinuing missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_playback) {
+		ast_log(LOG_ERROR, "ARI PlaybackContinuing missing required field playback\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+ari_validator ast_ari_validate_playback_continuing_fn(void)
+{
+	return ast_ari_validate_playback_continuing;
+}
+
 int ast_ari_validate_playback_finished(struct ast_json *json)
 {
 	int res = 1;
diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h
index 0bcdb0fa211a13426913427c7a53532900ac290d..2634528bacec7349c874b781fc725382f3bd8cb0 100644
--- a/res/ari/ari_model_validators.h
+++ b/res/ari/ari_model_validators.h
@@ -1186,6 +1186,24 @@ int ast_ari_validate_peer_status_change(struct ast_json *json);
  */
 ari_validator ast_ari_validate_peer_status_change_fn(void);
 
+/*!
+ * \brief Validator for PlaybackContinuing.
+ *
+ * Event showing the continuation of a media playback operation from one media URI to the next in the list.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ast_ari_validate_playback_continuing(struct ast_json *json);
+
+/*!
+ * \brief Function pointer to ast_ari_validate_playback_continuing().
+ *
+ * See \ref ast_ari_model_validators.h for more details.
+ */
+ari_validator ast_ari_validate_playback_continuing_fn(void);
+
 /*!
  * \brief Validator for PlaybackFinished.
  *
@@ -1457,6 +1475,7 @@ ari_validator ast_ari_validate_application_fn(void);
  * - id: string (required)
  * - language: string
  * - media_uri: string (required)
+ * - next_media_uri: string
  * - state: string (required)
  * - target_uri: string (required)
  * DeviceState
@@ -1670,6 +1689,11 @@ ari_validator ast_ari_validate_application_fn(void);
  * - timestamp: Date
  * - endpoint: Endpoint (required)
  * - peer: Peer (required)
+ * PlaybackContinuing
+ * - type: string (required)
+ * - application: string (required)
+ * - timestamp: Date
+ * - playback: Playback (required)
  * PlaybackFinished
  * - type: string (required)
  * - application: string (required)
diff --git a/res/ari/resource_bridges.c b/res/ari/resource_bridges.c
index 57c1c273889a739d925e26ae4ec4b928ef8ee786..cec443dbab4db23d79f13938a5905725c1f1bf57 100644
--- a/res/ari/resource_bridges.c
+++ b/res/ari/resource_bridges.c
@@ -332,7 +332,8 @@ static struct ast_channel *prepare_bridge_media_channel(const char *type)
  * \brief Performs common setup for a bridge playback operation
  * with both new controls and when existing controls are  found.
  *
- * \param args_media media string split from arguments
+ * \param args_media medias to play
+ * \param args_media_count number of media items in \c media
  * \param args_lang language string split from arguments
  * \param args_offset_ms milliseconds offset split from arguments
  * \param args_playback_id string to use for playback split from
@@ -346,7 +347,8 @@ static struct ast_channel *prepare_bridge_media_channel(const char *type)
  * \retval -1 operation failed
  * \retval operation was successful
  */
-static int ari_bridges_play_helper(const char *args_media,
+static int ari_bridges_play_helper(const char **args_media,
+	size_t args_media_count,
 	const char *args_lang,
 	int args_offset_ms,
 	int args_skipms,
@@ -371,8 +373,8 @@ static int ari_bridges_play_helper(const char *args_media,
 
 	language = S_OR(args_lang, snapshot->language);
 
-	playback = stasis_app_control_play_uri(control, args_media, language,
-		bridge->uniqueid, STASIS_PLAYBACK_TARGET_BRIDGE, args_skipms,
+	playback = stasis_app_control_play_uri(control, args_media, args_media_count,
+		language, bridge->uniqueid, STASIS_PLAYBACK_TARGET_BRIDGE, args_skipms,
 		args_offset_ms, args_playback_id);
 
 	if (!playback) {
@@ -396,7 +398,8 @@ static int ari_bridges_play_helper(const char *args_media,
 	return 0;
 }
 
-static void ari_bridges_play_new(const char *args_media,
+static void ari_bridges_play_new(const char **args_media,
+	size_t args_media_count,
 	const char *args_lang,
 	int args_offset_ms,
 	int args_skipms,
@@ -449,9 +452,9 @@ static void ari_bridges_play_new(const char *args_media,
 	}
 
 	ao2_lock(control);
-	if (ari_bridges_play_helper(args_media, args_lang, args_offset_ms,
-			args_skipms, args_playback_id, response, bridge, control,
-			&json, &playback_url)) {
+	if (ari_bridges_play_helper(args_media, args_media_count, args_lang,
+			args_offset_ms, args_skipms, args_playback_id, response, bridge,
+			control, &json, &playback_url)) {
 		ao2_unlock(control);
 		return;
 	}
@@ -497,7 +500,8 @@ enum play_found_result {
  * \brief Performs common setup for a bridge playback operation
  * with both new controls and when existing controls are  found.
  *
- * \param args_media media string split from arguments
+ * \param args_media medias to play
+ * \param args_media_count number of media items in \c media
  * \param args_lang language string split from arguments
  * \param args_offset_ms milliseconds offset split from arguments
  * \param args_playback_id string to use for playback split from
@@ -511,7 +515,8 @@ enum play_found_result {
  * \retval PLAY_FOUND_CHANNEL_UNAVAILABLE The operation failed because
  * the channel requested to playback with is breaking down.
  */
-static enum play_found_result ari_bridges_play_found(const char *args_media,
+static enum play_found_result ari_bridges_play_found(const char **args_media,
+	size_t args_media_count,
 	const char *args_lang,
 	int args_offset_ms,
 	int args_skipms,
@@ -537,9 +542,9 @@ static enum play_found_result ari_bridges_play_found(const char *args_media,
 		return PLAY_FOUND_CHANNEL_UNAVAILABLE;
 	}
 
-	if (ari_bridges_play_helper(args_media, args_lang, args_offset_ms,
-			args_skipms, args_playback_id, response, bridge, control,
-			&json, &playback_url)) {
+	if (ari_bridges_play_helper(args_media, args_media_count,
+			args_lang, args_offset_ms, args_skipms, args_playback_id,
+			response, bridge, control, &json, &playback_url)) {
 		ao2_unlock(control);
 		return PLAY_FOUND_FAILURE;
 	}
@@ -551,7 +556,8 @@ static enum play_found_result ari_bridges_play_found(const char *args_media,
 
 static void ari_bridges_handle_play(
 	const char *args_bridge_id,
-	const char *args_media,
+	const char **args_media,
+	size_t args_media_count,
 	const char *args_lang,
 	int args_offset_ms,
 	int args_skipms,
@@ -574,15 +580,15 @@ static void ari_bridges_handle_play(
 		 * that will work or else there isn't a channel for this bridge anymore,
 		 * in which case we'll revert to ari_bridges_play_new.
 		 */
-		if (ari_bridges_play_found(args_media, args_lang, args_offset_ms,
-				args_skipms, args_playback_id, response,bridge,
+		if (ari_bridges_play_found(args_media, args_media_count, args_lang,
+				args_offset_ms, args_skipms, args_playback_id, response,bridge,
 				play_channel) == PLAY_FOUND_CHANNEL_UNAVAILABLE) {
 			continue;
 		}
 		return;
 	}
 
-	ari_bridges_play_new(args_media, args_lang, args_offset_ms,
+	ari_bridges_play_new(args_media, args_media_count, args_lang, args_offset_ms,
 		args_skipms, args_playback_id, response, bridge);
 }
 
@@ -593,6 +599,7 @@ void ast_ari_bridges_play(struct ast_variable *headers,
 {
 	ari_bridges_handle_play(args->bridge_id,
 	args->media,
+	args->media_count,
 	args->lang,
 	args->offsetms,
 	args->skipms,
@@ -606,6 +613,7 @@ void ast_ari_bridges_play_with_id(struct ast_variable *headers,
 {
 	ari_bridges_handle_play(args->bridge_id,
 	args->media,
+	args->media_count,
 	args->lang,
 	args->offsetms,
 	args->skipms,
diff --git a/res/ari/resource_bridges.h b/res/ari/resource_bridges.h
index 36ff6a017843aebed0d4c487d2d55eacbf7745fe..17a3b8365f0974631e7ea38ba372c71594c5ffd8 100644
--- a/res/ari/resource_bridges.h
+++ b/res/ari/resource_bridges.h
@@ -245,11 +245,15 @@ void ast_ari_bridges_stop_moh(struct ast_variable *headers, struct ast_ari_bridg
 struct ast_ari_bridges_play_args {
 	/*! Bridge's id */
 	const char *bridge_id;
-	/*! Media's URI to play. */
-	const char *media;
+	/*! Array of Media URIs to play. */
+	const char **media;
+	/*! Length of media array. */
+	size_t media_count;
+	/*! Parsing context for media. */
+	char *media_parse;
 	/*! For sounds, selects language for sound. */
 	const char *lang;
-	/*! Number of media to skip before playing. */
+	/*! Number of milliseconds to skip before playing. Only applies to the first URI if multiple media URIs are specified. */
 	int offsetms;
 	/*! Number of milliseconds to skip for forward/reverse operations. */
 	int skipms;
@@ -283,11 +287,15 @@ struct ast_ari_bridges_play_with_id_args {
 	const char *bridge_id;
 	/*! Playback ID. */
 	const char *playback_id;
-	/*! Media's URI to play. */
-	const char *media;
+	/*! Array of Media URIs to play. */
+	const char **media;
+	/*! Length of media array. */
+	size_t media_count;
+	/*! Parsing context for media. */
+	char *media_parse;
 	/*! For sounds, selects language for sound. */
 	const char *lang;
-	/*! Number of media to skip before playing. */
+	/*! Number of milliseconds to skip before playing. Only applies to the first URI if multiple media URIs are specified. */
 	int offsetms;
 	/*! Number of milliseconds to skip for forward/reverse operations. */
 	int skipms;
diff --git a/res/ari/resource_channels.c b/res/ari/resource_channels.c
index edf1a20e6cb99554af0bb9545960143b9d3ef5e6..b42581c8463511891aaf13ec43a03c88e23fd4cf 100644
--- a/res/ari/resource_channels.c
+++ b/res/ari/resource_channels.c
@@ -469,7 +469,8 @@ void ast_ari_channels_stop_silence(struct ast_variable *headers,
 
 static void ari_channels_handle_play(
 	const char *args_channel_id,
-	const char *args_media,
+	const char **args_media,
+	size_t args_media_count,
 	const char *args_lang,
 	int args_offsetms,
 	int args_skipms,
@@ -515,7 +516,7 @@ static void ari_channels_handle_play(
 
 	language = S_OR(args_lang, snapshot->language);
 
-	playback = stasis_app_control_play_uri(control, args_media, language,
+	playback = stasis_app_control_play_uri(control, args_media, args_media_count, language,
 		args_channel_id, STASIS_PLAYBACK_TARGET_CHANNEL, args_skipms, args_offsetms, args_playback_id);
 	if (!playback) {
 		ast_ari_response_error(
@@ -551,6 +552,7 @@ void ast_ari_channels_play(struct ast_variable *headers,
 	ari_channels_handle_play(
 		args->channel_id,
 		args->media,
+		args->media_count,
 		args->lang,
 		args->offsetms,
 		args->skipms,
@@ -565,6 +567,7 @@ void ast_ari_channels_play_with_id(struct ast_variable *headers,
 	ari_channels_handle_play(
 		args->channel_id,
 		args->media,
+		args->media_count,
 		args->lang,
 		args->offsetms,
 		args->skipms,
diff --git a/res/ari/resource_channels.h b/res/ari/resource_channels.h
index 89b466d00a07c2edd52ea9e922b4114d54da5752..c690d70c81367ca639f76d74a7b579c10cbab391 100644
--- a/res/ari/resource_channels.h
+++ b/res/ari/resource_channels.h
@@ -505,11 +505,15 @@ void ast_ari_channels_stop_silence(struct ast_variable *headers, struct ast_ari_
 struct ast_ari_channels_play_args {
 	/*! Channel's id */
 	const char *channel_id;
-	/*! Media's URI to play. */
-	const char *media;
+	/*! Array of Media URIs to play. */
+	const char **media;
+	/*! Length of media array. */
+	size_t media_count;
+	/*! Parsing context for media. */
+	char *media_parse;
 	/*! For sounds, selects language for sound. */
 	const char *lang;
-	/*! Number of media to skip before playing. */
+	/*! Number of milliseconds to skip before playing. Only applies to the first URI if multiple media URIs are specified. */
 	int offsetms;
 	/*! Number of milliseconds to skip for forward/reverse operations. */
 	int skipms;
@@ -543,11 +547,15 @@ struct ast_ari_channels_play_with_id_args {
 	const char *channel_id;
 	/*! Playback ID. */
 	const char *playback_id;
-	/*! Media's URI to play. */
-	const char *media;
+	/*! Array of Media URIs to play. */
+	const char **media;
+	/*! Length of media array. */
+	size_t media_count;
+	/*! Parsing context for media. */
+	char *media_parse;
 	/*! For sounds, selects language for sound. */
 	const char *lang;
-	/*! Number of media to skip before playing. */
+	/*! Number of milliseconds to skip before playing. Only applies to the first URI if multiple media URIs are specified. */
 	int offsetms;
 	/*! Number of milliseconds to skip for forward/reverse operations. */
 	int skipms;
diff --git a/res/res_ari_bridges.c b/res/res_ari_bridges.c
index 633dc94eb2a2c8023a83e938c4604a64a63e5456..119687999fde2c2e0b0f7caf7fbffaab0f118c86 100644
--- a/res/res_ari_bridges.c
+++ b/res/res_ari_bridges.c
@@ -935,7 +935,32 @@ int ast_ari_bridges_play_parse_body(
 	/* Parse query parameters out of it */
 	field = ast_json_object_get(body, "media");
 	if (field) {
-		args->media = ast_json_string_get(field);
+		/* If they were silly enough to both pass in a query param and a
+		 * JSON body, free up the query value.
+		 */
+		ast_free(args->media);
+		if (ast_json_typeof(field) == AST_JSON_ARRAY) {
+			/* Multiple param passed as array */
+			size_t i;
+			args->media_count = ast_json_array_size(field);
+			args->media = ast_malloc(sizeof(*args->media) * args->media_count);
+
+			if (!args->media) {
+				return -1;
+			}
+
+			for (i = 0; i < args->media_count; ++i) {
+				args->media[i] = ast_json_string_get(ast_json_array_get(field, i));
+			}
+		} else {
+			/* Multiple param passed as single value */
+			args->media_count = 1;
+			args->media = ast_malloc(sizeof(*args->media) * args->media_count);
+			if (!args->media) {
+				return -1;
+			}
+			args->media[0] = ast_json_string_get(field);
+		}
 	}
 	field = ast_json_object_get(body, "lang");
 	if (field) {
@@ -978,7 +1003,47 @@ static void ast_ari_bridges_play_cb(
 
 	for (i = get_params; i; i = i->next) {
 		if (strcmp(i->name, "media") == 0) {
-			args.media = (i->value);
+			/* Parse comma separated list */
+			char *vals[MAX_VALS];
+			size_t j;
+
+			args.media_parse = ast_strdup(i->value);
+			if (!args.media_parse) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			if (strlen(args.media_parse) == 0) {
+				/* ast_app_separate_args can't handle "" */
+				args.media_count = 1;
+				vals[0] = args.media_parse;
+			} else {
+				args.media_count = ast_app_separate_args(
+					args.media_parse, ',', vals,
+					ARRAY_LEN(vals));
+			}
+
+			if (args.media_count == 0) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			if (args.media_count >= MAX_VALS) {
+				ast_ari_response_error(response, 400,
+					"Bad Request",
+					"Too many values for media");
+				goto fin;
+			}
+
+			args.media = ast_malloc(sizeof(*args.media) * args.media_count);
+			if (!args.media) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			for (j = 0; j < args.media_count; ++j) {
+				args.media[j] = (vals[j]);
+			}
 		} else
 		if (strcmp(i->name, "lang") == 0) {
 			args.lang = (i->value);
@@ -1051,6 +1116,8 @@ static void ast_ari_bridges_play_cb(
 #endif /* AST_DEVMODE */
 
 fin: __attribute__((unused))
+	ast_free(args.media_parse);
+	ast_free(args.media);
 	return;
 }
 int ast_ari_bridges_play_with_id_parse_body(
@@ -1061,7 +1128,32 @@ int ast_ari_bridges_play_with_id_parse_body(
 	/* Parse query parameters out of it */
 	field = ast_json_object_get(body, "media");
 	if (field) {
-		args->media = ast_json_string_get(field);
+		/* If they were silly enough to both pass in a query param and a
+		 * JSON body, free up the query value.
+		 */
+		ast_free(args->media);
+		if (ast_json_typeof(field) == AST_JSON_ARRAY) {
+			/* Multiple param passed as array */
+			size_t i;
+			args->media_count = ast_json_array_size(field);
+			args->media = ast_malloc(sizeof(*args->media) * args->media_count);
+
+			if (!args->media) {
+				return -1;
+			}
+
+			for (i = 0; i < args->media_count; ++i) {
+				args->media[i] = ast_json_string_get(ast_json_array_get(field, i));
+			}
+		} else {
+			/* Multiple param passed as single value */
+			args->media_count = 1;
+			args->media = ast_malloc(sizeof(*args->media) * args->media_count);
+			if (!args->media) {
+				return -1;
+			}
+			args->media[0] = ast_json_string_get(field);
+		}
 	}
 	field = ast_json_object_get(body, "lang");
 	if (field) {
@@ -1100,7 +1192,47 @@ static void ast_ari_bridges_play_with_id_cb(
 
 	for (i = get_params; i; i = i->next) {
 		if (strcmp(i->name, "media") == 0) {
-			args.media = (i->value);
+			/* Parse comma separated list */
+			char *vals[MAX_VALS];
+			size_t j;
+
+			args.media_parse = ast_strdup(i->value);
+			if (!args.media_parse) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			if (strlen(args.media_parse) == 0) {
+				/* ast_app_separate_args can't handle "" */
+				args.media_count = 1;
+				vals[0] = args.media_parse;
+			} else {
+				args.media_count = ast_app_separate_args(
+					args.media_parse, ',', vals,
+					ARRAY_LEN(vals));
+			}
+
+			if (args.media_count == 0) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			if (args.media_count >= MAX_VALS) {
+				ast_ari_response_error(response, 400,
+					"Bad Request",
+					"Too many values for media");
+				goto fin;
+			}
+
+			args.media = ast_malloc(sizeof(*args.media) * args.media_count);
+			if (!args.media) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			for (j = 0; j < args.media_count; ++j) {
+				args.media[j] = (vals[j]);
+			}
 		} else
 		if (strcmp(i->name, "lang") == 0) {
 			args.lang = (i->value);
@@ -1173,6 +1305,8 @@ static void ast_ari_bridges_play_with_id_cb(
 #endif /* AST_DEVMODE */
 
 fin: __attribute__((unused))
+	ast_free(args.media_parse);
+	ast_free(args.media);
 	return;
 }
 int ast_ari_bridges_record_parse_body(
diff --git a/res/res_ari_channels.c b/res/res_ari_channels.c
index 1f0818170216340ead10808d38383d5fdbe88d25..951a5475bc030ec776d9b47557c55f5fb32d9f22 100644
--- a/res/res_ari_channels.c
+++ b/res/res_ari_channels.c
@@ -1842,7 +1842,32 @@ int ast_ari_channels_play_parse_body(
 	/* Parse query parameters out of it */
 	field = ast_json_object_get(body, "media");
 	if (field) {
-		args->media = ast_json_string_get(field);
+		/* If they were silly enough to both pass in a query param and a
+		 * JSON body, free up the query value.
+		 */
+		ast_free(args->media);
+		if (ast_json_typeof(field) == AST_JSON_ARRAY) {
+			/* Multiple param passed as array */
+			size_t i;
+			args->media_count = ast_json_array_size(field);
+			args->media = ast_malloc(sizeof(*args->media) * args->media_count);
+
+			if (!args->media) {
+				return -1;
+			}
+
+			for (i = 0; i < args->media_count; ++i) {
+				args->media[i] = ast_json_string_get(ast_json_array_get(field, i));
+			}
+		} else {
+			/* Multiple param passed as single value */
+			args->media_count = 1;
+			args->media = ast_malloc(sizeof(*args->media) * args->media_count);
+			if (!args->media) {
+				return -1;
+			}
+			args->media[0] = ast_json_string_get(field);
+		}
 	}
 	field = ast_json_object_get(body, "lang");
 	if (field) {
@@ -1885,7 +1910,47 @@ static void ast_ari_channels_play_cb(
 
 	for (i = get_params; i; i = i->next) {
 		if (strcmp(i->name, "media") == 0) {
-			args.media = (i->value);
+			/* Parse comma separated list */
+			char *vals[MAX_VALS];
+			size_t j;
+
+			args.media_parse = ast_strdup(i->value);
+			if (!args.media_parse) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			if (strlen(args.media_parse) == 0) {
+				/* ast_app_separate_args can't handle "" */
+				args.media_count = 1;
+				vals[0] = args.media_parse;
+			} else {
+				args.media_count = ast_app_separate_args(
+					args.media_parse, ',', vals,
+					ARRAY_LEN(vals));
+			}
+
+			if (args.media_count == 0) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			if (args.media_count >= MAX_VALS) {
+				ast_ari_response_error(response, 400,
+					"Bad Request",
+					"Too many values for media");
+				goto fin;
+			}
+
+			args.media = ast_malloc(sizeof(*args.media) * args.media_count);
+			if (!args.media) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			for (j = 0; j < args.media_count; ++j) {
+				args.media[j] = (vals[j]);
+			}
 		} else
 		if (strcmp(i->name, "lang") == 0) {
 			args.lang = (i->value);
@@ -1958,6 +2023,8 @@ static void ast_ari_channels_play_cb(
 #endif /* AST_DEVMODE */
 
 fin: __attribute__((unused))
+	ast_free(args.media_parse);
+	ast_free(args.media);
 	return;
 }
 int ast_ari_channels_play_with_id_parse_body(
@@ -1968,7 +2035,32 @@ int ast_ari_channels_play_with_id_parse_body(
 	/* Parse query parameters out of it */
 	field = ast_json_object_get(body, "media");
 	if (field) {
-		args->media = ast_json_string_get(field);
+		/* If they were silly enough to both pass in a query param and a
+		 * JSON body, free up the query value.
+		 */
+		ast_free(args->media);
+		if (ast_json_typeof(field) == AST_JSON_ARRAY) {
+			/* Multiple param passed as array */
+			size_t i;
+			args->media_count = ast_json_array_size(field);
+			args->media = ast_malloc(sizeof(*args->media) * args->media_count);
+
+			if (!args->media) {
+				return -1;
+			}
+
+			for (i = 0; i < args->media_count; ++i) {
+				args->media[i] = ast_json_string_get(ast_json_array_get(field, i));
+			}
+		} else {
+			/* Multiple param passed as single value */
+			args->media_count = 1;
+			args->media = ast_malloc(sizeof(*args->media) * args->media_count);
+			if (!args->media) {
+				return -1;
+			}
+			args->media[0] = ast_json_string_get(field);
+		}
 	}
 	field = ast_json_object_get(body, "lang");
 	if (field) {
@@ -2007,7 +2099,47 @@ static void ast_ari_channels_play_with_id_cb(
 
 	for (i = get_params; i; i = i->next) {
 		if (strcmp(i->name, "media") == 0) {
-			args.media = (i->value);
+			/* Parse comma separated list */
+			char *vals[MAX_VALS];
+			size_t j;
+
+			args.media_parse = ast_strdup(i->value);
+			if (!args.media_parse) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			if (strlen(args.media_parse) == 0) {
+				/* ast_app_separate_args can't handle "" */
+				args.media_count = 1;
+				vals[0] = args.media_parse;
+			} else {
+				args.media_count = ast_app_separate_args(
+					args.media_parse, ',', vals,
+					ARRAY_LEN(vals));
+			}
+
+			if (args.media_count == 0) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			if (args.media_count >= MAX_VALS) {
+				ast_ari_response_error(response, 400,
+					"Bad Request",
+					"Too many values for media");
+				goto fin;
+			}
+
+			args.media = ast_malloc(sizeof(*args.media) * args.media_count);
+			if (!args.media) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			for (j = 0; j < args.media_count; ++j) {
+				args.media[j] = (vals[j]);
+			}
 		} else
 		if (strcmp(i->name, "lang") == 0) {
 			args.lang = (i->value);
@@ -2080,6 +2212,8 @@ static void ast_ari_channels_play_with_id_cb(
 #endif /* AST_DEVMODE */
 
 fin: __attribute__((unused))
+	ast_free(args.media_parse);
+	ast_free(args.media);
 	return;
 }
 int ast_ari_channels_record_parse_body(
diff --git a/res/res_stasis_playback.c b/res/res_stasis_playback.c
index 97191c26dd1153d4fdb19227447b9c798681314a..a64ecffa706b52cf385ba10ca638fea459eb4494 100644
--- a/res/res_stasis_playback.c
+++ b/res/res_stasis_playback.c
@@ -70,10 +70,16 @@ static struct ao2_container *playbacks;
 struct stasis_app_playback {
 	AST_DECLARE_STRING_FIELDS(
 		AST_STRING_FIELD(id);	/*!< Playback unique id */
-		AST_STRING_FIELD(media);	/*!< Playback media uri */
+		AST_STRING_FIELD(media);	/*!< The current media playing */
 		AST_STRING_FIELD(language);	/*!< Preferred language */
 		AST_STRING_FIELD(target);       /*!< Playback device uri */
-		);
+	);
+	/*! The list of medias to play back */
+	AST_VECTOR(, char *) medias;
+
+	/*! The current index in \c medias we're playing */
+	size_t media_index;
+
 	/*! Control object for the channel we're playing back to */
 	struct stasis_app_control *control;
 	/*! Number of milliseconds to skip before playing */
@@ -99,6 +105,8 @@ static struct ast_json *playback_to_json(struct stasis_message *message,
 
 	if (!strcmp(state, "playing")) {
 		type = "PlaybackStarted";
+	} else if (!strcmp(state, "continuing")) {
+		type = "PlaybackContinuing";
 	} else if (!strcmp(state, "done")) {
 		type = "PlaybackFinished";
 	} else {
@@ -117,6 +125,14 @@ STASIS_MESSAGE_TYPE_DEFN(stasis_app_playback_snapshot_type,
 static void playback_dtor(void *obj)
 {
 	struct stasis_app_playback *playback = obj;
+	int i;
+
+	for (i = 0; i < AST_VECTOR_SIZE(&playback->medias); i++) {
+		char *media = AST_VECTOR_GET(&playback->medias, i);
+
+		ast_free(media);
+	}
+	AST_VECTOR_FREE(&playback->medias);
 
 	ao2_cleanup(playback->control);
 	ast_string_field_free_memory(playback);
@@ -137,6 +153,11 @@ static struct stasis_app_playback *playback_create(
 		return NULL;
 	}
 
+	if (AST_VECTOR_INIT(&playback->medias, 8)) {
+		ao2_ref(playback, -1);
+		return NULL;
+	}
+
 	if (!ast_strlen_zero(id)) {
 		ast_string_field_set(playback, id, id);
 	} else {
@@ -180,6 +201,8 @@ static const char *state_to_string(enum stasis_app_playback_state state)
 		return "playing";
 	case STASIS_PLAYBACK_STATE_PAUSED:
 		return "paused";
+	case STASIS_PLAYBACK_STATE_CONTINUING:
+		return "continuing";
 	case STASIS_PLAYBACK_STATE_STOPPED:
 	case STASIS_PLAYBACK_STATE_COMPLETE:
 	case STASIS_PLAYBACK_STATE_CANCELED:
@@ -241,7 +264,11 @@ static void playback_final_update(struct stasis_app_playback *playback,
 
 	playback->playedms = playedms;
 	if (res == 0) {
-		playback->state = STASIS_PLAYBACK_STATE_COMPLETE;
+		if (playback->media_index == AST_VECTOR_SIZE(&playback->medias) - 1) {
+			playback->state = STASIS_PLAYBACK_STATE_COMPLETE;
+		} else {
+			playback->state = STASIS_PLAYBACK_STATE_CONTINUING;
+		}
 	} else {
 		if (playback->state == STASIS_PLAYBACK_STATE_STOPPED) {
 			ast_log(LOG_NOTICE, "%s: Playback stopped for %s\n",
@@ -262,7 +289,7 @@ static void play_on_channel(struct stasis_app_playback *playback,
 	int res;
 	long offsetms;
 
-	/* Even though these local variables look fairly pointless, the avoid
+	/* Even though these local variables look fairly pointless, they avoid
 	 * having a bunch of NULL's passed directly into
 	 * ast_control_streamfile() */
 	const char *fwd = NULL;
@@ -273,73 +300,80 @@ static void play_on_channel(struct stasis_app_playback *playback,
 
 	ast_assert(playback != NULL);
 
-	offsetms = playback->offsetms;
-
-	res = playback_first_update(playback, ast_channel_uniqueid(chan));
-
-	if (res != 0) {
-		return;
-	}
-
 	if (ast_channel_state(chan) != AST_STATE_UP) {
 		ast_indicate(chan, AST_CONTROL_PROGRESS);
 	}
 
-	if (ast_begins_with(playback->media, SOUND_URI_SCHEME)) {
-		playback->controllable = 1;
-
-		/* Play sound */
-		res = ast_control_streamfile_lang(chan, playback->media + strlen(SOUND_URI_SCHEME),
-				fwd, rev, stop, pause, restart, playback->skipms, playback->language,
-				&offsetms);
-	} else if (ast_begins_with(playback->media, RECORDING_URI_SCHEME)) {
-		/* Play recording */
-		RAII_VAR(struct stasis_app_stored_recording *, recording, NULL,
-			ao2_cleanup);
-		const char *relname =
-			playback->media + strlen(RECORDING_URI_SCHEME);
-		recording = stasis_app_stored_recording_find_by_name(relname);
-
-		if (!recording) {
-			ast_log(LOG_ERROR, "Attempted to play recording '%s' on channel '%s' but recording does not exist",
-				relname, ast_channel_name(chan));
-			return;
-		}
+	offsetms = playback->offsetms;
 
-		playback->controllable = 1;
+	for (; playback->media_index < AST_VECTOR_SIZE(&playback->medias); playback->media_index++) {
 
-		res = ast_control_streamfile_lang(chan,
-			stasis_app_stored_recording_get_file(recording), fwd, rev, stop, pause,
-			restart, playback->skipms, playback->language, &offsetms);
-	} else if (ast_begins_with(playback->media, NUMBER_URI_SCHEME)) {
-		int number;
+		/* Set the current media to play */
+		ast_string_field_set(playback, media, AST_VECTOR_GET(&playback->medias, playback->media_index));
 
-		if (sscanf(playback->media + strlen(NUMBER_URI_SCHEME), "%30d", &number) != 1) {
-			ast_log(LOG_ERROR, "Attempted to play number '%s' on channel '%s' but number is invalid",
-				playback->media + strlen(NUMBER_URI_SCHEME), ast_channel_name(chan));
+		res = playback_first_update(playback, ast_channel_uniqueid(chan));
+		if (res != 0) {
 			return;
 		}
 
-		res = ast_say_number(chan, number, stop, playback->language, NULL);
-	} else if (ast_begins_with(playback->media, DIGITS_URI_SCHEME)) {
-		res = ast_say_digit_str(chan, playback->media + strlen(DIGITS_URI_SCHEME),
-			stop, playback->language);
-	} else if (ast_begins_with(playback->media, CHARACTERS_URI_SCHEME)) {
-		res = ast_say_character_str(chan, playback->media + strlen(CHARACTERS_URI_SCHEME),
-			stop, playback->language, AST_SAY_CASE_NONE);
-	} else if (ast_begins_with(playback->media, TONE_URI_SCHEME)) {
-		playback->controllable = 1;
-		res = ast_control_tone(chan, playback->media + strlen(TONE_URI_SCHEME));
-	} else {
-		/* Play URL */
-		ast_log(LOG_ERROR, "Attempted to play URI '%s' on channel '%s' but scheme is unsupported\n",
-			playback->media, ast_channel_name(chan));
-		return;
-	}
+		if (ast_begins_with(playback->media, SOUND_URI_SCHEME)) {
+			playback->controllable = 1;
+
+			/* Play sound */
+			res = ast_control_streamfile_lang(chan, playback->media + strlen(SOUND_URI_SCHEME),
+					fwd, rev, stop, pause, restart, playback->skipms, playback->language,
+					&offsetms);
+		} else if (ast_begins_with(playback->media, RECORDING_URI_SCHEME)) {
+			/* Play recording */
+			RAII_VAR(struct stasis_app_stored_recording *, recording, NULL,
+				ao2_cleanup);
+			const char *relname =
+				playback->media + strlen(RECORDING_URI_SCHEME);
+			recording = stasis_app_stored_recording_find_by_name(relname);
+
+			if (!recording) {
+				ast_log(LOG_ERROR, "Attempted to play recording '%s' on channel '%s' but recording does not exist",
+					relname, ast_channel_name(chan));
+				continue;
+			}
+
+			playback->controllable = 1;
+
+			res = ast_control_streamfile_lang(chan,
+				stasis_app_stored_recording_get_file(recording), fwd, rev, stop, pause,
+				restart, playback->skipms, playback->language, &offsetms);
+		} else if (ast_begins_with(playback->media, NUMBER_URI_SCHEME)) {
+			int number;
+
+			if (sscanf(playback->media + strlen(NUMBER_URI_SCHEME), "%30d", &number) != 1) {
+				ast_log(LOG_ERROR, "Attempted to play number '%s' on channel '%s' but number is invalid",
+					playback->media + strlen(NUMBER_URI_SCHEME), ast_channel_name(chan));
+				continue;
+			}
+
+			res = ast_say_number(chan, number, stop, playback->language, NULL);
+		} else if (ast_begins_with(playback->media, DIGITS_URI_SCHEME)) {
+			res = ast_say_digit_str(chan, playback->media + strlen(DIGITS_URI_SCHEME),
+				stop, playback->language);
+		} else if (ast_begins_with(playback->media, CHARACTERS_URI_SCHEME)) {
+			res = ast_say_character_str(chan, playback->media + strlen(CHARACTERS_URI_SCHEME),
+				stop, playback->language, AST_SAY_CASE_NONE);
+		} else if (ast_begins_with(playback->media, TONE_URI_SCHEME)) {
+			playback->controllable = 1;
+			res = ast_control_tone(chan, playback->media + strlen(TONE_URI_SCHEME));
+		} else {
+			/* Play URL */
+			ast_log(LOG_ERROR, "Attempted to play URI '%s' on channel '%s' but scheme is unsupported\n",
+				playback->media, ast_channel_name(chan));
+			continue;
+		}
 
-	playback_final_update(playback, offsetms, res,
-		ast_channel_uniqueid(chan));
+		playback_final_update(playback, offsetms, res,
+			ast_channel_uniqueid(chan));
 
+		/* Reset offset for any subsequent media */
+		offsetms = 0;
+	}
 	return;
 }
 
@@ -431,30 +465,45 @@ static void set_target_uri(
 }
 
 struct stasis_app_playback *stasis_app_control_play_uri(
-	struct stasis_app_control *control, const char *uri,
-	const char *language, const char *target_id,
+	struct stasis_app_control *control, const char **media,
+	size_t media_count, const char *language, const char *target_id,
 	enum stasis_app_playback_target_type target_type,
 	int skipms, long offsetms, const char *id)
 {
 	struct stasis_app_playback *playback;
+	size_t i;
 
-	if (skipms < 0 || offsetms < 0) {
+	if (skipms < 0 || offsetms < 0 || media_count == 0) {
 		return NULL;
 	}
 
-	ast_debug(3, "%s: Sending play(%s) command\n",
-		stasis_app_control_get_channel_id(control), uri);
-
 	playback = playback_create(control, id);
 	if (!playback) {
 		return NULL;
 	}
 
+	for (i = 0; i < media_count; i++) {
+		char *media_uri;
+
+		media_uri = ast_malloc(strlen(media[i]) + 1);
+	 	if (!media_uri) {
+			ao2_ref(playback, -1);
+			return NULL;
+		}
+
+		ast_debug(3, "%s: Sending play(%s) command\n",
+			stasis_app_control_get_channel_id(control), media[i]);
+
+	    /* safe */
+		strcpy(media_uri, media[i]);
+		AST_VECTOR_APPEND(&playback->medias, media_uri);
+	}
+
 	if (skipms == 0) {
 		skipms = PLAYBACK_DEFAULT_SKIPMS;
 	}
 
-	ast_string_field_set(playback, media, uri);
+	ast_string_field_set(playback, media, AST_VECTOR_GET(&playback->medias, 0));
 	ast_string_field_set(playback, language, language);
 	set_target_uri(playback, target_type, target_id);
 	playback->skipms = skipms;
@@ -497,12 +546,22 @@ struct ast_json *stasis_app_playback_to_json(
 		return NULL;
 	}
 
-	json = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}",
-		"id", playback->id,
-		"media_uri", playback->media,
-		"target_uri", playback->target,
-		"language", playback->language,
-		"state", state_to_string(playback->state));
+	if (playback->media_index == AST_VECTOR_SIZE(&playback->medias) - 1) {
+		json = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}",
+			"id", playback->id,
+			"media_uri", playback->media,
+			"target_uri", playback->target,
+			"language", playback->language,
+			"state", state_to_string(playback->state));
+	} else {
+		json = ast_json_pack("{s: s, s: s, s: s, s: s, s: s, s: s}",
+			"id", playback->id,
+			"media_uri", playback->media,
+			"next_media_uri", AST_VECTOR_GET(&playback->medias, playback->media_index + 1),
+			"target_uri", playback->target,
+			"language", playback->language,
+			"state", state_to_string(playback->state));
+	}
 
 	return ast_json_ref(json);
 }
@@ -615,6 +674,13 @@ playback_opreation_cb operations[STASIS_PLAYBACK_STATE_MAX][STASIS_PLAYBACK_MEDI
 	[STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_REVERSE] = playback_reverse,
 	[STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_FORWARD] = playback_forward,
 
+	[STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_STOP] = playback_stop,
+	[STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_RESTART] = playback_restart,
+	[STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_PAUSE] = playback_pause,
+	[STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_UNPAUSE] = playback_noop,
+	[STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_REVERSE] = playback_reverse,
+	[STASIS_PLAYBACK_STATE_CONTINUING][STASIS_PLAYBACK_FORWARD] = playback_forward,
+
 	[STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_STOP] = playback_stop,
 	[STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_PAUSE] = playback_noop,
 	[STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_UNPAUSE] = playback_unpause,
diff --git a/rest-api/api-docs/bridges.json b/rest-api/api-docs/bridges.json
index b608be6d6b5e13b52841cb55a2dfa830c7b88705..ab2c6c2d54532d047fd2422b518d21572b998f38 100644
--- a/rest-api/api-docs/bridges.json
+++ b/rest-api/api-docs/bridges.json
@@ -328,10 +328,10 @@
 						},
 						{
 							"name": "media",
-							"description": "Media's URI to play.",
+							"description": "Media URIs to play.",
 							"paramType": "query",
 							"required": true,
-							"allowMultiple": false,
+							"allowMultiple": true,
 							"dataType": "string"
 						},
 						{
@@ -344,7 +344,7 @@
 						},
 						{
 							"name": "offsetms",
-							"description": "Number of media to skip before playing.",
+							"description": "Number of milliseconds to skip before playing. Only applies to the first URI if multiple media URIs are specified.",
 							"paramType": "query",
 							"required": false,
 							"allowMultiple": false,
@@ -420,10 +420,10 @@
 						},
 						{
 							"name": "media",
-							"description": "Media's URI to play.",
+							"description": "Media URIs to play.",
 							"paramType": "query",
 							"required": true,
-							"allowMultiple": false,
+							"allowMultiple": true,
 							"dataType": "string"
 						},
 						{
@@ -436,7 +436,7 @@
 						},
 						{
 							"name": "offsetms",
-							"description": "Number of media to skip before playing.",
+							"description": "Number of milliseconds to skip before playing. Only applies to the first URI if multiple media URIs are specified.",
 							"paramType": "query",
 							"required": false,
 							"allowMultiple": false,
diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json
index 2389f7cb9812d956a8250cde87fb5f0519c8f1bc..aafd231a1f31a09a0eca8dbe53e2835707fcb9e4 100644
--- a/rest-api/api-docs/channels.json
+++ b/rest-api/api-docs/channels.json
@@ -973,10 +973,10 @@
 						},
 						{
 							"name": "media",
-							"description": "Media's URI to play.",
+							"description": "Media URIs to play.",
 							"paramType": "query",
 							"required": true,
-							"allowMultiple": false,
+							"allowMultiple": true,
 							"dataType": "string"
 						},
 						{
@@ -989,7 +989,7 @@
 						},
 						{
 							"name": "offsetms",
-							"description": "Number of media to skip before playing.",
+							"description": "Number of milliseconds to skip before playing. Only applies to the first URI if multiple media URIs are specified.",
 							"paramType": "query",
 							"required": false,
 							"allowMultiple": false,
@@ -1055,10 +1055,10 @@
 						},
 						{
 							"name": "media",
-							"description": "Media's URI to play.",
+							"description": "Media URIs to play.",
 							"paramType": "query",
 							"required": true,
-							"allowMultiple": false,
+							"allowMultiple": true,
 							"dataType": "string"
 						},
 						{
@@ -1071,7 +1071,7 @@
 						},
 						{
 							"name": "offsetms",
-							"description": "Number of media to skip before playing.",
+							"description": "Number of milliseconds to skip before playing. Only applies to the first URI if multiple media URIs are specified.",
 							"paramType": "query",
 							"required": false,
 							"allowMultiple": false,
diff --git a/rest-api/api-docs/events.json b/rest-api/api-docs/events.json
index dee7c2db934c6fe0fbd6afea41b9da517bc1377d..ca26161017a6b982a8bba8efd2376b5335d6f7c4 100644
--- a/rest-api/api-docs/events.json
+++ b/rest-api/api-docs/events.json
@@ -146,6 +146,7 @@
 			"subTypes": [
 				"DeviceStateChanged",
 				"PlaybackStarted",
+				"PlaybackContinuing",
 				"PlaybackFinished",
 				"RecordingStarted",
 				"RecordingFinished",
@@ -270,6 +271,17 @@
 				}
 			}
 		},
+		"PlaybackContinuing": {
+			"id": "PlaybackContinuing",
+			"description": "Event showing the continuation of a media playback operation from one media URI to the next in the list.",
+			"properties": {
+				"playback": {
+					"type": "Playback",
+					"description": "Playback control object",
+					"required": true
+				}
+			}
+		},
 		"PlaybackFinished": {
 			"id": "PlaybackFinished",
 			"description": "Event showing the completion of a media playback operation.",
diff --git a/rest-api/api-docs/playbacks.json b/rest-api/api-docs/playbacks.json
index 63df3f24b48530ce51fe8c9d4ee7740711fa8e8e..9f90035588cc7a49eb9a7efff8b43abe2e6258b6 100644
--- a/rest-api/api-docs/playbacks.json
+++ b/rest-api/api-docs/playbacks.json
@@ -124,9 +124,14 @@
 				},
 				"media_uri": {
 					"type": "string",
-					"description": "URI for the media to play back.",
+					"description": "The URI for the media currently being played back.",
 					"required": true
 				},
+				"next_media_uri": {
+					"type": "string",
+					"description": "If a list of URIs is being played, the next media URI to be played back.",
+					"required": false
+				},
 				"target_uri": {
 					"type": "string",
 					"description": "URI for the channel or bridge to play the media on",
@@ -145,7 +150,8 @@
 						"values": [
 							"queued",
 							"playing",
-							"complete"
+							"continuing",
+							"done"
 						]
 					}
 				}