From be65baf476b63f90eedfe80667720885958ce4bb Mon Sep 17 00:00:00 2001
From: Milanka Ringwald <mila@ringwald.ch>
Date: Fri, 2 Feb 2018 15:06:32 +0100
Subject: [PATCH] avrcp browsing controller: fragmentation for get folder items

---
 src/btstack_defines.h                   |   5 +-
 src/btstack_event.h                     |  25 ++-
 src/classic/avrcp.h                     |  22 ++-
 src/classic/avrcp_browsing_controller.c | 201 +++++++++++++++---------
 src/classic/avrcp_browsing_controller.h |   1 -
 src/classic/avrcp_controller.c          |  37 +++++
 src/classic/avrcp_controller.h          |  11 ++
 7 files changed, 213 insertions(+), 89 deletions(-)

diff --git a/src/btstack_defines.h b/src/btstack_defines.h
index 1c78e4d22..5243e9225 100644
--- a/src/btstack_defines.h
+++ b/src/btstack_defines.h
@@ -1937,13 +1937,14 @@ typedef uint8_t sm_key_t[16];
 
 
 /**
- * @format 1211
+ * @format 12211
  * @param subevent_code
  * @param browsing_cid
+ * @param uid_counter
  * @param browsing_status
  * @param bluetooth_status
  */
-#define AVRCP_SUBEVENT_BROWSING_MEDIA_ITEM_DONE                                0x1E
+#define AVRCP_SUBEVENT_BROWSING_DONE                                          0x1E
 
 
 /**
diff --git a/src/btstack_event.h b/src/btstack_event.h
index cca3a0997..d4f3008f8 100644
--- a/src/btstack_event.h
+++ b/src/btstack_event.h
@@ -6216,31 +6216,40 @@ static inline uint16_t avrcp_subevent_browsing_connection_released_get_browsing_
 }
 
 /**
- * @brief Get field browsing_cid from event AVRCP_SUBEVENT_BROWSING_MEDIA_ITEM_DONE
+ * @brief Get field browsing_cid from event AVRCP_SUBEVENT_BROWSING_DONE
  * @param event packet
  * @return browsing_cid
  * @note: btstack_type 2
  */
