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.
-
Service Initialization
-
In
app_ble_service_init(), callams_init()andclient_init()to complete the initialization of the AMS service and client.client_init(1); ams_init(1);
-
-
Message Handling Registration
-
In the
watch_handle_io_message()function, add handling for messages of typeIO_MSG_TYPE_AMSand callams_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
-
-
Service Discovery Process
-
In
app_handle_authen_state_evt(), add a call toams_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 theancs_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;
-
-
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;
-
-
-
Remote Command Sending
After subscribing, the AMS service can be used. For example, in the
play_pause_touch_cb()callback, you can callams_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);
-
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;
-
-
Parse Entity Update Content
-
After obtaining the Entity Update data, the first three bytes are
T_AMS_ENTITY_IDand the correspondingATTR_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
-
-
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.