AMS User Guide

Introduction to AMS

AMS is a service in the iOS system that uses BLE connection to allow external devices (such as smartwatches) to remotely control the media player on iOS devices. Through AMS, external devices can not only control music playback on the iOS side (such as play, pause, skip tracks, etc.) but also obtain detailed information about the music being played by the current iOS media player.

Note

For detailed specifications, refer to: Apple Media Service (AMS) Specification

The AMS service includes three main characteristics:

  • Remote Command Used to send control commands to the media player, such as play, pause, previous track, next track, etc.

  • Entity Update Used to obtain the current status information of the media player, like playback status, volume, etc.

  • Entity Attribute Used to read or write detailed attribute information of various entities.

Entity Update mainly supports three types of entities:

  • Player Represents the currently active media application. Its main attributes include name, playback status, volume, etc.

  • Queue Represents the currently loaded play queue.

  • Track Represents the currently playing track. Its attributes mainly include the artist, title, and playtime.

SDK-based AMS Call Process

This section will introduce how to integrate and call AMS-related functions in the current SDK environment to achieve Bluetooth remote control of the iOS music player.

  1. Service Initialization

    • In app_ble_service_init(), call ams_init() and client_init() to complete the initialization of the AMS service and client.

      client_init(1);
      ams_init(1);
      
  2. Message Handling Registration

    • In the watch_handle_io_message() function, add handling for messages of type IO_MSG_TYPE_AMS and call ams_handle_msg() for distribution and processing.

      #if (F_APP_BLE_AMS_CLIENT_SUPPORT == 1)
         case IO_MSG_TYPE_AMS:
            {
                  ams_handle_msg(p_watch_msg);
            }
            break;
      #endif
      
  3. Service Discovery Process

    • In app_handle_authen_state_evt(), add a call to ams_start_discovery() to initiate the discovery of AMS services.

      #if (F_APP_BLE_AMS_CLIENT_SUPPORT == 1)
            ams_start_discovery(conn_id);
      #endif
      
    • If you need to use both AMS and ANCS services, please call ams_start_discovery() again in the ancs_client_cb() callback function.

      case ANCS_WRITE_NOTIFICATION_SOURCE_NOTIFY_ENABLE:
         APP_PRINT_INFO0("ANCS_WRITE_NOTIFICATION_SOURCE_NOTIFY_ENABLE");
         ams_start_discovery(conn_id);
         break;
      
  4. Characteristic Subscription

    • After successfully discovering services, subscribe to the Remote Command and Entity Update characteristics in the ams_client_cb() callback function.

      • Subscribe to Remote Command:

        case AMS_DISC_DONE:
           APP_PRINT_INFO0("ams_client_cb: discover procedure done.");
           ams_subscribe_remote_cmd(conn_id, true);
           break;
        
      • Subscribe to Entity Update:

        case AMS_WRITE_REMOTE_CMD_NOTIFY_ENABLE:
           APP_PRINT_INFO0("AMS_WRITE_REMOTE_CMD_NOTIFY_ENABLE");
           ams_subscribe_entity_upd(conn_id, true);
           break;
        
  5. Remote Command Sending

    • After subscribing, the AMS service can be used. For example, in the play_pause_touch_cb() callback, you can call ams_write_remote_cmd() to send a Remote Command (such as play/pause control).

    • The parameter for the Remote Command is the remote command id, which is used to control operations like pause and play on the music player.

      // Call the player interface here
      
      uint8_t conn_id = 0;
      
      T_AMS_MSG ams_msg_rem_cmd;
      ams_msg_rem_cmd.type = AMS_MSG_TYPE_REMOTE_CMD;
      ams_msg_rem_cmd.remote_cmd_id = (T_AMS_REMOTE_CMD_ID)REMOTE_CMD_ID_TOGGLE_PLAY_PAUSE;
      ams_send_msg(conn_id, &ams_msg_rem_cmd);
      ams_send_msg_to_app(conn_id);
      
  6. Request and Obtain Entity Update

    • Request Entity Update information from the AMS server. Entities include Player, Queue, and Track. The received data content can be viewed in the logs.

      case AMS_WRITE_ENTITY_UPD_NOTIFY_ENABLE:
         {
            APP_PRINT_INFO0("AMS_WRITE_ENTITY_UPD_NOTIFY_ENABLE");
      
            T_AMS_MSG ams_msg;
            static uint8_t cmd[5];
            cmd[0] = ENTITY_ID_TRACK;
            cmd[1] = TRACK_ATTR_ID_ARTIST;
            cmd[2] = TRACK_ATTR_ID_ALBUM;
            cmd[3] = TRACK_ATTR_ID_TITLE;
            cmd[4] = TRACK_ATTR_ID_DURATION;
            ams_msg.type = AMS_MSG_ENTITY_UPDATE_CMD;
            ams_msg.param = cmd;
            ams_msg.param_len = 5;
            ams_send_msg(conn_id, &ams_msg);
      
            T_AMS_MSG ams_msg1;
            static uint8_t cmd1[5];
            cmd1[0] = ENTITY_ID_QUEUE;
            cmd1[1] = QUEUE_ATTR_ID_INDEX;
            cmd1[2] = QUEUE_ATTR_ID_COUNT;
            cmd1[3] = QUEUE_ATTR_ID_SHUFFLE_MODE;
            cmd1[4] = QUEUE_ATTR_ID_REPEAT_MODE;
            ams_msg1.type = AMS_MSG_ENTITY_UPDATE_CMD;
            ams_msg1.param = cmd1;
            ams_msg1.param_len = 5;
            ams_send_msg(conn_id, &ams_msg1);
      
            T_AMS_MSG ams_msg2;
            static uint8_t cmd2[4];
            cmd2[0] = ENTITY_ID_PLAYER;
            cmd2[1] = PLAYER_ATTR_IDPLAYER_ATTR_ID_VOLUME_NAME;
            cmd2[2] = PLAYER_ATTR_ID_PLAYBACK_INFO;
            cmd2[3] = PLAYER_ATTR_ID_VOLUME;
            ams_msg2.type = AMS_MSG_ENTITY_UPDATE_CMD;
            ams_msg2.param = cmd2;
            ams_msg2.param_len = 4;
            ams_send_msg(conn_id, &ams_msg2);
         }
         break;
      
  7. Parse Entity Update Content

    • After obtaining the Entity Update data, the first three bytes are T_AMS_ENTITY_ID and the corresponding ATTR_ID. Starting from the fourth byte is the specific content.

      case AMS_NOTIFY_FROM_ENTITY_UPD:
         {
            APP_PRINT_INFO2("AMS_NOTIFY_FROM_ENTITY_UPD: data[%d]: %b",
                              p_cb_data->cb_content.notify_data.value_size,
                              TRACE_BINARY(p_cb_data->cb_content.notify_data.value_size,
                                          p_cb_data->cb_content.notify_data.p_value));
            uint32_t msg_num;
            os_msg_queue_peek(ams_queue_handle, &msg_num);
            if (msg_num)
            {
                  ams_send_msg_to_app(conn_id);
            }
            ams_parse_entity_update(p_cb_data->cb_content.notify_data.p_value,
                                    p_cb_data->cb_content.notify_data.value_size);
         }
         break;
      
      • The log output is as follows:

      AMS_NOTIFY_FROM_ENTITY_UPD: data[10]: 02 03 00 32 35 32 2E 30 30 30
      
    • The specific parsing process can be viewed inside the ams_parse_entity_update() function or through logging results.

      PLAYER related:
      
      // Get the name of the player
         case PLAYER_ATTR_IDPLAYER_ATTR_ID_VOLUME_NAME:
            {
               *(uint8_t *)(p_data + len) = '\0';
               APP_PRINT_INFO1("AMS PLAYER_ATTR_IDPLAYER_ATTR_ID_VOLUME_NAME: %b", TRACE_STRING((uint8_t *)(p_data + 3)));
            }
            break;
      // Get the playback status/playback rate/elapsed time of the current song
         case PLAYER_ATTR_ID_PLAYBACK_INFO:
            {
               APP_PRINT_INFO0("AMS PLAYER_ATTR_ID_PLAYBACK_INFO");
               *(uint8_t *)(p_data + len) = '\0';
               APP_PRINT_INFO1("AMS player_status, player_rate, elapsed_time = %s", TRACE_STRING((uint8_t *)(p_data + 3)));
      
            }
            break;
      // Get the volume
         case PLAYER_ATTR_ID_VOLUME:
            {
               *(uint8_t *)(p_data + len) = '\0';
               APP_PRINT_INFO1("AMS PLAYER_ATTR_ID_VOLUME = %s", TRACE_STRING((uint8_t *)(p_data + 3)));
      
            }
            break;
      
      QUEUE related:
      
      // Which song in the list is currently playing
         case QUEUE_ATTR_ID_INDEX:
            {
               *(uint8_t *)(p_data + len) = '\0';
               APP_PRINT_INFO1("AMS PLAYER_ATTR_ID_QUEUE_ATTR_ID_INDEX = %s", TRACE_STRING((uint8_t *)(p_data + 3)));
            }
            break;
      // Total number of songs in the list
         case QUEUE_ATTR_ID_COUNT:
            {
               *(uint8_t *)(p_data + len) = '\0';
               APP_PRINT_INFO1("AMS QUEUE_ATTR_ID_COUNT = %s", TRACE_STRING((uint8_t *)(p_data + 3)));
            }
            break;
      // Shuffle mode (non-random 0; single track random 1; list random 2)
         case QUEUE_ATTR_ID_SHUFFLE_MODE:
            {
               *(uint8_t *)(p_data + len) = '\0';
               APP_PRINT_INFO1("AMS QUEUE_ATTR_ID_SHUFFLE_MODE = %s", TRACE_STRING((uint8_t *)(p_data + 3)));
            }
            break;
      // Repeat mode (non-repeat 0; single track repeat 1; list loop 2)
         case QUEUE_ATTR_ID_REPEAT_MODE:
            {
               *(uint8_t *)(p_data + len) = '\0';
               APP_PRINT_INFO1("AMS QUEUE_ATTR_ID_REPEAT_MODE = %s", TRACE_STRING((uint8_t *)(p_data + 3)));
            }
            break;
      
      TRACK related:
      
      // Get the artist's name
         case TRACK_ATTR_ID_ARTIST:
            {
               *(uint8_t *)(p_data + len) = '\0';
            APP_PRINT_INFO1("AMS TRACK_ATTR_ID_ARTIST: %b", TRACE_STRING((uint8_t *)(p_data + 3)));
         }
         break;
      // Get the album name
         case TRACK_ATTR_ID_ALBUM:
            {
               *(uint8_t *)(p_data + len) = '\0';
            APP_PRINT_INFO1("AMS TRACK_ATTR_ID_ALBUM: %b", TRACE_STRING((uint8_t *)(p_data + 3)));
         }
            break;
      // Get the song name
         case TRACK_ATTR_ID_TITLE:
            {
               *(uint8_t *)(p_data + len) = '\0';
               APP_PRINT_INFO1("AMS TRACK_ATTR_ID_TITLE: %b", TRACE_STRING((uint8_t *)(p_data + 3)));
            }
            break;
      // Get the total duration of the current song
         case TRACK_ATTR_ID_DURATION:
            {
               *(uint8_t *)(p_data + len) = '\0';
               APP_PRINT_INFO1("AMS TRACK_ATTR_ID_DURATION = %s", TRACE_STRING((uint8_t *)(p_data + 3)));
            }
            break;
      
      • Output log is as follows:

      AMS PLAYER_ATTR_IDPLAYER_ATTR_ID_VOLUME_NAME: NetEase Cloud Music
      AMS player_status, player_rate, elapsed_time = 1,1.0,0.081
      // The volume range is 0-1, increasing/decreasing by 0.0625 with each press
      AMS PLAYER_ATTR_ID_VOLUME = 0.0625
      
  8. Actual function trigger

    • After configuring through the above process, establish a connection between the watch and the phone via BLE. Once connected successfully, open the music player on the phone, and by touching the pause/play button on the watch, it is possible to remotely control the music playback/pause on the phone.

    • Other control functions can be configured and integrated as needed.

Note

Please supplement the specific details of interface calls and data transmission routines according to the actual business process.