[0.2.112] Add new messages handling for callback 31/205831/2
authorEunhye Choi <eunhae1.choi@samsung.com>
Thu, 9 May 2019 10:08:33 +0000 (19:08 +0900)
committerEunhye Choi <eunhae1.choi@samsung.com>
Thu, 9 May 2019 11:10:04 +0000 (20:10 +0900)
- Add new messages handling to invoke callback function about
  media stream buffer info, seek data and changed video stream info
  to ensure the configured callback operation normally even if _unprepare() is called.
- Set the exporting buffer callback path in prepare function
  to consider the disconnection after unprepare.

Change-Id: Icdbda6eee18a07b521c535abad1fd152b4c68e02

legacy/include/legacy_player_private.h
legacy/src/legacy_player.c
legacy/src/legacy_player_internal.c
packaging/mmsvc-player.spec

index 1bf77de..6f65e69 100644 (file)
@@ -18,6 +18,7 @@
 #define        __TIZEN_MEDIA_LEGACY_PLAYER_PRIVATE_H__
 #include <mm_types.h>
 #include "legacy_player.h"
+#include "mm_player.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -103,6 +104,7 @@ typedef struct {
        pthread_t prepare_async_thread;
        player_error_e error_code;
        int64_t last_play_position;
+       mmplayer_audio_extract_opt_e pcm_extract_opt;
 } legacy_player_t;
 
 int __player_convert_error_code(int code, char *func_name);
index 3142ed3..42c5408 100644 (file)
@@ -27,6 +27,7 @@
 #include <dlog.h>
 #include "muse_player.h"
 #include "legacy_player.h"
+#include "legacy_player_internal.h"
 #include "legacy_player_private.h"
 
 #define MIN_BUFFERING_TIME_MS 0   /* use platform default value */
@@ -265,6 +266,22 @@ static int __unset_callback(muse_player_event_e type, legacy_player_h player)
        return PLAYER_ERROR_NONE;
 }
 
+static int
+__set_buffer_export_callback(legacy_player_t *handle)
+{
+       int ret = MM_ERROR_NONE;
+
+       if (handle->user_cb[MUSE_PLAYER_EVENT_TYPE_MEDIA_PACKET_VIDEO_FRAME])
+               ret = mm_player_set_video_decoded_callback(handle->mm_handle,
+                               (mm_player_video_decoded_callback)handle->user_cb[MUSE_PLAYER_EVENT_TYPE_MEDIA_PACKET_VIDEO_FRAME], handle->user_data[MUSE_PLAYER_EVENT_TYPE_MEDIA_PACKET_VIDEO_FRAME]);
+
+       if (handle->user_cb[MUSE_PLAYER_EVENT_TYPE_MEDIA_PACKET_AUDIO_FRAME])
+               ret = mm_player_set_audio_decoded_callback(handle->mm_handle, handle->pcm_extract_opt,
+                               (mm_player_audio_decoded_callback)handle->user_cb[MUSE_PLAYER_EVENT_TYPE_MEDIA_PACKET_AUDIO_FRAME], handle->user_data[MUSE_PLAYER_EVENT_TYPE_MEDIA_PACKET_AUDIO_FRAME]);
+
+       return ret;
+}
+
 static player_state_e __convert_player_state(mmplayer_state_e state)
 {
        if (state == MM_PLAYER_STATE_NONE)
@@ -273,6 +290,70 @@ static player_state_e __convert_player_state(mmplayer_state_e state)
        return state + 1;
 }
 