-static inline uint16_t avrcp_subevent_browsing_media_item_done_get_browsing_cid(const uint8_t * event){
+static inline uint16_t avrcp_subevent_browsing_done_get_browsing_cid(const uint8_t * event){
     return little_endian_read_16(event, 3);
 }
 /**
- * @brief Get field browsing_status from event AVRCP_SUBEVENT_BROWSING_MEDIA_ITEM_DONE
+ * @brief Get field uid_counter from event AVRCP_SUBEVENT_BROWSING_DONE
+ * @param event packet
+ * @return uid_counter
+ * @note: btstack_type 2
+ */
+static inline uint16_t avrcp_subevent_browsing_done_get_uid_counter(const uint8_t * event){
+    return little_endian_read_16(event, 5);
+}
+/**
+ * @brief Get field browsing_status from event AVRCP_SUBEVENT_BROWSING_DONE
  * @param event packet
  * @return browsing_status
  * @note: btstack_type 1
  */
-static inline uint8_t avrcp_subevent_browsing_media_item_done_get_browsing_status(const uint8_t * event){
-    return event[5];
+static inline uint8_t avrcp_subevent_browsing_done_get_browsing_status(const uint8_t * event){
+    return event[7];
 }
 /**
- * @brief Get field bluetooth_status from event AVRCP_SUBEVENT_BROWSING_MEDIA_ITEM_DONE
+ * @brief Get field bluetooth_status from event AVRCP_SUBEVENT_BROWSING_DONE
  * @param event packet
  * @return bluetooth_status
  * @note: btstack_type 1
  */
-static inline uint8_t avrcp_subevent_browsing_media_item_done_get_bluetooth_status(const uint8_t * event){
-    return event[6];
+static inline uint8_t avrcp_subevent_browsing_done_get_bluetooth_status(const uint8_t * event){
+    return event[8];
 }
 
 /**
diff --git a/src/classic/avrcp.h b/src/classic/avrcp.h
index 37ec260e5..0e1d819d4 100644
--- a/src/classic/avrcp.h
+++ b/src/classic/avrcp.h
@@ -57,6 +57,7 @@ extern "C" {
 #define AVRCP_MEDIA_ATTR_COUNT 7
 #define AVRCP_MAX_ATTRIBUTTE_SIZE 100
 #define AVRCP_ATTRIBUTE_HEADER_LEN  8
+#define AVRCP_MAX_FOLDER_NAME_SIZE      20
 
 typedef enum {
     AVRCP_STATUS_INVALID_COMMAND = 0,           // sent if TG received a PDU that it did not understand.
@@ -129,6 +130,7 @@ typedef enum {
     AVRCP_PDU_ID_SET_BROWSED_PLAYER = 0x70,
     AVRCP_PDU_ID_GET_FOLDER_ITEMS = 0x71,
     AVRCP_PDU_ID_CHANGE_PATH = 0x72,
+    AVRCP_PDU_ID_PLAY_ITEM = 0x74,
     AVRCP_PDU_ID_UNDEFINED = 0xFF
 } avrcp_pdu_id_t;
 
@@ -256,6 +258,13 @@ typedef enum {
     AVCTP_W2_RECEIVE_RESPONSE
 } avctp_connection_state_t;
 
+typedef enum {
+    AVRCP_BROWSING_MEDIA_PLAYER_LIST = 0x00,
+    AVRCP_BROWSING_MEDIA_PLAYER_VIRTUAL_FILESYSTEM,
+    AVRCP_BROWSING_SEARCH,
+    AVRCP_BROWSING_NOW_PLAYING
+} avrcp_browsing_scope_t;
+
 typedef struct {
     uint16_t len;
     uint8_t  * value;
@@ -304,7 +313,7 @@ typedef struct {
 
     // get folder item
     uint8_t  get_folder_item;
-    uint8_t  scope;
+    avrcp_browsing_scope_t  scope;
     uint32_t start_item;
     uint32_t end_item;
     uint32_t attr_bitmap;
@@ -314,6 +323,17 @@ typedef struct {
     uint8_t  direction;
     uint16_t uid_counter;
     uint8_t  folder_uid[8];
+
+    // fragmentation
+    uint8_t fragmented;
+    avrcp_pdu_id_t fragmented_pdu_id;
+    uint8_t fragmented_browsing_status;
+    uint16_t fragmented_uid_counter;
+    uint16_t num_items;
+    uint8_t item_type;
+    uint16_t item_length;
+    uint16_t fragment_size;
+    uint8_t  fragment[100];
 } avrcp_browsing_connection_t;
 // BROWSING END
 
diff --git a/src/classic/avrcp_browsing_controller.c b/src/classic/avrcp_browsing_controller.c
index 47f347266..d9f435260 100644
--- a/src/classic/avrcp_browsing_controller.c
+++ b/src/classic/avrcp_browsing_controller.c
@@ -383,26 +383,33 @@ static void avrcp_browsing_controller_handle_can_send_now(avrcp_browsing_connect
                 avrcp_browsing_controller_send_change_path_cmd(connection->l2cap_browsing_cid, connection);
                 break;
             }
-            
+            break;
         default:
             return;
     }
 }
 
-static void avrcp_browsing_controller_emit_done(btstack_packet_handler_t callback, uint16_t browsing_cid, uint8_t browsing_status, uint8_t bluetooth_status){
+
+static void avrcp_browsing_controller_emit_done_with_uid_counter(btstack_packet_handler_t callback, uint16_t browsing_cid, uint16_t uid_counter, uint8_t browsing_status, uint8_t bluetooth_status){
     if (!callback) return;
-    uint8_t event[7];
+    uint8_t event[9];
     int pos = 0;
     event[pos++] = HCI_EVENT_AVRCP_META;
     event[pos++] = sizeof(event) - 2;
-    event[pos++] = AVRCP_SUBEVENT_BROWSING_MEDIA_ITEM_DONE;
+    event[pos++] = AVRCP_SUBEVENT_BROWSING_DONE;
     little_endian_store_16(event, pos, browsing_cid);
     pos += 2;
+    little_endian_store_16(event, pos, uid_counter);
+    pos += 2;
     event[pos++] = browsing_status;
     event[pos++] = bluetooth_status;
     (*callback)(HCI_EVENT_PACKET, 0, event, sizeof(event));
 }
 
+static void avrcp_browsing_controller_emit_done(btstack_packet_handler_t callback, uint16_t browsing_cid, uint8_t browsing_status, uint8_t bluetooth_status){
+    avrcp_browsing_controller_emit_done_with_uid_counter(callback, browsing_cid, 0, browsing_status, bluetooth_status);
+}
+
 static void avrcp_browsing_controller_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
     avrcp_browsing_connection_t * browsing_connection;
             
@@ -410,99 +417,136 @@ static void avrcp_browsing_controller_packet_handler(uint8_t packet_type, uint16
         case L2CAP_DATA_PACKET:{
             browsing_connection = get_avrcp_browsing_connection_for_l2cap_cid(channel, &avrcp_controller_context);
             if (!browsing_connection) break;
-            browsing_connection->state = AVCTP_CONNECTION_OPENED;
-
-            // printf_hexdump(packet, size);
             int pos = 0;
             uint8_t transport_header = packet[pos++];
             // Transaction label | Packet_type | C/R | IPID (1 == invalid profile identifier)
             // uint8_t transaction_label = transport_header >> 4;
             avrcp_packet_type_t avctp_packet_type = (transport_header & 0x0F) >> 2;
-            // uint8_t frame_type = (transport_header & 0x03) >> 1;
-            // uint8_t ipid = transport_header & 0x01;
-            pos += 2;
-            browsing_connection->num_packets = 1;
-            switch (avctp_packet_type){
-                case AVRCP_SINGLE_PACKET:
-                    break;
-                case AVRCP_START_PACKET:
+            
+            avrcp_pdu_id_t pdu_id;
+            uint8_t browsing_status;
+            uint16_t uid_counter;
+            if (!browsing_connection->fragmented){
+                browsing_connection->state = AVCTP_CONNECTION_OPENED;
+                // printf_hexdump(packet, size);
+                // uint8_t frame_type = (transport_header & 0x03) >> 1;
+                // uint8_t ipid = transport_header & 0x01;
+                pos += 2;
+                browsing_connection->num_packets = 1;
+                if (avctp_packet_type == AVRCP_START_PACKET){
                     browsing_connection->num_packets = packet[pos++];
-                    break;
-                case AVRCP_CONTINUE_PACKET:
-                case AVRCP_END_PACKET:
-                    // browsing_connection->num_packets = packet[pos++];
-                    break;
-            }
+                }
     
-            if (pos + 4 > size){
-                avrcp_browsing_controller_emit_done(avrcp_controller_context.browsing_avrcp_callback, channel, AVRCP_BROWSING_ERROR_CODE_INVALID_COMMAND, ERROR_CODE_SUCCESS);
-                return;  
+                if (pos + 4 > size){
+                    avrcp_browsing_controller_emit_done(avrcp_controller_context.browsing_avrcp_callback, channel, AVRCP_BROWSING_ERROR_CODE_INVALID_COMMAND, ERROR_CODE_SUCCESS);
+                    return;  
+                }
+                pdu_id = packet[pos++];
+                // uint16_t length = big_endian_read_16(packet, pos);
+                pos += 2;
+                browsing_status = packet[pos++]; 
+                if (browsing_status != AVRCP_BROWSING_ERROR_CODE_SUCCESS){
+                    avrcp_browsing_controller_emit_done(avrcp_controller_context.browsing_avrcp_callback, channel, browsing_status, ERROR_CODE_SUCCESS);
+                    return;        
+                }
+                uid_counter =  big_endian_read_16(packet, pos);
+                pos += 2;
+            } else {
+                pdu_id = browsing_connection->fragmented_pdu_id;
+                browsing_status = browsing_connection->fragmented_browsing_status;
+                uid_counter = browsing_connection->fragmented_uid_counter;
             }
-            
-            avrcp_pdu_id_t pdu_id = packet[pos++];
-            uint16_t length = big_endian_read_16(packet, pos);
-            pos += 2;
-            UNUSED(length);
-            // if (browsing_connection->num_packets > 1)
-            // if (browsing_connection->num_packets == 1 && (pos + length > size)){
-            //     printf("pos + length > size, %d, %d, %d \n", pos, length, size);
-            //     avrcp_browsing_controller_emit_done(avrcp_controller_context.browsing_avrcp_callback, channel, AVRCP_BROWSING_ERROR_CODE_INVALID_COMMAND, ERROR_CODE_SUCCESS);
-            //     return;  
-            // }
-            
-            uint8_t browsing_status = packet[pos++]; 
-            if (browsing_status != AVRCP_BROWSING_ERROR_CODE_SUCCESS){
-                printf("browsing_status %d\n", browsing_status);
-                avrcp_browsing_controller_emit_done(avrcp_controller_context.browsing_avrcp_callback, channel, browsing_status, ERROR_CODE_SUCCESS);
-                return;        
-            }
-            
+
             uint32_t i;
             switch(pdu_id){
                 case AVRCP_PDU_ID_CHANGE_PATH:
                     printf("AVRCP_PDU_ID_CHANGE_PATH \n");
                     break;
                 case AVRCP_PDU_ID_SET_ADDRESSED_PLAYER:
-                    printf("AVRCP_PDU_ID_CHANGE_PATH \n");
+                    printf("AVRCP_PDU_ID_SET_ADDRESSED_PLAYER \n");
                     break;
                 case AVRCP_PDU_ID_SET_BROWSED_PLAYER:{
-                    browsing_connection->browsed_player_uid_counter = big_endian_read_16(packet, pos);
-                    pos += 2;
-                    uint32_t num_items = big_endian_read_32(packet, pos);
+                    browsing_connection->browsed_player_uid_counter = uid_counter;
+                    // uint32_t num_items = big_endian_read_32(packet, pos);
                     pos += 4;
-                    printf("AVRCP_PDU_ID_SET_BROWSED_PLAYER uuid counter 0x0%2x, num items %d\n", browsing_connection->browsed_player_uid_counter, num_items);
-                    
-                    for (i = 0; i < num_items; i++){
-                        uint16_t browsable_item_length = 5 + big_endian_read_16(packet, pos+3);
-                        printf(" pos %d, len %d\n", pos, browsable_item_length);
-                        // reuse byte to put the new type AVRCP_BROWSING_MEDIA_ROOT_FOLDER
+                    // uint16_t charset = big_endian_read_16(packet, pos);
+                    pos += 2;
+                    uint8_t folder_depth = packet[pos++];
+
+                    for (i = 0; i < folder_depth; i++){
+                        uint16_t folder_name_length = big_endian_read_16(packet, pos);
+                        pos += 2;
+                        // reuse packet and add data type as a header
                         packet[pos-1] = AVRCP_BROWSING_MEDIA_ROOT_FOLDER;
-                        (*avrcp_controller_context.browsing_avrcp_callback)(AVRCP_BROWSING_DATA_PACKET, channel, packet+pos, browsable_item_length+1);
-                        pos += browsable_item_length;
+                        (*avrcp_controller_context.browsing_avrcp_callback)(AVRCP_BROWSING_DATA_PACKET, channel, packet+pos-1, folder_name_length+1);
+                        pos += folder_name_length;
+                    }
+                    if (avctp_packet_type == AVRCP_SINGLE_PACKET || avctp_packet_type == AVRCP_END_PACKET){
+                        avrcp_browsing_controller_emit_done_with_uid_counter(avrcp_controller_context.browsing_avrcp_callback, channel, browsing_connection->browsed_player_uid_counter, browsing_status, ERROR_CODE_SUCCESS);
                     }
                     break;
                 }
                 case AVRCP_PDU_ID_GET_FOLDER_ITEMS:{
-                    // uint16_t uid_counter = big_endian_read_16(packet, pos);
-                    pos += 2;
-                    uint16_t num_items = big_endian_read_16(packet, pos);
-                    pos += 2;
-                    for (i = 0; i < num_items; i++){
-                        uint16_t browsable_item_length = 3 + big_endian_read_16(packet, pos+1);
-                        (*avrcp_controller_context.browsing_avrcp_callback)(AVRCP_BROWSING_DATA_PACKET, channel, packet+pos, browsable_item_length);
-                        pos += browsable_item_length;
-                    }               
+                    printf("AVRCP_PDU_ID_GET_FOLDER_ITEMS \n");
+                    // send UID counter with done
+                    if (!browsing_connection->fragmented){
+                        browsing_connection->num_items = big_endian_read_16(packet, pos);
+                        pos += 2;
+                    } 
+                    while (pos < size){
+                        uint8_t header_size = 1;
+                        if (browsing_connection->fragment_size == 0){
+                            browsing_connection->item_type = packet[pos++];
+                            browsing_connection->item_length = big_endian_read_16(packet, pos);
+                            pos += 2;
+                            // reuse packet and add data type as a header
+                            packet[pos-1] = browsing_connection->item_type;
+                            
+                            if (browsing_connection->item_length + pos > size) {
+                                browsing_connection->fragment_size = size - pos;
+                                browsing_connection->fragment[0] = browsing_connection->item_type;
+                                memcpy(&browsing_connection->fragment[header_size], &packet[pos], browsing_connection->fragment_size);
+                                break;
+                            }
+                            (*avrcp_controller_context.browsing_avrcp_callback)(AVRCP_BROWSING_DATA_PACKET, channel, &packet[pos-header_size], browsing_connection->item_length+header_size);
+                            browsing_connection->num_items--;
+                            pos += browsing_connection->item_length;
+                        } else {
+                            uint16_t remaining_bytes_to_copy = browsing_connection->item_length - browsing_connection->fragment_size;
+                            if (remaining_bytes_to_copy + pos > size) {
+                                memcpy(&browsing_connection->fragment[browsing_connection->fragment_size+1], &packet[pos], size - pos);
+                                browsing_connection->fragment_size += size - pos;
+                                break;
+                            } 
+                            memcpy(&browsing_connection->fragment[browsing_connection->fragment_size+1], &packet[pos], remaining_bytes_to_copy);
+                            (*avrcp_controller_context.browsing_avrcp_callback)(AVRCP_BROWSING_DATA_PACKET, channel, browsing_connection->fragment, browsing_connection->item_length+1);
+                            browsing_connection->num_items--;
+                            browsing_connection->fragment_size = 0;
+                            pos += remaining_bytes_to_copy;
+                        } 
+                    }
                     break;
                 }
                 default:
                     break;
             }
+
             switch (avctp_packet_type){
                 case AVRCP_SINGLE_PACKET:
                 case AVRCP_END_PACKET:
-                    avrcp_browsing_controller_emit_done(avrcp_controller_context.browsing_avrcp_callback, channel, browsing_status, ERROR_CODE_SUCCESS);
+                    browsing_connection->state = AVCTP_CONNECTION_OPENED;
+                    browsing_connection->fragmented = 0;
+                    browsing_connection->num_items = 0;
+                    browsing_connection->fragment_size = 0;
+                    avrcp_browsing_controller_emit_done_with_uid_counter(avrcp_controller_context.browsing_avrcp_callback, channel, uid_counter, browsing_status, ERROR_CODE_SUCCESS);
                     break;
-                default:
+                case AVRCP_START_PACKET:
+                    browsing_connection->fragmented = 1;
+                    browsing_connection->fragmented_pdu_id = pdu_id;
+                    browsing_connection->fragmented_browsing_status = browsing_status;
+                    break;
+                case AVRCP_CONTINUE_PACKET:
+                    browsing_connection->fragmented = 1;
                     break;
             }
             break;
@@ -600,7 +644,7 @@ uint8_t avrcp_avrcp_browsing_decline_incoming_connection(uint16_t avrcp_browsing
  * @param attribute_count
  * @param attribute_list
  **/
-static uint8_t avrcp_browsing_controller_get_folder_items(uint16_t avrcp_browsing_cid, uint8_t scope, uint32_t start_item, uint32_t end_item, uint32_t attr_bitmap){
+static uint8_t avrcp_browsing_controller_get_folder_items(uint16_t avrcp_browsing_cid, avrcp_browsing_scope_t scope, uint32_t start_item, uint32_t end_item, uint32_t attr_bitmap){
     avrcp_connection_t * avrcp_connection = get_avrcp_connection_for_browsing_cid(avrcp_browsing_cid, &avrcp_controller_context);
     if (!avrcp_connection){
         log_error("avrcp_browsing_controller_disconnect: could not find a connection.");
@@ -620,21 +664,21 @@ static uint8_t avrcp_browsing_controller_get_folder_items(uint16_t avrcp_browsin
 }
 
 uint8_t avrcp_browsing_controller_get_media_players(uint16_t avrcp_browsing_cid, uint32_t start_item, uint32_t end_item, uint32_t attr_bitmap){
-    return avrcp_browsing_controller_get_folder_items(avrcp_browsing_cid, 0, start_item, end_item, attr_bitmap);
+    return avrcp_browsing_controller_get_folder_items(avrcp_browsing_cid, AVRCP_BROWSING_MEDIA_PLAYER_LIST, start_item, end_item, attr_bitmap);
 }
 
 uint8_t avrcp_browsing_controller_browse_file_system(uint16_t avrcp_browsing_cid, uint32_t start_item, uint32_t end_item, uint32_t attr_bitmap){
     // return avrcp_browsing_controller_get_folder_items(avrcp_browsing_cid, 1, 0, 0xFFFFFFFF, attr_bitmap);
-    return avrcp_browsing_controller_get_folder_items(avrcp_browsing_cid, 1, start_item, end_item, attr_bitmap);
+    return avrcp_browsing_controller_get_folder_items(avrcp_browsing_cid, AVRCP_BROWSING_MEDIA_PLAYER_VIRTUAL_FILESYSTEM, start_item, end_item, attr_bitmap);
 }
 
 uint8_t avrcp_browsing_controller_browse_media(uint16_t avrcp_browsing_cid, uint32_t start_item, uint32_t end_item, uint32_t attr_bitmap){
     // return avrcp_browsing_controller_get_folder_items(avrcp_browsing_cid, 2, 0, 0xFFFFFFFF, 0, NULL);
-    return avrcp_browsing_controller_get_folder_items(avrcp_browsing_cid, 2, start_item, end_item, attr_bitmap);
+    return avrcp_browsing_controller_get_folder_items(avrcp_browsing_cid, AVRCP_BROWSING_SEARCH, start_item, end_item, attr_bitmap);
 }
 
 uint8_t avrcp_browsing_controller_browse_now_playing_list(uint16_t avrcp_browsing_cid, uint32_t start_item, uint32_t end_item, uint32_t attr_bitmap){
-    return avrcp_browsing_controller_get_folder_items(avrcp_browsing_cid, 3, start_item, end_item, attr_bitmap);
+    return avrcp_browsing_controller_get_folder_items(avrcp_browsing_cid, AVRCP_BROWSING_NOW_PLAYING, start_item, end_item, attr_bitmap);
 }
 
 
@@ -690,7 +734,7 @@ uint8_t avrcp_browsing_controller_change_path(uint16_t avrcp_browsing_cid, uint8
     
     avrcp_browsing_connection_t * connection = avrcp_connection->browsing_connection;
     
-    if (!connection || connection->state != AVCTP_CONNECTION_OPENED || !folder_uid){
+    if (!connection || connection->state != AVCTP_CONNECTION_OPENED){
         log_error("avrcp_browsing_controller_change_path: connection in wrong state.");
         return ERROR_CODE_COMMAND_DISALLOWED;
     } 
@@ -699,19 +743,22 @@ uint8_t avrcp_browsing_controller_change_path(uint16_t avrcp_browsing_cid, uint8
         log_error("avrcp_browsing_controller_change_path: no browsed player set.");
         return ERROR_CODE_COMMAND_DISALLOWED;
     }
-
+    printf(" send change path\n");
     connection->change_path = 1;
     connection->direction = direction;
-
-    memcpy(connection->folder_uid, folder_uid, 8);
+    memset(connection->folder_uid, 0, 8);
+    if (folder_uid){
+        memcpy(connection->folder_uid, folder_uid, 8);
+    }
+    
     avrcp_request_can_send_now(avrcp_connection, connection->l2cap_browsing_cid);
     return ERROR_CODE_SUCCESS;
 }
 
 uint8_t avrcp_browsing_controller_go_up_one_level(uint16_t avrcp_browsing_cid){
-    return avrcp_browsing_controller_change_path(avrcp_browsing_cid, 0, 0);
+    return avrcp_browsing_controller_change_path(avrcp_browsing_cid, 0, NULL);
 }
 
 uint8_t avrcp_browsing_controller_go_down_one_level(uint16_t avrcp_browsing_cid, uint8_t * folder_uid){
     return avrcp_browsing_controller_change_path(avrcp_browsing_cid, 1, folder_uid);
-}
+}
\ No newline at end of file
diff --git a/src/classic/avrcp_browsing_controller.h b/src/classic/avrcp_browsing_controller.h
index 91561d89b..20275cd8a 100644
--- a/src/classic/avrcp_browsing_controller.h
+++ b/src/classic/avrcp_browsing_controller.h
@@ -202,7 +202,6 @@ uint8_t avrcp_browsing_controller_set_addressed_player(uint16_t avrcp_browsing_c
 uint8_t avrcp_browsing_controller_change_path(uint16_t avrcp_browsing_cid, uint8_t direction, uint8_t * folder_uid);
 uint8_t avrcp_browsing_controller_go_up_one_level(uint16_t avrcp_browsing_cid);
 uint8_t avrcp_browsing_controller_go_down_one_level(uint16_t avrcp_browsing_cid, uint8_t * folder_uid);
-
 /* API_END */
 
 #if defined __cplusplus
diff --git a/src/classic/avrcp_controller.c b/src/classic/avrcp_controller.c
index 31c4df338..cb32bcb65 100644
--- a/src/classic/avrcp_controller.c
+++ b/src/classic/avrcp_controller.c
@@ -1185,3 +1185,40 @@ uint8_t avrcp_controller_disconnect(uint16_t avrcp_cid){
     l2cap_disconnect(connection->l2cap_signaling_cid, 0);
     return ERROR_CODE_SUCCESS;
 }
+
+uint8_t avrcp_controller_play_item(uint16_t avrcp_cid, avrcp_browsing_scope_t scope, uint8_t * uid, uint16_t uid_counter){
+    avrcp_connection_t * connection = get_avrcp_connection_for_avrcp_cid(avrcp_cid, &avrcp_controller_context);
+    if (!connection){
+        log_error("avrcp_controller_play_item: could not find a connection.");
+        return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
+    }
+    if (connection->state != AVCTP_CONNECTION_OPENED) return ERROR_CODE_COMMAND_DISALLOWED;
+    connection->state = AVCTP_W2_SEND_COMMAND;
+
+    connection->transaction_label++;
+    connection->command_type = AVRCP_CTYPE_CONTROL;
+    connection->subunit_type = AVRCP_SUBUNIT_TYPE_PANEL;
+    connection->subunit_id = AVRCP_SUBUNIT_ID;
+    connection->command_opcode = AVRCP_CMD_OPCODE_VENDOR_DEPENDENT;
+    int pos = 0;
+    big_endian_store_24(connection->cmd_operands, pos, BT_SIG_COMPANY_ID);
+    pos += 3;
+    connection->cmd_operands[pos++] = AVRCP_PDU_ID_PLAY_ITEM; // PDU ID
+    // reserved
+    connection->cmd_operands[pos++] = 0;
+    // Parameter Length
+    big_endian_store_16(connection->cmd_operands, pos, 11);
+    pos += 2;
+    connection->cmd_operands[pos++]  = scope;
+    memset(&connection->cmd_operands[pos], 0, 8);
+    if (uid){
+        memcpy(&connection->cmd_operands[pos], uid, 8);
+    }
+    pos += 8;
+    big_endian_store_16(connection->cmd_operands, pos, uid_counter);
+    pos += 2;
+    connection->cmd_operands_length = pos;
+
+    avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
+    return ERROR_CODE_SUCCESS;
+}
diff --git a/src/classic/avrcp_controller.h b/src/classic/avrcp_controller.h
index 9b97fd4aa..8b68ee40b 100644
--- a/src/classic/avrcp_controller.h
+++ b/src/classic/avrcp_controller.h
@@ -284,6 +284,17 @@ uint8_t avrcp_controller_set_shuffle_mode(uint16_t avrcp_cid, avrcp_shuffle_mode
  * @returns status
  */
 uint8_t avrcp_controller_set_repeat_mode(uint16_t avrcp_cid, avrcp_repeat_mode_t mode);
+
+/**
+ * @brief The PlayItem command starts playing an item indicated by the UID. It is routed to the Addressed Player.
+ * @param avrcp_cid
+ * @param scope
+ * @param uid
+ * @param uid_counter
+ * @return status 
+ **/
+uint8_t avrcp_controller_play_item(uint16_t avrcp_cid, avrcp_browsing_scope_t scope, uint8_t * uid, uint16_t uid_counter);
+
 /* API_END */
 
 // Used by AVRCP controller and AVRCP browsing controller