diff --git a/CMakeLists.txt b/CMakeLists.txt index 56130453..9230d197 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,7 @@ set(obs-websocket_SOURCES src/WSRequestHandler_StudioMode.cpp src/WSRequestHandler_Transitions.cpp src/WSRequestHandler_Outputs.cpp + src/WSRequestHandler_MediaControl.cpp src/WSEvents.cpp src/Config.cpp src/Utils.cpp diff --git a/src/WSEvents.cpp b/src/WSEvents.cpp index 726ab883..c9b701b1 100644 --- a/src/WSEvents.cpp +++ b/src/WSEvents.cpp @@ -277,6 +277,15 @@ void WSEvents::connectSourceSignals(obs_source_t* source) { signal_handler_connect(sh, "filter_add", OnSourceFilterAdded, this); signal_handler_connect(sh, "filter_remove", OnSourceFilterRemoved, this); signal_handler_connect(sh, "reorder_filters", OnSourceFilterOrderChanged, this); + + signal_handler_connect(sh, "media_play", OnMediaPlaying, this); + signal_handler_connect(sh, "media_pause", OnMediaPaused, this); + signal_handler_connect(sh, "media_restart", OnMediaRestarted, this); + signal_handler_connect(sh, "media_stopped", OnMediaStopped, this); + signal_handler_connect(sh, "media_next", OnMediaNext, this); + signal_handler_connect(sh, "media_previous", OnMediaPrevious, this); + signal_handler_connect(sh, "media_started", OnMediaStarted, this); + signal_handler_connect(sh, "media_ended", OnMediaEnded, this); if (sourceType == OBS_SOURCE_TYPE_SCENE) { signal_handler_connect(sh, "reorder", OnSceneReordered, this); @@ -326,6 +335,15 @@ void WSEvents::disconnectSourceSignals(obs_source_t* source) { signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this); signal_handler_disconnect(sh, "transition_stop", OnTransitionEnd, this); signal_handler_disconnect(sh, "transition_video_stop", OnTransitionVideoEnd, this); + + signal_handler_disconnect(sh, "media_play", OnMediaPlaying, this); + signal_handler_disconnect(sh, "media_pause", OnMediaPaused, this); + signal_handler_disconnect(sh, "media_restart", OnMediaRestarted, this); + signal_handler_disconnect(sh, "media_stopped", OnMediaStopped, this); + signal_handler_disconnect(sh, "media_next", OnMediaNext, this); + signal_handler_disconnect(sh, "media_previous", OnMediaPrevious, this); + signal_handler_disconnect(sh, "media_started", OnMediaStarted, this); + signal_handler_disconnect(sh, "media_ended", OnMediaEnded, this); } void WSEvents::connectFilterSignals(obs_source_t* filter) { @@ -411,6 +429,16 @@ QString WSEvents::getRecordingTimecode() { return Utils::nsToTimestamp(getRecordingTime()); } +OBSDataAutoRelease getMediaSourceData(calldata_t* data) { + OBSDataAutoRelease fields = obs_data_create(); + OBSSource source = calldata_get_pointer(data, "source"); + + obs_data_set_string(fields, "sourceName", obs_source_get_name(source)); + obs_data_set_string(fields, "sourceTypeId", obs_source_get_id(source)); + + return fields; +} + /** * Indicates a scene change. * @@ -1388,6 +1416,174 @@ void WSEvents::OnSourceFilterOrderChanged(void* param, calldata_t* data) { self->broadcastUpdate("SourceFiltersReordered", fields); } +/** + * A media source has started playing. + * + * Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. + * + * @return {String} `sourceName` Source name + * @return {String} `sourceTypeId` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) + * + * @api events + * @name MediaPlaying + * @category media + * @since 4.9.0 + */ +void WSEvents::OnMediaPlaying(void* param, calldata_t* data) { + auto self = reinterpret_cast(param); + + OBSDataAutoRelease fields = getMediaSourceData(data); + + self->broadcastUpdate("MediaPlaying", fields); +} + +/** + * A media source has been paused. + * + * Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. + * + * @return {String} `sourceName` Source name + * @return {String} `sourceTypeId` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) + * + * @api events + * @name MediaPaused + * @category media + * @since 4.9.0 + */ +void WSEvents::OnMediaPaused(void* param, calldata_t* data) { + auto self = reinterpret_cast(param); + + OBSDataAutoRelease fields = getMediaSourceData(data); + + self->broadcastUpdate("MediaPaused", fields); +} + +/** + * A media source has been restarted. + * + * Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. + * + * @return {String} `sourceName` Source name + * @return {String} `sourceTypeId` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) + * + * @api events + * @name MediaRestarted + * @category media + * @since 4.9.0 + */ +void WSEvents::OnMediaRestarted(void* param, calldata_t* data) { + auto self = reinterpret_cast(param); + + OBSDataAutoRelease fields = getMediaSourceData(data); + + self->broadcastUpdate("MediaRestarted", fields); +} + +/** + * A media source has been stopped. + * + * Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. + * + * @return {String} `sourceName` Source name + * @return {String} `sourceTypeId` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) + * + * @api events + * @name MediaStopped + * @category media + * @since 4.9.0 + */ +void WSEvents::OnMediaStopped(void* param, calldata_t* data) { + auto self = reinterpret_cast(param); + + OBSDataAutoRelease fields = getMediaSourceData(data); + + self->broadcastUpdate("MediaStopped", fields); +} + +/** + * A media source has gone to the next item in the playlist. + * + * Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. + * + * @return {String} `sourceName` Source name + * @return {String} `sourceTypeId` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) + * + * @api events + * @name MediaNext + * @category media + * @since 4.9.0 + */ +void WSEvents::OnMediaNext(void* param, calldata_t* data) { + auto self = reinterpret_cast(param); + + OBSDataAutoRelease fields = getMediaSourceData(data); + + self->broadcastUpdate("MediaNext", fields); +} + +/** + * A media source has gone to the previous item in the playlist. + * + * Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. + * + * @return {String} `sourceName` Source name + * @return {String} `sourceTypeId` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) + * + * @api events + * @name MediaPrevious + * @category media + * @since 4.9.0 + */ +void WSEvents::OnMediaPrevious(void* param, calldata_t* data) { + auto self = reinterpret_cast(param); + + OBSDataAutoRelease fields = getMediaSourceData(data); + + self->broadcastUpdate("MediaPrevious", fields); +} + +/** + * A media source has been started. + * + * Note: These events are emitted by the OBS sources themselves. For example when the media file starts playing. The behavior depends on the type of media source being used. + * + * @return {String} `sourceName` Source name + * @return {String} `sourceTypeId` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) + * + * @api events + * @name MediaStarted + * @category media + * @since 4.9.0 + */ +void WSEvents::OnMediaStarted(void* param, calldata_t* data) { + auto self = reinterpret_cast(param); + + OBSDataAutoRelease fields = getMediaSourceData(data); + + self->broadcastUpdate("MediaStarted", fields); +} + +/** + * A media source has ended. + * + * Note: These events are emitted by the OBS sources themselves. For example when the media file ends. The behavior depends on the type of media source being used. + * + * @return {String} `sourceName` Source name + * @return {String} `sourceTypeId` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) + * + * @api events + * @name MediaEnded + * @category media + * @since 4.9.0 + */ +void WSEvents::OnMediaEnded(void* param, calldata_t* data) { + auto self = reinterpret_cast(param); + + OBSDataAutoRelease fields = getMediaSourceData(data); + + self->broadcastUpdate("MediaEnded", fields); +} + /** * Scene items have been reordered. * diff --git a/src/WSEvents.h b/src/WSEvents.h index a7c3d24b..dc814e83 100644 --- a/src/WSEvents.h +++ b/src/WSEvents.h @@ -135,6 +135,15 @@ private: static void OnSourceFilterRemoved(void* param, calldata_t* data); static void OnSourceFilterVisibilityChanged(void* param, calldata_t* data); static void OnSourceFilterOrderChanged(void* param, calldata_t* data); + + static void OnMediaPlaying(void* param, calldata_t* data); + static void OnMediaPaused(void* param, calldata_t* data); + static void OnMediaRestarted(void* param, calldata_t* data); + static void OnMediaStopped(void* param, calldata_t* data); + static void OnMediaNext(void* param, calldata_t* data); + static void OnMediaPrevious(void* param, calldata_t* data); + static void OnMediaStarted(void* param, calldata_t* data); + static void OnMediaEnded(void* param, calldata_t* data); static void OnSceneReordered(void* param, calldata_t* data); static void OnSceneItemAdd(void* param, calldata_t* data); diff --git a/src/WSRequestHandler.cpp b/src/WSRequestHandler.cpp index c3be3d58..8d2a257d 100644 --- a/src/WSRequestHandler.cpp +++ b/src/WSRequestHandler.cpp @@ -154,7 +154,19 @@ const QHash WSRequestHandler::messageMap { { "ListOutputs", &WSRequestHandler::ListOutputs }, { "GetOutputInfo", &WSRequestHandler::GetOutputInfo }, { "StartOutput", &WSRequestHandler::StartOutput }, - { "StopOutput", &WSRequestHandler::StopOutput } + { "StopOutput", &WSRequestHandler::StopOutput }, + + { "PlayPauseMedia", &WSRequestHandler::PlayPauseMedia }, + { "RestartMedia", &WSRequestHandler::RestartMedia }, + { "StopMedia", &WSRequestHandler::StopMedia }, + { "NextMedia", &WSRequestHandler::NextMedia }, + { "PreviousMedia", &WSRequestHandler::PreviousMedia }, + { "GetMediaDuration", &WSRequestHandler::GetMediaDuration }, + { "GetMediaTime", &WSRequestHandler::GetMediaTime }, + { "SetMediaTime", &WSRequestHandler::SetMediaTime }, + { "ScrubMedia", &WSRequestHandler::ScrubMedia }, + { "GetMediaState", &WSRequestHandler::GetMediaState }, + { "GetMediaSourcesList", &WSRequestHandler::GetMediaSourcesList } }; const QSet WSRequestHandler::authNotRequired { diff --git a/src/WSRequestHandler.h b/src/WSRequestHandler.h index 8fec7d69..9fc4a0f2 100644 --- a/src/WSRequestHandler.h +++ b/src/WSRequestHandler.h @@ -172,4 +172,16 @@ class WSRequestHandler { RpcResponse GetOutputInfo(const RpcRequest&); RpcResponse StartOutput(const RpcRequest&); RpcResponse StopOutput(const RpcRequest&); + + RpcResponse PlayPauseMedia(const RpcRequest&); + RpcResponse RestartMedia(const RpcRequest&); + RpcResponse StopMedia(const RpcRequest&); + RpcResponse NextMedia(const RpcRequest&); + RpcResponse PreviousMedia(const RpcRequest&); + RpcResponse GetMediaDuration(const RpcRequest&); + RpcResponse GetMediaTime(const RpcRequest&); + RpcResponse SetMediaTime(const RpcRequest&); + RpcResponse ScrubMedia(const RpcRequest&); + RpcResponse GetMediaState(const RpcRequest&); + RpcResponse GetMediaSourcesList(const RpcRequest&); }; diff --git a/src/WSRequestHandler_MediaControl.cpp b/src/WSRequestHandler_MediaControl.cpp new file mode 100644 index 00000000..bb01ff81 --- /dev/null +++ b/src/WSRequestHandler_MediaControl.cpp @@ -0,0 +1,396 @@ +#include "Utils.h" + +#include "WSRequestHandler.h" + +bool isMediaSource(const QString& sourceKind) +{ + return (sourceKind == "vlc_source" || sourceKind == "ffmpeg_source"); +} + +QString getSourceMediaState(obs_source_t *source) +{ + QString mediaState; + enum obs_media_state mstate = obs_source_media_get_state(source); + switch (mstate) { + case OBS_MEDIA_STATE_NONE: + mediaState = "none"; + break; + case OBS_MEDIA_STATE_PLAYING: + mediaState = "playing"; + break; + case OBS_MEDIA_STATE_OPENING: + mediaState = "opening"; + break; + case OBS_MEDIA_STATE_BUFFERING: + mediaState = "buffering"; + break; + case OBS_MEDIA_STATE_PAUSED: + mediaState = "paused"; + break; + case OBS_MEDIA_STATE_STOPPED: + mediaState = "stopped"; + break; + case OBS_MEDIA_STATE_ENDED: + mediaState = "ended"; + break; + case OBS_MEDIA_STATE_ERROR: + mediaState = "error"; + break; + default: + mediaState = "unknown"; + } + return mediaState; +} + +/** +* Pause or play a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) +* +* @param {String} `sourceName` Source name. +* @param {boolean} `playPause` Whether to pause or play the source. `false` for play, `true` for pause. +* +* @api requests +* @name PlayPauseMedia +* @category media control +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::PlayPauseMedia(const RpcRequest& request) { + if ((!request.hasField("sourceName")) || (!request.hasField("playPause"))) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + bool playPause = obs_data_get_bool(request.parameters(), "playPause"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + obs_source_media_play_pause(source, playPause); + return request.success(); +} + +/** +* Restart a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) +* +* @param {String} `sourceName` Source name. +* +* @api requests +* @name RestartMedia +* @category media control +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::RestartMedia(const RpcRequest& request) { + if (!request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + obs_source_media_restart(source); + return request.success(); +} + +/** +* Stop a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) +* +* @param {String} `sourceName` Source name. +* +* @api requests +* @name StopMedia +* @category media control +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::StopMedia(const RpcRequest& request) { + if (!request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + obs_source_media_stop(source); + return request.success(); +} + +/** +* Skip to the next media item in the playlist. Supports only vlc media source (as of OBS v25.0.8) +* +* @param {String} `sourceName` Source name. +* +* @api requests +* @name NextMedia +* @category media control +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::NextMedia(const RpcRequest& request) { + if (!request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + obs_source_media_next(source); + return request.success(); +} + +/** +* Go to the previous media item in the playlist. Supports only vlc media source (as of OBS v25.0.8) +* +* @param {String} `sourceName` Source name. +* +* @api requests +* @name PreviousMedia +* @category media control +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::PreviousMedia(const RpcRequest& request) { + if (!request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + obs_source_media_previous(source); + return request.success(); +} + +/** +* Get the length of media in milliseconds. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) +* Note: For some reason, for the first 5 or so seconds that the media is playing, the total duration can be off by upwards of 50ms. +* +* @param {String} `sourceName` Source name. +* +* @return {int} `mediaDuration` The total length of media in milliseconds.. +* +* @api requests +* @name GetMediaDuration +* @category media control +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::GetMediaDuration(const RpcRequest& request) { + if (!request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_int(response, "mediaDuration", obs_source_media_get_duration(source)); + return request.success(response); +} + +/** +* Get the current timestamp of media in milliseconds. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) +* +* @param {String} `sourceName` Source name. +* +* @return {int} `timestamp` The time in milliseconds since the start of the media. +* +* @api requests +* @name GetMediaTime +* @category media control +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::GetMediaTime(const RpcRequest& request) { + if (!request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_int(response, "timestamp", obs_source_media_get_time(source)); + return request.success(response); +} + +/** +* Set the timestamp of a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) +* +* @param {String} `sourceName` Source name. +* @param {int} `timestamp` Milliseconds to set the timestamp to. +* +* @api requests +* @name SetMediaTime +* @category media control +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::SetMediaTime(const RpcRequest& request) { + if (!request.hasField("sourceName") || !request.hasField("timestamp")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + int64_t timestamp = (int64_t)obs_data_get_int(request.parameters(), "timestamp"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + obs_source_media_set_time(source, timestamp); + return request.success(); +} + +/** +* Scrub media using a supplied offset. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) +* Note: Due to processing/network delays, this request is not perfect. The processing rate of this request has also not been tested. +* +* @param {String} `sourceName` Source name. +* @param {int} `timeOffset` Millisecond offset (positive or negative) to offset the current media position. +* +* @api requests +* @name ScrubMedia +* @category media control +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::ScrubMedia(const RpcRequest& request) { + if (!request.hasField("sourceName") || !request.hasField("timeOffset")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + int64_t timeOffset = (int64_t)obs_data_get_int(request.parameters(), "timeOffset"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + int64_t newTime = obs_source_media_get_time(source) + timeOffset; + if (newTime < 0) { + newTime = 0; + } + + obs_source_media_set_time(source, newTime); + return request.success(); +} + +/** +* Get the current playing state of a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) +* +* @param {String} `sourceName` Source name. +* +* @return {String} `mediaState` The media state of the provided source. States: `none`, `playing`, `opening`, `buffering`, `paused`, `stopped`, `ended`, `error`, `unknown` +* +* @api requests +* @name GetMediaState +* @category media control +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::GetMediaState(const RpcRequest& request) { + if (!request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_string(response, "mediaState", getSourceMediaState(source).toUtf8()); + + return request.success(response); +} + +/** +* List the media state of all media sources (vlc and media source) +* +* @return {Array} `mediaSources` Array of sources +* @return {String} `mediaSources.*.sourceName` Unique source name +* @return {String} `mediaSources.*.sourceKind` Unique source internal type (a.k.a `ffmpeg_source` or `vlc_source`) +* @return {String} `mediaSources.*.mediaState` The current state of media for that source. States: `none`, `playing`, `opening`, `buffering`, `paused`, `stopped`, `ended`, `error`, `unknown` +* +* @api requests +* @name GetMediaSourcesList +* @category sources +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::GetMediaSourcesList(const RpcRequest& request) +{ + OBSDataArrayAutoRelease sourcesArray = obs_data_array_create(); + + auto sourceEnumProc = [](void* privateData, obs_source_t* source) -> bool { + obs_data_array_t* sourcesArray = (obs_data_array_t*)privateData; + + QString sourceTypeId = obs_source_get_id(source); + if (isMediaSource(sourceTypeId)) { + OBSDataAutoRelease sourceData = obs_data_create(); + obs_data_set_string(sourceData, "sourceName", obs_source_get_name(source)); + obs_data_set_string(sourceData, "sourceKind", sourceTypeId.toUtf8()); + + QString mediaState = getSourceMediaState(source); + obs_data_set_string(sourceData, "mediaState", mediaState.toUtf8()); + + obs_data_array_push_back(sourcesArray, sourceData); + } + return true; + }; + obs_enum_sources(sourceEnumProc, sourcesArray); + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_array(response, "mediaSources", sourcesArray); + return request.success(response); +}