+static void __buffer_status_callback(legacy_player_t *handle, MMMessageParamType *msg)
+{
+       muse_player_event_e event_type;
+       muse_player_event_e event_type_internal;
+       player_media_stream_buffer_status_e buffer_status;
+       unsigned long long buffer_bytes;
+
+       /* check public api callback, higher priority */
+       if (msg->buffer_status.stream_type == MM_PLAYER_STREAM_TYPE_AUDIO) {
+               event_type = MUSE_PLAYER_EVENT_TYPE_MEDIA_STREAM_AUDIO_BUFFER_STATUS;
+               event_type_internal = MUSE_PLAYER_EVENT_TYPE_MEDIA_STREAM_AUDIO_BUFFER_STATUS_WITH_INFO;
+       } else if (msg->buffer_status.stream_type == MM_PLAYER_STREAM_TYPE_VIDEO) {
+               event_type = MUSE_PLAYER_EVENT_TYPE_MEDIA_STREAM_VIDEO_BUFFER_STATUS;
+               event_type_internal = MUSE_PLAYER_EVENT_TYPE_MEDIA_STREAM_VIDEO_BUFFER_STATUS_WITH_INFO;
+       } else {
+               LOGE("invalid stream type %d", msg->buffer_status.stream_type);
+               return;
+       }
+
+       buffer_status = msg->buffer_status.status;
+       buffer_bytes = msg->buffer_status.bytes;
+
+       LEGACY_PLAYER_USER_CB_LOCK(handle, event_type);
+       if (handle->user_cb[event_type]) {
+               LOGD("event type %d, status %d", event_type, buffer_status);
+               ((player_media_stream_buffer_status_cb)handle->user_cb[event_type])(buffer_status, handle->user_data[event_type]);
+               LEGACY_PLAYER_USER_CB_UNLOCK(handle, event_type);
+               return;
+       }
+       LEGACY_PLAYER_USER_CB_UNLOCK(handle, event_type);
+
+       /* check internal api callback */
+       LEGACY_PLAYER_USER_CB_LOCK(handle, event_type_internal);
+       if (handle->user_cb[event_type_internal]) {
+               LOGD("event type %d, status %d, bytes %llu", event_type_internal, buffer_status, buffer_bytes);
+               ((player_media_stream_buffer_status_cb_ex)handle->user_cb[event_type_internal])(buffer_status, buffer_bytes, handle->user_data[event_type_internal]);
+       }
+       LEGACY_PLAYER_USER_CB_UNLOCK(handle, event_type_internal);
+}
+
+static void __video_stream_changed_callback(legacy_player_t *handle)
+{
+       int ret = MM_ERROR_NONE;
+       muse_player_event_e event_type = MUSE_PLAYER_EVENT_TYPE_VIDEO_STREAM_CHANGED;
+       int width = 0;
+       int height = 0;
+       int fps = 0;
+       int bit_rate = 0;
+
+       ret = mm_player_get_attribute(handle->mm_handle, NULL,
+                                                                         MM_PLAYER_VIDEO_WIDTH, &width,
+                                                                         MM_PLAYER_VIDEO_HEIGHT, &height,
+                                                                         MM_PLAYER_VIDEO_FPS, &fps,
+                                                                         MM_PLAYER_VIDEO_BITRATE, &bit_rate, (char *)NULL);
+
+       if (ret != MM_ERROR_NONE) {
+               LOGE("get attr is failed");
+               return;
+       }
+
+       ((player_video_stream_changed_cb)handle->user_cb[event_type])(width, height, fps, bit_rate, handle->user_data[event_type]);
+       return;
+}
+
 static int __msg_callback(int message, void *param, void *user_data)
 {
        legacy_player_t *handle = (legacy_player_t *)user_data;
@@ -410,17 +491,32 @@ static int __msg_callback(int message, void *param, void *user_data)
                LOGI("MM_MESSAGE_PLAY_POSITION (%"PRId64" ns)", msg->time.elapsed);
                handle->last_play_position = msg->time.elapsed;
                break;
-       case MM_MESSAGE_UNKNOWN:        /* 0x00 */
-       case MM_MESSAGE_WARNING:        /* 0x02 */
-       case MM_MESSAGE_CONNECTING:     /* 0x100 */
-       case MM_MESSAGE_CONNECTED:      /* 0x101 */
-       case MM_MESSAGE_BLUETOOTH_ON:   /* 0x106 */
-       case MM_MESSAGE_BLUETOOTH_OFF:  /* 0x107 */
-       case MM_MESSAGE_RTP_SENDER_REPORT:      /* 0x10a */
-       case MM_MESSAGE_RTP_RECEIVER_REPORT:    /* 0x10b */
-       case MM_MESSAGE_RTP_SESSION_STATUS:     /* 0x10c */
-       case MM_MESSAGE_SENDER_STATE:   /* 0x10d */
-       case MM_MESSAGE_RECEIVER_STATE: /* 0x10e */
+       case MM_MESSAGE_VIDEO_STREAM_CHANGED:
+               if (handle->user_cb[MUSE_PLAYER_EVENT_TYPE_VIDEO_STREAM_CHANGED])
+                       __video_stream_changed_callback(handle);
+               break;
+       case MM_MESSAGE_PUSH_BUFFER_STATUS:
+               __buffer_status_callback(handle, msg);
+               break;
+       case MM_MESSAGE_PUSH_BUFFER_SEEK_DATA:
+       {
+               muse_player_event_e event_type = MUSE_PLAYER_EVENT_TYPE_MEDIA_STREAM_VIDEO_SEEK;
+
+               if (msg->seek_data.stream_type == MM_PLAYER_STREAM_TYPE_AUDIO)
+                       event_type = MUSE_PLAYER_EVENT_TYPE_MEDIA_STREAM_AUDIO_SEEK;
+
+               if (handle->user_cb[event_type])
+                       ((player_media_stream_seek_cb)handle->user_cb[event_type])(msg->seek_data.offset, handle->user_data[event_type]);
+
+               break;
+       }
+       case MM_MESSAGE_UNKNOWN:
+       case MM_MESSAGE_WARNING:
+       case MM_MESSAGE_CONNECTING:
+       case MM_MESSAGE_CONNECTED:
+       case MM_MESSAGE_RTP_SENDER_REPORT:
+       case MM_MESSAGE_RTP_RECEIVER_REPORT:
+       case MM_MESSAGE_RTP_SESSION_STATUS:
        default:
                break;
        }
