Skip to content

Latest commit

 

History

History
879 lines (722 loc) · 32.6 KB

File metadata and controls

879 lines (722 loc) · 32.6 KB

How to use the Management API

Getting Started

This document contains the instructions to operate the Broadcast Development Kit (BDK) using Postman. This includes:

Prerequisites

Authorization

BDK Management API endpoints are secured. To be able to make API requests, it is necessary to include a valid bearer token in the requests' headers.

In this section we will describe how to generate an access token with the App registration used in the SPA (if you have already configured the Web UI for the Broadcast Development Kit for the implicit grant flow or by creating a new App registration for the client credential flow.

Client credential flow

Create an App registration

Create a new App registration to be able to configure it and thus be able to obtain the access token. Review the following Microsoft documentation that will explain how to do it, and consider the following settings:

  • Name: Meaningful name.
  • Supported account types: Accounts in this organizational directory only (your-organization only - Single tenant).

Once you've registered the App registration you must add a client secret, copy the value and save it together with the application client id in a secure place, we will need them for future steps.

API permissions

From the App registration created view, go to the API permissions option that is in the Manage menu, click the Add a permission button and then ensure the APIs my organization uses tab is selected. Search for the App Registration created for the Management API and click on the search result.

Search API permissions

Then inside App registration created, select AccessAll and click on Add permissions.

Request API permissions.png

NOTE: If your user does not have the necessary permissions to enable the add-on permission. You must ask a user with the required permission to enable it.

Add permissions

How to generate an access token with client credential grant type

Once the App registration is created, follow the steps below to generate the access token from Postman:

  1. Open Postman and create a new request.

  2. In the Authorization tab, select the type OAuth2.

  3. Then in the Configure New Token section enter the parameters that are highlighted.

    Configure New Token in Postman - Client Credentials
    Configure New Token in Postman with Client Credentials
    Placeholder Description
    Grant Type Select the Client Credentials option.
    accessTokenURL https://login.microsoftonline.com/{{tenantId}}/oauth2/v2.0/token
    tenantId Tenant Id of the subscription.
    clientId Client Id of the App registration created.
    clientSecret Client Secret of the App registration created.
    scope api://{{clientIdManagementAPI}}/.default
    clientIdManagementAPI Client Id of the App Registration of the ManagementApi.
  4. Next, to generate the token, click on the Get New Access Token button, a new window will be displayed, and then it will show the generated access token. To use this token, click on the button Use Token, this will use the access token in the created request.

    Management Access Tokens
    Management Access Tokens

Implicit grant flow

How to generate an access token with implicit grant type

NOTE: It is necessary to use the App registration of the SPA to generate the access token, please review the following documentation.

To generate the access token with implicit grant type, the following steps are suggested:

  1. Open Postman and create a new request.

  2. In the Authorization tab, select the type OAuth2.

  3. Then in the Configure New Token section enter the parameters that are highlighted.

    Configure New Token in Postman
    Configure New Token in Postman
    Placeholder Description
    Grant Type Select the Implicit option.
    callbackUrl https://{{spaRedirectUrl}}
    spaRedirectUrl Spa redirect URL configured.
    authUrl https://login.microsoftonline.com/{{tenantId}}/oauth2/v2.0/authorize
    tenantId Tenant Id of the subscription.
    clientId Client Id of the App registration created.
    scope api://{{clientIdManagementAPI}}/access_as_producer
    clientIdManagementAPI Client Id of the App Registration of the ManagementApi.
  4. Next, to generate the token, click on the Get New Access Token button. Login, and after a few seconds, a pop-up window Manage Access Tokens will be displayed, which shows the generated access token. To use this token, click on the button Use Token, this will use the access token in the created request.

    Management Access Tokens
    Management Access Tokens

Call Flow

The life cycle of a call follows these steps:

  1. First, we need to start the service (i.e. the VM) that host the bot using the Start Service endpoint.
  2. After requesting the provisioning of the service, we must poll the Get Service State endpoint several times until the service is Provisioned.
  3. Once the service is provisioned, to invite the bot into a meeting we must use the Initialize Call endpoint. The request will return a call with state equals to 0 (Establishing).
  4. Then, we must poll the Get Call Details endpoint until the state of the call is 1 (Established) and the Participants are available.
  5. After that, we are able to start and stop the streams we want to capture.
  6. Once the service is not longer needed in the call and all streams are stopped, we can disconnect it using the Delete Call endpoint.
  7. After the bot usage is done, it can be terminated using the Stop Service endpoint.
  8. To make sure that the VM has been stopped, we must poll the Get Service State endpoint several times until the service is Deprovisioned.

Swagger

The REST endpoints exposed by the Management API are documented by using OpenAPI specification. To access Swagger UI go to https://{{MANAGEMENT_API_URL}}/Swagger

Managament API Swagger UI

Postman

Optionally, you can import the OpenAPI specification in Postman by importing the following JSON https://{{MANAGEMENT_API_URL}}/swagger/v1/swagger.json

Operations

The following instructions are the steps necessary to start the service, join the bot into the meeting, start an extraction or injection, disconnect the call and stop the service.

It is necessary to have a scheduled meeting in Microsoft Teams to join the bot in it and also to have already configured the access token in Postman in order to follow the instructions.

Start the service

  1. Get service state: We need to verify the state of the service before join the bot into the meeting, check if the service is available or not. If it is not we need to start it.

    Method: GET
    Endpoint: https://{{appServiceUrl}}/api/service/{{serviceId}}/state

    Placeholder Description
    appServiceUrl AppService Url
    serviceId Service Id (the default id is 00000000-0000-0000-0000-000000000000)

    Response:

    {
        "id": "serviceId",
        "resource": {
            "id": "serviceId",
            "callId": null,
            "name": "serviceName",
            "state": 3,
            "createdAt": "2021-08-02T11:26:13.7519720+00:00",
            "infrastructure": {
                "virtualMachineName": "test-vm",
                "resourceGroup": "test-bot-vm",
                "subscriptionId": "a874b409-c14a-448b-935d-296ac546e568",
                "id": "/subscriptions/a874b409-c14a-448b-935d-296ac546e568/resourcegroups/test-bot-vm/providers/microsoft.compute/virtualmachines/test-vm",
                "powerState": "PowerState/deallocated",
                "ipAddress": "21.120.170.210",
                "dns": "domain.co",
                "provisioningDetails": {
                    "state": {
                        "id": 3,
                        "name": "Deprovisioned"
                    },
                    "message": "Service serviceName deprovisioned."
                }
            }
        }

    If the provisioningDetails is deprovisioned we need to start the service by calling the start endpoint.

  2. Start Service:

    Method: POST
    Endpoint: https://{{appServiceUrl}}/api/service/{{serviceId}}/start

    Placeholder Description
    appServiceUrl AppService URL
    serviceId Service Id (the default id is 00000000-0000-0000-0000-000000000000)

    Response:

    {
        "id": "serviceId",
        "resource": {
            "id": "serviceId",
            "callId": null,
            "name": "serviceName",
            "state": 0,
            "createdAt": "2021-08-02T11:26:13.7519720+00:00",
            "infrastructure": {
                "virtualMachineName": "test-vm",
                "resourceGroup": "test-bot-vm",
                "subscriptionId": "a874b409-c14a-448b-935d-296ac546e568",
                "id": "/subscriptions/a874b409-c14a-448b-935d-296ac546e568/resourcegroups/test-bot-vm/providers/microsoft.compute/virtualmachines/test-vm",
                "powerState": "PowerState/deallocated",
                "ipAddress": "21.120.170.210",
                "dns": "domain.co",
                "provisioningDetails": {
                    "state": {
                        "id": 0,
                        "name": "Provisioning"
                    },
                    "message": "Provisioning service serviceName."
                }
            }
        }
    }

    After calling the start endpoint the service will start provisioning, it is necessary to check the status until the provisioningDetails changes to Provisioned

Join the bot into a meeting

  1. Initialize Call: To join the bot into the meeting we will call the initialize-call endpoint with the Teams meeting URL. The service needs to be Provisioned to join the bot to the call.

    Method: POST
    Endpoint: https://{{appServiceUrl}}/api/call/initialize-call

    Placeholder Description
    appServiceUrl AppService Url

    Headers: Verify the Content-type key has as value application/json
    Body: raw

    Complete the body in Postman with the following:

    { 
        "MeetingUrl": "{{teamsMeetingUrl}}" 
    } 
    Placeholder Description
    teamsMeetingUrl Teams meeting URL

    To obtain the Teams meeting URL, join a Microsoft Teams meeting, press the Show participants button, then press Share invite and copy the link.

    Steps to get the Teams meeting URL
    Steps to get the Teams meeting URL

    Response:

    {
        "id": "callId",
        "resource": {
            "id": "callId",
            "meetingUrl": "teamsMeetingUrl",
            "meetingId": "meetingId",
            "state": 0,
            "createdAt": "2021-08-03T16:17:57.4086259+00:00",
            "startedAt": "0001-01-01T00:00:00",
            "endedAt": "0001-01-01T00:00:00",
            "meetingType": 0,
            "botFqdn": "domain.co",
            "botIp": null,
            "defaultPassphrase": null,
            "defaultLatency": 0,
            "graphId": null,
            "streams": [],
            "injectionStream": null,
            "publicContext": {},
            "privateContext": {
                "streamKey": "hJw5wZxAETkyExVhqdtw"
            }
        }
    }

    The returned status is equals 0 (Establishing) means that the bot is joining the call. To be able to start a stream, the call state has to be in 1 (Established). You can use the Get Call Details operation to verify the status of the call. The returned Id value must to be copied to be used in the following steps.

  2. Get call details: The call details endpoint will retrieve call and participants information. If the state of the call is 1 (Established), it means that the bot is joined in the call.

    Method: GET
    Endpoint: https://{{appServiceUrl}}/api/call/{{callId}}

    Placeholder Description
    callId Call Id obtained as response in the previous step

    Response:

    {
        "id": "callId",
        "meetingUrl": "",
        "meetingId": "meetingId",
        "state": 1,
        "createdAt": "2021-08-03T16:17:57.4086259+00:00",
        "startedAt": "2021-08-03T16:18:05.9058345Z",
        "endedAt": "0001-01-01T00:00:00",
        "meetingType": 0,
        "botFqdn": "domain.co",
        "botIp": null,
        "defaultPassphrase": null,
        "defaultLatency": 0,
        "graphId": "graphId",
        "streams": [
            {
                "id": "screnShareId",
                "aadId": null,
                "callId": "callId",
                "participantGraphId": "screenShareGraphId",
                "displayName": "Screen Share",
                "photoUrl": null,
                "type": 0,
                "state": 0,
                "isHealthy": true,
                "healthMessage": null,
                "audioMuted": false,
                "isSharingAudio": false,
                "isSharingVideo": false,
                "isSharingScreen": false,
                "details": {
                    "streamUrl": null,
                    "audioDemuxed": false,
                    "passphrase": null,
                    "keyLength": 0,
                    "latency": 0,
                    "previewUrl": null
                },
                "createdAt": "0001-01-01T00:00:00",
                "leftAt": null,
                "error": null
            },
            {
                "id": "primarySpeakerId",
                "aadId": null,
                "callId": "callId",
                "participantGraphId": "primarySpeakerGraphId",
                "displayName": "Primary Speaker",
                "photoUrl": null,
                "type": 1,
                "state": 0,
                "isHealthy": true,
                "healthMessage": null,
                "audioMuted": false,
                "isSharingAudio": false,
                "isSharingVideo": false,
                "isSharingScreen": false,
                "details": {
                    "streamUrl": null,
                    "audioDemuxed": false,
                    "passphrase": null,
                    "keyLength": 0,
                    "latency": 0,
                    "previewUrl": null
                },
                "createdAt": "0001-01-01T00:00:00",
                "leftAt": null,
                "error": null
            },
            {
                "id": "participant1Id",
                "aadId": "aadId1",
                "callId": "callId",
                "participantGraphId": "participant1GraphId",
                "displayName": "Participant 1 Name",
                "photoUrl": "https://appServiceUrl/api/participant/photo/00000000-0000-0000-0000-000000000000",
                "type": 2,
                "state": 0,
                "isHealthy": true,
                "healthMessage": "",
                "audioMuted": true,
                "isSharingAudio": true,
                "isSharingVideo": false,
                "isSharingScreen": false,
                "details": {
                    "streamUrl": null,
                    "audioDemuxed": false,
                    "passphrase": null,
                    "keyLength": 0,
                    "latency": 0,
                    "previewUrl": null
                },
                "createdAt": "0001-01-01T00:00:00",
                "leftAt": null,
                "error": null
            },
            {
                "id": "participant2Id",
                "aadId": "aadId2",
                "callId": "callId",
                "participantGraphId": "participant2GraphId",
                "displayName": "Participant 2 Name",
                "photoUrl": "https://appServiceUrl/api/participant/photo/00000000-0000-0000-0000-000000000000",
                "type": 2,
                "state": 0,
                "isHealthy": true,
                "healthMessage": "",
                "audioMuted": true,
                "isSharingAudio": true,
                "isSharingVideo": true,
                "isSharingScreen": false,
                "details": {
                    "streamUrl": null,
                    "audioDemuxed": false,
                    "passphrase": null,
                    "keyLength": 0,
                    "latency": 0,
                    "previewUrl": null
                },
                "createdAt": "0001-01-01T00:00:00",
                "leftAt": null,
                "error": null
            }
        ],
        "injectionStream": null,
        "publicContext": {},
        "privateContext": {
            "streamKey": "hJw5wZxAETkyExVhqdtw"
        }
    }

    The list of the participants joined to the call is listed in the streams property of the response.

Start an extraction

  1. Start extraction stream: To start the extraction of a participant it is necessary to check if the isSharingVideo property of the participant is true. In previous step request response we can see that participant 2 is sharing video.

    Method: POST
    Endpoint: https://{{appServiceUrl}}/api/call/{{callId}}/stream/start-extraction

    Placeholder Description
    callId Call Id

    Headers: Verify the Content-type key has as value application/json
    Body: raw

    Body for RTMP

    Complete the body in Postman with the following:

    {  
        "participantId": "{{participantId}}",  
        "participantGraphId": "{{participantGraphId}}",  
        "resourceType": 2, 
        "protocol": 1, 
        "mode": 1,
        "streamUrl": "{{streamUrl}}",
        "streamKey": "{{streamKey}}", 
        "timeOverlay": false,
        "enableSsl": false
    } 
    Placeholder Description
    participantId Participant Id
    participantGraphId Participant Graph Id
    resourceType Select a value from ResourceType table
    protocol Use 1 for RTMP extraction protocol
    mode Use 1 for Pull, 2 for Push
    streamUrl Stream Url for RTMP in Push mode, use null in Pull mode
    streamKey Stream key for RTMP in Push mode, use null in Pull mode (In Pull mode the extraction uses the stream key of the call's privateContext)
    timeOverlay true or false
    enableSsl Enable SSL: true or false it is in Pull mode, use false in Push mode

    Body for SRT

    Complete the body in Postman with the following:

    {  
        "participantId": "{{participantId}}",  
        "participantGraphId": "{{participantGraphId}}",  
        "resourceType": 2, 
        "protocol": 0, 
        "mode": 2,
        "streamUrl": "{{streamUrl}}",
        "streamKey": "{{streamKey}}", 
        "timeOverlay": false,
        "keyLength": 0,
        "latency": 750
    } 
    Placeholder Description
    participantId Participant Id
    participantGraphId Participant Graph Id
    resourceType Select a value from ResourceType table
    protocol Use 0 for SRT extraction protocol
    mode Use 1 for Caller, 2 for Listener.
    streamUrl Stream Url for extraction in SRT Caller mode, use null for Listener mode
    streamKey use a passphrase or null
    timeOverlay true or false
    keyLength Allowed values: 0, 16, 24, 32
    latency Latency for SRT (eg: 750)

    Table of Available ResourceTypes

    ResourceType Value
    Screen Share 0
    PrimarySpeaker 1
    Participant 2
    TogetherMode 3
    LargeGallery 4
    LiveEvent 5

    Response:

    {
        "id": "participant2Id",
        "resource": {
            "id": "participant2Id",
            "aadId": "aadId2",
            "callId": "callId",
            "participantGraphId": "participant2GraphId",
            "displayName": "Participant 2 Name",
            "photoUrl": "https://appServiceUrl/api/participant/photo/00000000-0000-0000-0000-000000000000",
            "type": 2,
            "state": 2,
            "isHealthy": true,
            "healthMessage": "",
            "audioMuted": true,
            "isSharingAudio": true,
            "isSharingVideo": true,
            "isSharingScreen": false,
            "details": {
                "streamUrl": "rtmps://domain:2940/secure-extraction/hJw5wZxAETkyExVhqdtw?callId=callid",
                "audioDemuxed": false,
                "passphrase": "hJw5wZxAETkyExVhqdtw",
                "keyLength": 0,
                "latency": 0,
                "previewUrl": null
            },
            "createdAt": "0001-01-01T00:00:00",
            "leftAt": null,
            "error": null
        }
    }

    When started the extraction in SRT Listener mode or in RTMP Pull mode, you can use the streamUrl in the details of the response to consume the extraction with a player as VLC

  2. Stop extraction stream:

    Method: POST
    Endpoint: https://{{appServiceUrl}}/api/call/{{callId}}/stream/stop-extraction

    Headers: Verify the Content-type key has as value application/json
    Body: raw

    Complete the body in Postman with the following:

    { 
        "participantId": "{{participantId}}",
        "participantGraphId": "{{participantGraphId}}",
        "resourceType": 2,
    } 
    Placeholder Description
    participantId Participant Id
    participantGraphId Participant Graph Id
    resourceType Select a value from ResourceType table

    Response:

    {
        "id": "participant2Id",
        "resource": {
            "id": "participant2Id",
            "aadId": "aadId2",
            "callId": "callId",
            "participantGraphId": "participant2GraphId",
            "displayName": "Participant 2 Name",
            "photoUrl": "https://appServiceUrl/api/participant/photo/00000000-0000-0000-0000-000000000000",
            "type": 2,
            "state": 0,
            "isHealthy": true,
            "healthMessage": "",
            "audioMuted": false,
            "isSharingAudio": true,
            "isSharingVideo": true,
            "isSharingScreen": false,
            "details": {
                "streamUrl": null,
                "audioDemuxed": false,
                "passphrase": null,
                "keyLength": 0,
                "latency": 0,
                "previewUrl": null
            },
            "createdAt": "0001-01-01T00:00:00",
            "leftAt": null,
            "error": null
        }
    }

Start an injection

  1. Start injection stream:

    Method: POST
    Endpoint: https://{{appServiceUrl}}/api/call/{{callId}}/stream/start-injection

    Placeholder Description
    callId Call Id

    Headers: Verify the Content-type key has as value application/json
    Body: raw

    Body for RTMP

    {
        "protocol": 1,
        "mode": 1,
        "streamUrl": "{{streamUrl}}",
        "enableSSl": false
    }
    Placeholder Description
    protocol Use 1 for RTMP injection protocol
    mode Use 1 for Pull, 2 for Push
    streamUrl RTMP in Pull mode, null for Push mode
    enableSsl Enable SSL: true or false used when mode is Push

    Body for SRT

    {
        "protocol": 0,
        "mode": 1,
        "streamUrl": "{{streamUrl}}",
        "streamKey": "{{streamKey}}",
        "latency": 750,
        "keyLength": 0,
    }
    Placeholder Description
    protocol Use 0 for SRT injection protocol
    mode Use 1 for Caller, 2 for Listener.
    streamUrl Stream Url for injection in SRT Caller mode, use null in Listener mode
    streamKey Use a passphrase or null
    latency Latency (eg: 750)
    keyLength Allowed values: 0, 16, 24, 32

    Response:

    {
        "id": "streamId",
        "resource": {
            "id": "streamId",
            "callId": "callId",
            "injectionUrl": "rtmps://domain.co:2936/secure-ingest/streamkey?callId=callid",
            "passphrase": "streamkey",
            "latency": 0,
            "keyLength": 0,
            "state": 2,
            "startingAt": "2021-08-04T15:41:37.9975386Z",
            "startedAt": "2021-08-04T15:41:38.1097951Z",
            "endingAt": "0001-01-01T00:00:00",
            "endedAt": "0001-01-01T00:00:00",
            "protocol": 1,
            "streamMode": 2,
            "error": null
        }
    }

    The id of the response should be copied for later use when stopping the injection.

  2. Optionally, you can use the following operations to control the injection:

    • Hide:

      Method: POST
      Endpoint: https://{{appServiceUrl}}/api/call/{{callId}}/injection/hide

    • Display:

      Method: POST
      Endpoint: https://{{appServiceUrl}}/api/call/{{callId}}/injection/display

    • Mute:

      Method: POST
      Endpoint: https://{{appServiceUrl}}/api/call/{{callId}}/mute

    • Un-mute:

      Method: POST
      Endpoint: https://{{appServiceUrl}}/api/call/{{callId}}/unmute

    • Set Volume:

      Method: POST
      Endpoint: https://{{appServiceUrl}}/api/call/{{callId}}/injection/set-volume

      Note: Check OpenAPI specification for information about request body

  3. Stop injection stream:

    Method: POST
    Endpoint: https://{{appServiceUrl}}/api/call/{{callId}}/stream/{{streamId}}/stop-injection

    Placeholder Description
    callId Call Id
    streamId Obtained Stream Id when started the injection

    Response

    {
        "id": "streamId",
        "resource": {
            "id": "streamId",
            "callId": "callId",
            "injectionUrl": "rtmps://domain.co:2936/secure-ingest/streamkey?callId=callid",
            "passphrase": "streamkey",
            "latency": 0,
            "keyLength": 0,
            "state": 0,
            "startingAt": "2021-08-04T16:01:22.4798097Z",
            "startedAt": "2021-08-04T16:01:22.7420745Z",
            "endingAt": "2021-08-04T16:03:36.9824056Z",
            "endedAt": "2021-08-04T16:03:37.0222905Z",
            "protocol": 1,
            "streamMode": 2,
            "error": null
        }
    }

Generate RTMP stream key

  1. Generate RTMP stream key: This endpoint updates the streamKey in the privateContext of the call, used key for RTMP injections and extractions. Only need to use this endpoint if you want to change the RMTP key of the current meeting, each meeting has its own RTMP stream key.

    Method: POST
    Endpoint: https://{{appServiceUrl}}/api/call/{{callId}}/generate-stream-key

    Placeholder Description
    callId Call Id

    Headers: Verify the Content-type key has as value application/json
    Body: raw

    Response

    {
        "callId": "callId",
        "streamKey": "QBfeZihvokOGiISvQ2pyTw"
    }

Disconnect Call

  1. Disconnect the bot from the call:

    Method: DELETE Endpoint: https://{{appServiceUrl}}/api/call/{{callId}}

    Response:

    {
        "id": "callid",
        "resource": {
            "id": "callid",
            "meetingUrl": "teamsMeetingUrl",
            "meetingId": "meetingId",
            "state": 2,
            "createdAt": "2021-08-03T16:17:57.4086259+00:00",
            "startedAt": "2021-08-03T16:18:05.9058345Z",
            "endedAt": "0001-01-01T00:00:00",
            "meetingType": 0,
            "botFqdn": "domain.co",
            "botIp": null,
            "defaultPassphrase": null,
            "defaultLatency": 0,
            "graphId": "graphId",
            "streams": [],
            "injectionStream": null,
            "publicContext": {},
            "privateContext": {
                "streamKey": "hJw5wZxAETkyExVhqdtw"
            }
        }
    }

Stop service

  1. Stop the service:

    Method: POST
    Endpoint: https://{{appServiceUrl}}/api/service/{{serviceId}}/stop

    Placeholder Description
    appServiceUrl AppService URL
    serviceId Service Id (the default id is 00000000-0000-0000-0000-000000000000)

    Response:

    {
        "id": "serviceId",
        "resource": {
            "id": "serviceId",
            "callId": null,
            "name": "serviceName",
            "state": 3,
            "createdAt": "2021-06-07T15:44:16.6961976+00:00",
            "infrastructure": {
                "virtualMachineName": "test-vm",
                "resourceGroup": "test-bot-vm",
                "subscriptionId": "a874b409-c14a-448b-935d-296ac546e568",
                "id": "/subscriptions/a874b409-c14a-448b-935d-296ac546e568/resourcegroups/test-bot-vm/providers/microsoft.compute/virtualmachines/test-vm",
                "powerState": "PowerState/running",
                "ipAddress": "21.120.170.210",
                "dns": "domain.co",
                "provisioningDetails": {
                    "state": {
                        "id": 2,
                        "name": "Deprovisioning"
                    },
                    "message": "Deprovisioning service serviceName"
                }
            }
        }
    }