@@ -617,6 +713,12 @@ int legacy_player_prepare_async(legacy_player_h player, legacy_player_prepared_c
        if (ret != MM_ERROR_NONE)
                LOGW("Failed to set message callback function (0x%x)", ret);
 
+       ret = __set_buffer_export_callback(handle);
+       if (ret != MM_ERROR_NONE) {
+               LOGW("Failed to set buffer export callback function (0x%x)", ret);
+               return __player_convert_error_code(ret, (char *)__FUNCTION__);
+       }
+
        if (handle->display_type == PLAYER_DISPLAY_TYPE_NONE) {
                ret = mm_player_set_attribute(handle->mm_handle, NULL, "display_surface_type", MM_DISPLAY_SURFACE_NULL, (char *)NULL);
                if (ret != MM_ERROR_NONE)
@@ -692,6 +794,12 @@ int legacy_player_prepare(legacy_player_h player)
        if (ret != MM_ERROR_NONE)
                LOGW("Failed to set message callback function (0x%x)", ret);
 
+       ret = __set_buffer_export_callback(handle);
+       if (ret != MM_ERROR_NONE) {
+               LOGW("Failed to set buffer export callback function (0x%x)", ret);
+               return __player_convert_error_code(ret, (char *)__FUNCTION__);
+       }
+
        if (handle->display_type == PLAYER_DISPLAY_TYPE_NONE) {
                ret = mm_player_set_attribute(handle->mm_handle, NULL, "display_surface_type", MM_DISPLAY_SURFACE_NULL, (char *)NULL);
                if (ret != MM_ERROR_NONE)
@@ -1408,7 +1516,7 @@ int legacy_player_get_video_stream_info(legacy_player_h player, int *fps, int *b
                LOGE("PLAYER_ERROR_INVALID_STATE : current state - %d", handle->state);
                return PLAYER_ERROR_INVALID_STATE;
        }
-       ret = mm_player_get_attribute(handle->mm_handle, NULL, "content_video_fps", fps, "content_video_bitrate", bit_rate, (char *)NULL);
+       ret = mm_player_get_attribute(handle->mm_handle, NULL, MM_PLAYER_VIDEO_FPS, fps, MM_PLAYER_VIDEO_BITRATE, bit_rate, (char *)NULL);
        if (ret != MM_ERROR_NONE)
                return __player_convert_error_code(ret, (char *)__FUNCTION__);
 
@@ -1803,58 +1911,23 @@ int legacy_player_release_video_stream_bo(legacy_player_h player, void *bo)
 int legacy_player_set_media_packet_video_frame_decoded_cb(legacy_player_h player, legacy_player_media_packet_video_decoded_cb callback, void *user_data)
 {
        legacy_player_t *handle = (legacy_player_t *)player;
-       int ret = MM_ERROR_NONE;
        PLAYER_INSTANCE_CHECK(player);
        PLAYER_NULL_ARG_CHECK(callback);
        PLAYER_STATE_CHECK(handle, PLAYER_STATE_IDLE);
 
-       ret = mm_player_set_video_decoded_callback(handle->mm_handle, (mm_player_video_decoded_callback)callback, user_data);
-       if (ret != MM_ERROR_NONE)
-               return __player_convert_error_code(ret, (char *)__FUNCTION__);
+       handle->user_cb[MUSE_PLAYER_EVENT_TYPE_MEDIA_PACKET_VIDEO_FRAME] = callback;
+       handle->user_data[MUSE_PLAYER_EVENT_TYPE_MEDIA_PACKET_VIDEO_FRAME] = user_data;
 
        return PLAYER_ERROR_NONE;
 }
 
-static bool __video_stream_changed_callback(void *user_data)
-{
-       legacy_player_t *handle = (legacy_player_t *)user_data;
-       int ret = MM_ERROR_NONE;
-       muse_player_event_e event_type = MUSE_PLAYER_EVENT_TYPE_VIDEO_STREAM_CHANGED;
-
-       LOGE("event type %d", event_type);
-
-       if (handle->user_cb[event_type]) {
-               int width = 0, height = 0, fps = 0, bit_rate = 0;
-               ret = mm_player_get_attribute(handle->mm_handle, NULL,
-                                                                                 MM_PLAYER_VIDEO_WIDTH, &width,
-                                                                                 MM_PLAYER_VIDEO_HEIGHT, &height,
-                                                                                 "content_video_fps", &fps,
-                                                                                 "content_video_bitrate", &bit_rate, (char *)NULL);
-
-               if (ret != MM_ERROR_NONE) {
-                       LOGE("get attr is failed");
-                       return false;
-               }
-               ((player_video_stream_changed_cb)handle->user_cb[event_type])(width, height, fps, bit_rate, handle->user_data[event_type]);
-               return true;
-       }
-
-       LOGE("video stream changed cb was not set.");
-       return false;
-}
-
 int legacy_player_set_video_stream_changed_cb(legacy_player_h player, player_video_stream_changed_cb callback, void *user_data)
 {
        legacy_player_t *handle = (legacy_player_t *)player;
-       int ret = MM_ERROR_NONE;
        PLAYER_INSTANCE_CHECK(player);
        PLAYER_NULL_ARG_CHECK(callback);
        PLAYER_STATE_CHECK(handle, PLAYER_STATE_IDLE);
 
-       ret = mm_player_set_video_stream_changed_callback(handle->mm_handle, (mm_player_stream_changed_callback)__video_stream_changed_callback, (void *)handle);
-       if (ret != MM_ERROR_NONE)
-               return __player_convert_error_code(ret, (char *)__FUNCTION__);
-
        return __set_callback(MUSE_PLAYER_EVENT_TYPE_VIDEO_STREAM_CHANGED, player, callback, user_data);
 }
 
@@ -1867,58 +1940,9 @@ int legacy_player_unset_video_stream_changed_cb(legacy_player_h player)
        return PLAYER_ERROR_NONE;
 }
 
-static bool __media_stream_buffer_status_callback(player_stream_type_e type, player_media_stream_buffer_status_e status, unsigned long long bytes, void *user_data)
-{
-       legacy_player_t *handle = (legacy_player_t *)user_data;
-       muse_player_event_e event_type;
-
-       if (type == PLAYER_STREAM_TYPE_AUDIO)
-               event_type = MUSE_PLAYER_EVENT_TYPE_MEDIA_STREAM_AUDIO_BUFFER_STATUS;
-       else if (type == PLAYER_STREAM_TYPE_VIDEO)
-               event_type = MUSE_PLAYER_EVENT_TYPE_MEDIA_STREAM_VIDEO_BUFFER_STATUS;
-       else
-               return false;
-
-       LOGE("event type %d", event_type);
-
-       if (handle->user_cb[event_type]) {
-               ((player_media_stream_buffer_status_cb)handle->user_cb[event_type])(status, handle->user_data[event_type]);
-       } else {
-               LOGE("[type:%d] buffer status cb was not set.", type);
-               return false;
-       }
-
-       return true;
-}
-
-static bool __media_stream_seek_data_callback(player_stream_type_e type, unsigned long long offset, void *user_data)
-{
-       legacy_player_t *handle = (legacy_player_t *)user_data;
-       muse_player_event_e event_type;
-
-       if (type == PLAYER_STREAM_TYPE_AUDIO)
-               event_type = MUSE_PLAYER_EVENT_TYPE_MEDIA_STREAM_AUDIO_SEEK;
-       else if (type == PLAYER_STREAM_TYPE_VIDEO)
-               event_type = MUSE_PLAYER_EVENT_TYPE_MEDIA_STREAM_VIDEO_SEEK;
-       else
-               return false;
-
-       LOGE("event type %d", event_type);
-
-       if (handle->user_cb[event_type]) {
-               ((player_media_stream_seek_cb)handle->user_cb[event_type])(offset, handle->user_data[event_type]);
-       } else {
-               LOGE("[type:%d] seek cb was not set.", type);
-               return false;
-       }
-
-       return true;
-}
-
 int legacy_player_set_media_stream_buffer_status_cb(legacy_player_h player, player_stream_type_e type, player_media_stream_buffer_status_cb callback, void *user_data)
 {
        legacy_player_t *handle = (legacy_player_t *)player;
-       int ret = MM_ERROR_NONE;
        PLAYER_INSTANCE_CHECK(player);
        PLAYER_NULL_ARG_CHECK(callback);
        PLAYER_STATE_CHECK(handle, PLAYER_STATE_IDLE);
@@ -1929,11 +1953,6 @@ int legacy_player_set_media_stream_buffer_status_cb(legacy_player_h player, play
                return PLAYER_ERROR_INVALID_PARAMETER;
        }
 
-       ret = mm_player_set_media_stream_buffer_status_callback(handle->mm_handle, type, (mm_player_media_stream_buffer_status_callback)__media_stream_buffer_status_callback, (void *)handle);
-
-       if (ret != MM_ERROR_NONE)
-               return __player_convert_error_code(ret, (char *)__FUNCTION__);
-
        if (type == PLAYER_STREAM_TYPE_VIDEO)
                return __set_callback(MUSE_PLAYER_EVENT_TYPE_MEDIA_STREAM_VIDEO_BUFFER_STATUS, player, callback, user_data);
        else
@@ -1957,7 +1976,6 @@ int legacy_player_unset_media_stream_buffer_status_cb(legacy_player_h player, pl
 int legacy_player_set_media_stream_seek_cb(legacy_player_h player, player_stream_type_e type, player_media_stream_seek_cb callback, void *user_data)
 {
        legacy_player_t *handle = (legacy_player_t *)player;
-       int ret = MM_ERROR_NONE;
        PLAYER_INSTANCE_CHECK(player);
        PLAYER_NULL_ARG_CHECK(callback);
        PLAYER_STATE_CHECK(handle, PLAYER_STATE_IDLE);
@@ -1968,10 +1986,6 @@ int legacy_player_set_media_stream_seek_cb(legacy_player_h player, player_stream
                return PLAYER_ERROR_INVALID_PARAMETER;
        }
 
-       ret = mm_player_set_media_stream_seek_data_callback(handle->mm_handle, type, (mm_player_media_stream_seek_data_callback)__media_stream_seek_data_callback, (void *)handle);
-       if (ret != MM_ERROR_NONE)
-               return __player_convert_error_code(ret, (char *)__FUNCTION__);
-
        if (type == PLAYER_STREAM_TYPE_VIDEO)
                return __set_callback(MUSE_PLAYER_EVENT_TYPE_MEDIA_STREAM_VIDEO_SEEK, player, callback, user_data);
 
index 83de76d..5b25a7b 100644 (file)
@@ -34,7 +34,6 @@
 int legacy_player_set_pcm_extraction_mode(legacy_player_h player, bool sync, legacy_player_media_packet_audio_decoded_cb callback, void *user_data)
 {
        legacy_player_t *handle = (legacy_player_t *)player;
-       int ret = MM_ERROR_NONE;
        PLAYER_INSTANCE_CHECK(player);
        PLAYER_NULL_ARG_CHECK(callback);
        mmplayer_audio_extract_opt_e opt = MM_PLAYER_AUDIO_EXTRACT_DEINTERLEAVE; /* sync is true */
@@ -44,9 +43,9 @@ int legacy_player_set_pcm_extraction_mode(legacy_player_h player, bool sync, leg
        if (!sync)
                opt = MM_PLAYER_AUDIO_EXTRACT_NO_SYNC_WITH_CLOCK | MM_PLAYER_AUDIO_EXTRACT_DEINTERLEAVE;
 
-       ret = mm_player_set_audio_decoded_callback(handle->mm_handle, opt, (mm_player_audio_decoded_callback)callback, user_data);
-       if (ret != MM_ERROR_NONE)
-               return __player_convert_error_code(ret, (char *)__FUNCTION__);
+       handle->pcm_extract_opt = opt;
+       handle->user_cb[MUSE_PLAYER_EVENT_TYPE_MEDIA_PACKET_AUDIO_FRAME] = callback;
+       handle->user_data[MUSE_PLAYER_EVENT_TYPE_MEDIA_PACKET_AUDIO_FRAME] = user_data;
 
        return PLAYER_ERROR_NONE;
 }
@@ -109,45 +108,9 @@ int legacy_player_set_streaming_playback_rate(legacy_player_h player, float rate
        return ret;
 }
 
-static bool __media_stream_buffer_status_callback_ex(player_stream_type_e type, player_media_stream_buffer_status_e status, unsigned long long bytes, void *user_data)
-{
-       legacy_player_t *handle = (legacy_player_t *)user_data;
-       muse_player_event_e event_type;
-
-       if (!handle) {
-               LOGE("user_data param is null");
-               return false;
-       }
-
-       if (type == PLAYER_STREAM_TYPE_AUDIO)
-               event_type = MUSE_PLAYER_EVENT_TYPE_MEDIA_STREAM_AUDIO_BUFFER_STATUS_WITH_INFO;
-       else if (type == PLAYER_STREAM_TYPE_VIDEO)
-               event_type = MUSE_PLAYER_EVENT_TYPE_MEDIA_STREAM_VIDEO_BUFFER_STATUS_WITH_INFO;
-       else
-               return false;
-
-       LOGE("event type %d, status %d, bytes %llu", event_type, status, bytes);
-
-       LEGACY_PLAYER_USER_CB_LOCK(handle, event_type);
-
-       if (handle->user_cb[event_type]) {
-               ((player_media_stream_buffer_status_cb_ex)handle->user_cb[event_type])(status, bytes, handle->user_data[event_type]);
-       } else {
-               LOGE("[type:%d] buffer status cb was not set.", type);
-               LEGACY_PLAYER_USER_CB_UNLOCK(handle, event_type);
-               return false;
-       }
-
-       LEGACY_PLAYER_USER_CB_UNLOCK(handle, event_type);
-
-       return true;
-}
-
-
 int legacy_player_set_media_stream_buffer_status_cb_ex(legacy_player_h player, player_stream_type_e type, player_media_stream_buffer_status_cb_ex callback, void *user_data)
 {
        legacy_player_t *handle = (legacy_player_t *)player;
-       int ret = MM_ERROR_NONE;
        muse_player_event_e event_type;
        PLAYER_INSTANCE_CHECK(player);
        PLAYER_NULL_ARG_CHECK(callback);
@@ -167,13 +130,6 @@ int legacy_player_set_media_stream_buffer_status_cb_ex(legacy_player_h player, p
        else
                event_type = MUSE_PLAYER_EVENT_TYPE_MEDIA_STREAM_AUDIO_BUFFER_STATUS_WITH_INFO;
 
-
-       ret = mm_player_set_media_stream_buffer_status_callback(handle->mm_handle, type, (mm_player_media_stream_buffer_status_callback)__media_stream_buffer_status_callback_ex, (void *)handle);
-       if (ret != MM_ERROR_NONE) {
-               LEGACY_PLAYER_USER_CB_UNLOCK(handle, event_type);
-               return __player_convert_error_code(ret, (char *)__FUNCTION__);
-       }
-
        LEGACY_PLAYER_USER_CB_LOCK(handle, event_type);
 
        handle->user_cb[event_type] = callback;
index 8e3f77e..a24d0a3 100644 (file)
@@ -1,6 +1,6 @@
 Name:       mmsvc-player
 Summary:    A Media Player module for muse server
-Version:    0.2.111
+Version:    0.2.112
 Release:    0
 Group:      Multimedia/Libraries
 License:    Apache-2.0