

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 啟動對話串流至 Amazon Lex V2 機器人
<a name="start-stream-conversation"></a>

您可以使用 [StartConversation](https://docs.aws.amazon.com/lexv2/latest/APIReference/API_runtime_StartConversation.html) 操作，在您的應用程式中啟動使用者與 Amazon Lex V2 機器人之間的串流。來自應用程式的`POST`請求會在您的應用程式與 Amazon Lex V2 機器人之間建立連線。這可讓您的應用程式和機器人開始透過事件互相交換資訊。

**注意**  
使用 StartConversation 時，如果表達用語音訊持續時間超過 的設定值`max-length-ms`，Amazon Lex V2 會在指定的持續時間中斷音訊。

只有下列 SDKs 支援`StartConversation`此操作：
+ [適用於 C\$1\$1 的 AWS SDK](https://docs.aws.amazon.com/goto/SdkForCpp/runtime.lex.v2-2020-08-07/StartConversation)
+ [適用於 Java 的 AWS SDK 第 2 版](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lexruntimev2/LexRuntimeV2AsyncClient.html)
+ [適用於 JavaScript 的 AWS 開發套件 v3](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-lex-runtime-v2/index.html#aws-sdkclient-lex-runtime-v2)
+ [適用於 Ruby V3 的 AWS 開發套件](https://docs.aws.amazon.com/goto/SdkForRubyV3/runtime.lex.v2-2020-08-07/StartConversation)

您的應用程式必須傳送至 Amazon Lex V2 機器人的第一個事件是 [ConfigurationEvent](https://docs.aws.amazon.com/lexv2/latest/APIReference/API_runtime_ConfigurationEvent.html)。此事件包含回應類型格式等資訊。以下是您可以在組態事件中使用的參數：
+ **responseContentType** – 決定機器人是否以文字或語音回應使用者輸入。
+ **sessionState** – 與機器人串流工作階段相關的資訊，例如預先決定的意圖或對話方塊狀態。
+ **welcomeMessages** – 指定使用者在與機器人對話開始時播放的歡迎訊息。這些訊息會在使用者提供任何輸入之前播放。若要啟用歡迎訊息，您還必須為 `sessionState`和 `dialogAction` 參數指定值。
+ **disablePlayback** – 決定機器人是否應該等待用戶端的提示，然後再開始接聽來電者輸入。根據預設，會啟用播放，因此此欄位的值為 `false`。
+ **requestAttributes** – 提供請求的其他資訊。

如需有關如何為上述參數指定值的資訊，請參閱 [StartConversation](https://docs.aws.amazon.com/lexv2/latest/APIReference/API_runtime_StartConversation.html) 操作的 [ConfigurationEvent](https://docs.aws.amazon.com/lexv2/latest/APIReference/API_runtime_ConfigurationEvent.html) 資料類型。

機器人和應用程式之間的每個串流只能有一個組態事件。在您的應用程式傳送組態事件之後，機器人可以從您的應用程式進行額外的通訊。

如果您已指定使用者使用音訊與 Amazon Lex V2 機器人通訊，您的應用程式可以在該對話期間將下列事件傳送至機器人：
+ [AudioInputEvent](https://docs.aws.amazon.com/lexv2/latest/APIReference/API_runtime_AudioInputEvent.html) – 包含大小上限為 320 位元組的音訊區塊。您的應用程式必須使用多個音訊輸入事件，將訊息從伺服器傳送至機器人。串流中的每個音訊輸入事件都必須具有相同的音訊格式。
+ [DTMFInputEvent](https://docs.aws.amazon.com/lexv2/latest/APIReference/API_runtime_DTMFInputEvent.html) – 將 DTMF 輸入傳送至機器人。每個 DTMF 按鍵對應至單一事件。
+ [PlaybackCompletionEvent](https://docs.aws.amazon.com/lexv2/latest/APIReference/API_runtime_PlaybackCompletionEvent.html) – 通知伺服器使用者輸入的回應已播放給他們。如果您要傳送音訊回應給使用者，則必須使用播放完成事件。如果您的`disablePlayback`組態事件是 `true`，則無法使用此功能。
+ [DisconnectionEvent](https://docs.aws.amazon.com/lexv2/latest/APIReference/API_runtime_DTMFInputEvent.html) – 通知機器人使用者已中斷與對話的連線。

如果您已指定使用者使用文字與機器人通訊，您的應用程式可以在該對話期間將下列事件傳送至機器人：
+ [TextInputEvent](https://docs.aws.amazon.com/lexv2/latest/APIReference/API_runtime_TextInputEvent.html) – 從您的應用程式傳送至機器人的文字。文字輸入事件中最多可有 512 個字元。
+ [PlaybackCompletionEvent](https://docs.aws.amazon.com/lexv2/latest/APIReference/API_runtime_PlaybackCompletionEvent.html) – 通知伺服器使用者輸入的回應已播放給他們。如果您要將音訊播放回使用者，您必須使用此事件。如果您的`disablePlayback`組態事件是 `true`，則無法使用此功能。
+ [DisconnectionEvent](https://docs.aws.amazon.com/lexv2/latest/APIReference/API_runtime_DTMFInputEvent.html) – 通知機器人使用者已中斷與對話的連線。

您必須以正確的格式對傳送到 Amazon Lex V2 機器人的每個事件進行編碼。如需詳細資訊，請參閱[事件串流編碼](event-stream-encoding.md)。

每個事件都有事件 ID。為了協助疑難排解串流中可能發生的任何問題，請為每個輸入事件指派唯一的事件 ID。然後，您可以使用機器人對任何處理失敗進行疑難排解。

Amazon Lex V2 也會針對每個事件使用時間戳記。除了事件 ID 之外，您還可以使用這些時間戳記來協助疑難排解任何網路傳輸問題。

在使用者與 Amazon Lex V2 機器人之間的對話期間，機器人可以傳送下列傳出事件以回應使用者：
+ [IntentResultEvent](https://docs.aws.amazon.com/lexv2/latest/APIReference/API_runtime_IntentResultEvent.html) – 包含 Amazon Lex V2 從使用者表達用語決定的意圖。每個內部結果事件包括：
  + **inputMode** – 使用者表達用語的類型。有效值為 `Speech`、`DTMF` 或 `Text`。
  + **解**譯 – Amazon Lex V2 從使用者表達用語中決定的解譯。
  + **requestAttributes** – 如果您尚未使用 lambda 函數修改請求屬性，這些屬性與對話開始時傳遞的屬性相同。
  + **sessionId** – 用於對話的工作階段識別符。
  + **sessionState** – 使用者使用 Amazon Lex V2 的工作階段狀態。
+ [TranscriptEvent](https://docs.aws.amazon.com/lexv2/latest/APIReference/API_runtime_TranscriptEvent.html) – 如果使用者為您的應用程式提供輸入，則此事件包含使用者對機器人表達用語的文字記錄。`TranscriptEvent` 如果沒有使用者輸入，您的應用程式不會收到 。

  傳送至應用程式的文字記錄事件值取決於您是否已指定音訊 （語音和 DMTF) 或文字做為對話模式：
  + 語音輸入的文字記錄 – 如果使用者正在與機器人說話，文字記錄事件就是使用者音訊的文字記錄。這是從使用者開始說話到他們結束說話時所有語音的文字記錄。
  + DTMF 輸入的文字記錄 – 如果使用者在鍵盤上輸入，文字記錄事件會包含使用者在輸入中按的所有數字。
  + 文字輸入的文字記錄 – 如果使用者提供文字輸入，文字記錄事件會包含使用者輸入中的所有文字。
+ [TextResponseEvent](https://docs.aws.amazon.com/lexv2/latest/APIReference/API_runtime_TextResponseEvent.html) – 包含文字格式的機器人回應。預設會傳回文字回應。如果您已設定 Amazon Lex V2 傳回音訊回應，則此文字會用來產生音訊回應。每個文字回應事件都包含一組訊息物件，機器人會傳回給使用者。
+ [AudioResponseEvent](https://docs.aws.amazon.com/lexv2/latest/APIReference/API_runtime_AudioResponseEvent.html) – 包含從 中產生的文字合成的音訊回應`TextResponseEvent`。若要接收音訊回應事件，您必須設定 Amazon Lex V2 以提供音訊回應。所有音訊回應事件都具有相同的音訊格式。每個事件都包含不超過 100 個位元組的音訊區塊。Amazon Lex V2 會傳送空的音訊區塊，並將 `bytes` 欄位設定為 `null`，以表示音訊回應事件結束到您的應用程式。
+ [PlaybackInterruptionEvent](https://docs.aws.amazon.com/lexv2/latest/APIReference/API_runtime_PlaybackInterruptionEvent.html) – 當使用者中斷機器人傳送至應用程式的回應時，Amazon Lex V2 會觸發此事件以停止播放回應。
+ [HeartbeatEvent](https://docs.aws.amazon.com/lexv2/latest/APIReference/API_runtime_HeartbeatEvent.html) – Amazon Lex V2 會定期傳送此事件，以防止應用程式與機器人之間的連線逾時。

## 使用 Amazon Lex V2 機器人時音訊對話的事件時間序列
<a name="audio-conversation-sequence"></a>

下圖顯示使用者與 Amazon Lex V2 機器人之間的串流音訊對話。應用程式會持續將音訊串流至機器人，而機器人會從音訊尋找使用者輸入。在此範例中，使用者和機器人都使用語音進行通訊。每個圖表對應於使用者表達用語，以及機器人對該表達用語的回應。

下圖顯示應用程式與機器人之間的對話開始。串流從時間零 (t0) 開始。

![\[Timeline showing audio input events from application and various response events from bot during a conversation.\]](http://docs.aws.amazon.com/zh_tw/lexv2/latest/dg/images/Streaming-Page-1.png)


下列清單說明上圖的事件。
+ t0：應用程式會將組態事件傳送至機器人以啟動串流。
+ t1：應用程式串流音訊資料。此資料會從應用程式分成一系列的輸入事件。
+ t2：對於*使用者表達用語 1*，機器人會在使用者開始說話時偵測到音訊輸入事件。
+ t2：當使用者說話時，機器人會傳送活動訊號事件來維持連線。它會間歇性地傳送這些事件，以確保連線不會逾時。
+ t3：機器人會偵測使用者表達用語的結尾。
+ t4：機器人會將文字記錄事件傳回給應用程式，其中包含使用者語音的文字記錄。這是*機器人對使用者表達用語 1 的回應*開始。
+ t5：機器人會傳送意圖結果事件，以指出使用者想要執行的動作。
+ t6：機器人開始在文字回應事件中以文字形式提供其回應。
+ t7：機器人會傳送一系列音訊回應事件至應用程式，以供使用者播放。
+ t8：機器人會傳送另一個活動訊號事件來間歇性維護連線。

下圖是上一個圖表的接續。它會顯示應用程式傳送播放完成事件至機器人，以指出其已停止播放使用者的音訊回應。應用程式會播放*對使用者表達用語 1 的機器人回應*。使用者使用*使用者表達用語 2 回應對使用者表達用語 1 的機器人回應*。 **

![\[Timeline of audio input events from user and response events from bot, showing interaction flow.\]](http://docs.aws.amazon.com/zh_tw/lexv2/latest/dg/images/Streaming-Page-2.png)


下列清單說明上圖的事件：
+ t10：應用程式會傳送播放完成事件，表示已完成播放機器人的訊息給使用者。
+ t11：應用程式會將使用者回應傳回給機器人，做為*使用者表達用語 2*。
+ t12：對於對*使用者表達用語 2 的機器人回應*，機器人會等待使用者停止說話，然後開始提供音訊回應。
+ t13：當機器人將*機器人回應傳送給應用程式的使用者表達用語 2* 時，機器人會偵測*使用者表達用語 3 *的開始。機器人會停止*對使用者表達用語 2 的機器人回應*，並傳送播放中斷事件。
+ t14：機器人會傳送播放中斷事件至應用程式，以表示使用者已中斷提示。

下圖顯示*機器人對使用者表達用語 3 的回應*，以及在機器人回應使用者表達用語之後繼續對話。

![\[Diagram showing events flow between application, bot, and user utterances over time.\]](http://docs.aws.amazon.com/zh_tw/lexv2/latest/dg/images/Streaming-Page-3.png)


# 使用 API 開始串流對話
<a name="using-streaming-api"></a>

當您啟動串流至 Amazon Lex V2 機器人時，您會完成下列任務：

1. 建立與伺服器的初始連線。

1. 設定安全登入資料和機器人詳細資訊。機器人詳細資訊包括機器人接受 DTMF 和音訊輸入，還是文字輸入。

1. 將事件傳送至伺服器。這些事件是來自使用者的文字資料或音訊資料。

1. 處理從伺服器傳送的事件。在此步驟中，您可以判斷機器人輸出是以文字或語音呈現給使用者。

下列程式碼範例會初始化與 Amazon Lex V2 機器人和本機電腦的串流對話。您可以修改程式碼以符合您的需求。

下列程式碼是使用 適用於 Java 的 AWS SDK 開始與機器人連線並設定機器人詳細資訊和登入資料的範例請求。

```
package com.lex.streaming.sample;

import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.lexruntimev2.LexRuntimeV2AsyncClient;
import software.amazon.awssdk.services.lexruntimev2.model.ConversationMode;
import software.amazon.awssdk.services.lexruntimev2.model.StartConversationRequest;

import java.net.URISyntaxException;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

/**
 * The following code creates a connection with the Amazon Lex bot and configures the bot details and credentials.  
 * Prerequisite: To use this example, you must be familiar with the Reactive streams programming model.
 * For more information, see
 * https://github.com/reactive-streams/reactive-streams-jvm.
 * This example uses AWS SDK for Java for Amazon Lex V2.
 * <p>
 * The following sample application interacts with an Amazon Lex bot with the streaming API. It uses the Audio
 * conversation mode to return audio responses to the user's input.
 * <p>
 * The code in this example accomplishes the following:
 * <p>
 * 1. Configure details about the conversation between the user and the Amazon Lex bot. These details include the conversation mode and the specific bot the user is speaking with.
 * 2. Create an events publisher that passes the audio events to the Amazon Lex bot after you establish the connection. The code we provide in this example tells your computer to pick up the audio from
 * your microphone and send that audio data to Amazon Lex.
 * 3. Create a response handler that handles the audio responses from the Amazon Lex bot and plays back the audio to you.
 */
public class LexBidirectionalStreamingExample {

    public static void main(String[] args) throws URISyntaxException, InterruptedException {
        String botId = "";
        String botAliasId = "";
        String localeId = "";
        String accessKey = "";
        String secretKey = "";
        String sessionId = UUID.randomUUID().toString();
        Region region = Region.region_name; // Choose an AWS Region where the Amazon Lex Streaming API is available.

        AwsCredentialsProvider awsCredentialsProvider = StaticCredentialsProvider
                .create(AwsBasicCredentials.create(accessKey, secretKey));

        // Create a new SDK client. You need to use an asynchronous client.
        System.out.println("step 1: creating a new Lex SDK client");
        LexRuntimeV2AsyncClient lexRuntimeServiceClient = LexRuntimeV2AsyncClient.builder()
                .region(region)
                .credentialsProvider(awsCredentialsProvider)
                .build();


        // Configure the bot, alias and locale that you'll use to have a conversation.
        System.out.println("step 2: configuring bot details");
        StartConversationRequest.Builder startConversationRequestBuilder = StartConversationRequest.builder()
                .botId(botId)
                .botAliasId(botAliasId)
                .localeId(localeId);

        // Configure the conversation mode of the bot. By default, the
        // conversation mode is audio.
        System.out.println("step 3: choosing conversation mode");
        startConversationRequestBuilder = startConversationRequestBuilder.conversationMode(ConversationMode.AUDIO);

        // Assign a unique identifier for the conversation.
        System.out.println("step 4: choosing a unique conversation identifier");
        startConversationRequestBuilder = startConversationRequestBuilder.sessionId(sessionId);

        // Start the initial request.
        StartConversationRequest startConversationRequest = startConversationRequestBuilder.build();

        // Create a stream of audio data to the Amazon Lex bot. The stream will start after the connection is established with the bot.
        EventsPublisher eventsPublisher = new EventsPublisher();

        // Create a class to handle responses from bot. After the server processes the user data you've streamed, the server responds
        // on another stream.
        BotResponseHandler botResponseHandler = new BotResponseHandler(eventsPublisher);

        // Start a connection and pass in the publisher that streams the audio and process the responses from the bot.
        System.out.println("step 5: starting the conversation ...");
        CompletableFuture<Void> conversation = lexRuntimeServiceClient.startConversation(
                startConversationRequest,
                eventsPublisher,
                botResponseHandler);

        // Wait until the conversation finishes. The conversation finishes if the dialog state reaches the "Closed" state.
        // The client stops the connection. If an exception occurs during the conversation, the
        // client sends a disconnection event.
        conversation.whenComplete((result, exception) -> {
            if (exception != null) {
                eventsPublisher.disconnect();
            }
        });

        // The conversation finishes when the dialog state is closed and last prompt has been played.
        while (!botResponseHandler.isConversationComplete()) {
            Thread.sleep(100);
        }

        // Randomly sleep for 100 milliseconds to prevent JVM from exiting.
        // You won't need this in your production code because your JVM is
        // likely to always run.
        // When the conversation finishes, the following code block stops publishing more data and informs the Amazon Lex bot that there is no more data to send.
        if (botResponseHandler.isConversationComplete()) {
            System.out.println("conversation is complete.");
            eventsPublisher.stop();
        }
    }
}
```

下列程式碼是使用 適用於 Java 的 AWS SDK 將事件傳送至機器人的範例請求。此範例中的程式碼會使用電腦上的麥克風來傳送音訊事件。

```
package com.lex.streaming.sample;

import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import software.amazon.awssdk.services.lexruntimev2.model.StartConversationRequestEventStream;

/**
 * You use the Events publisher to send events to the Amazon Lex bot. When you establish a connection, the bot uses the
 * subscribe() method and enables the events publisher starts sending events to
 * your computer. The bot uses the "request" method of the subscription to make more requests. For more information on the request method, see https://github.com/reactive-streams/reactive-streams-jvm. 
 */
public class EventsPublisher implements Publisher<StartConversationRequestEventStream> {

    private AudioEventsSubscription audioEventsSubscription;

    @Override
    public void subscribe(Subscriber<? super StartConversationRequestEventStream> subscriber) {
        if (audioEventsSubscription == null) {

            audioEventsSubscription = new AudioEventsSubscription(subscriber);
            subscriber.onSubscribe(audioEventsSubscription);

        } else {
            throw new IllegalStateException("received unexpected subscription request");
        }
    }

    public void disconnect() {
        if (audioEventsSubscription != null) {
            audioEventsSubscription.disconnect();
        }
    }

    public void stop() {
        if (audioEventsSubscription != null) {
            audioEventsSubscription.stop();
        }
    }

    public void playbackFinished() {
        if (audioEventsSubscription != null) {
            audioEventsSubscription.playbackFinished();
        }
    }
}
```

下列程式碼是使用 適用於 Java 的 AWS SDK 處理機器人回應的範例請求。此範例中的程式碼會將 Amazon Lex V2 設定為播放音訊回應給您。

```
package com.lex.streaming.sample;

import javazoom.jl.decoder.JavaLayerException;
import javazoom.jl.player.advanced.AdvancedPlayer;
import javazoom.jl.player.advanced.PlaybackEvent;
import javazoom.jl.player.advanced.PlaybackListener;
import software.amazon.awssdk.core.async.SdkPublisher;
import software.amazon.awssdk.services.lexruntimev2.model.AudioResponseEvent;
import software.amazon.awssdk.services.lexruntimev2.model.DialogActionType;
import software.amazon.awssdk.services.lexruntimev2.model.IntentResultEvent;
import software.amazon.awssdk.services.lexruntimev2.model.PlaybackInterruptionEvent;
import software.amazon.awssdk.services.lexruntimev2.model.StartConversationResponse;
import software.amazon.awssdk.services.lexruntimev2.model.StartConversationResponseEventStream;
import software.amazon.awssdk.services.lexruntimev2.model.StartConversationResponseHandler;
import software.amazon.awssdk.services.lexruntimev2.model.TextResponseEvent;
import software.amazon.awssdk.services.lexruntimev2.model.TranscriptEvent;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.concurrent.CompletableFuture;

/**
 * The following class is responsible for processing events sent from the Amazon Lex bot. The bot sends multiple audio events,
 * so the following code concatenates those audio events and uses a publicly available Java audio player to play out the message to
 * the user.
 */
public class BotResponseHandler implements StartConversationResponseHandler {

    private final EventsPublisher eventsPublisher;

    private boolean lastBotResponsePlayedBack;
    private boolean isDialogStateClosed;
    private AudioResponse audioResponse;


    public BotResponseHandler(EventsPublisher eventsPublisher) {
        this.eventsPublisher = eventsPublisher;
        this.lastBotResponsePlayedBack = false;// At the start, we have not played back last response from bot.
        this.isDialogStateClosed = false; // At the start, the dialog state is open.
    }

    @Override
    public void responseReceived(StartConversationResponse startConversationResponse) {
        System.out.println("successfully established the connection with server. request id:" + startConversationResponse.responseMetadata().requestId()); // would have 2XX, request id.
    }

    @Override
    public void onEventStream(SdkPublisher<StartConversationResponseEventStream> sdkPublisher) {

        sdkPublisher.subscribe(event -> {
            if (event instanceof PlaybackInterruptionEvent) {
                handle((PlaybackInterruptionEvent) event);
            } else if (event instanceof TranscriptEvent) {
                handle((TranscriptEvent) event);
            } else if (event instanceof IntentResultEvent) {
                handle((IntentResultEvent) event);
            } else if (event instanceof TextResponseEvent) {
                handle((TextResponseEvent) event);
            } else if (event instanceof AudioResponseEvent) {
                handle((AudioResponseEvent) event);
            }
        });
    }

    @Override
    public void exceptionOccurred(Throwable throwable) {
        System.err.println("got an exception:" + throwable);
    }

    @Override
    public void complete() {
        System.out.println("on complete");
    }

    private void handle(PlaybackInterruptionEvent event) {
        System.out.println("Got a PlaybackInterruptionEvent: " + event);
    }

    private void handle(TranscriptEvent event) {
        System.out.println("Got a TranscriptEvent: " + event);
    }


    private void handle(IntentResultEvent event) {
        System.out.println("Got an IntentResultEvent: " + event);
        isDialogStateClosed = DialogActionType.CLOSE.equals(event.sessionState().dialogAction().type());
    }

    private void handle(TextResponseEvent event) {
        System.out.println("Got an TextResponseEvent: " + event);
        event.messages().forEach(message -> {
            System.out.println("Message content type:" + message.contentType());
            System.out.println("Message content:" + message.content());
        });
    }

    private void handle(AudioResponseEvent event) {//Synthesize speech
        // System.out.println("Got a AudioResponseEvent: " + event);
        if (audioResponse == null) {
            audioResponse = new AudioResponse();
            //Start an audio player in a different thread.
            CompletableFuture.runAsync(() -> {
                try {
                    AdvancedPlayer audioPlayer = new AdvancedPlayer(audioResponse);

                    audioPlayer.setPlayBackListener(new PlaybackListener() {
                        @Override
                        public void playbackFinished(PlaybackEvent evt) {
                            super.playbackFinished(evt);

                            // Inform the Amazon Lex bot that the playback has finished.
                            eventsPublisher.playbackFinished();
                            if (isDialogStateClosed) {
                                lastBotResponsePlayedBack = true;
                            }
                        }
                    });
                    audioPlayer.play();
                } catch (JavaLayerException e) {
                    throw new RuntimeException("got an exception when using audio player", e);
                }
            });
        }

        if (event.audioChunk() != null) {
            audioResponse.write(event.audioChunk().asByteArray());
        } else {
            // The audio audio prompt has ended when the audio response has no
            // audio bytes.
            try {
                audioResponse.close();
                audioResponse = null;  // Prepare for the next audio prompt.
            } catch (IOException e) {
                throw new UncheckedIOException("got an exception when closing the audio response", e);
            }
        }
    }

    // The conversation with the Amazon Lex bot is complete when the bot marks the Dialog as DialogActionType.CLOSE
    // and any prompt playback is finished. For more information, see
    // https://docs.aws.amazon.com/lexv2/latest/dg/API_runtime_DialogAction.html.
    public boolean isConversationComplete() {
        return isDialogStateClosed && lastBotResponsePlayedBack;
    }

}
```

若要設定機器人以音訊回應輸入事件，您必須先從 Amazon Lex V2 訂閱音訊事件，然後設定機器人以提供音訊回應給使用者的輸入事件。

下列程式碼是從 Amazon Lex V2 訂閱音訊事件 適用於 Java 的 AWS SDK 的範例。

```
package com.lex.streaming.sample;

import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.services.lexruntimev2.model.AudioInputEvent;
import software.amazon.awssdk.services.lexruntimev2.model.ConfigurationEvent;
import software.amazon.awssdk.services.lexruntimev2.model.DisconnectionEvent;
import software.amazon.awssdk.services.lexruntimev2.model.PlaybackCompletionEvent;
import software.amazon.awssdk.services.lexruntimev2.model.StartConversationRequestEventStream;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.TargetDataLine;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicLong;

public class AudioEventsSubscription implements Subscription {
    private static final AudioFormat MIC_FORMAT = new AudioFormat(8000, 16, 1, true, false);
    private static final String AUDIO_CONTENT_TYPE = "audio/lpcm; sample-rate=8000; sample-size-bits=16; channel-count=1; is-big-endian=false";
    //private static final String RESPONSE_TYPE = "audio/pcm; sample-rate=8000";
    private static final String RESPONSE_TYPE = "audio/mpeg";
    private static final int BYTES_IN_AUDIO_CHUNK = 320;
    private static final AtomicLong eventIdGenerator = new AtomicLong(0);

    private final AudioInputStream audioInputStream;
    private final Subscriber<? super StartConversationRequestEventStream> subscriber;
    private final EventWriter eventWriter;
    private CompletableFuture eventWriterFuture;


    public AudioEventsSubscription(Subscriber<? super StartConversationRequestEventStream> subscriber) {
        this.audioInputStream = getMicStream();
        this.subscriber = subscriber;
        this.eventWriter = new EventWriter(subscriber, audioInputStream);
        configureConversation();
    }

    private AudioInputStream getMicStream() {
        try {
            DataLine.Info dataLineInfo = new DataLine.Info(TargetDataLine.class, MIC_FORMAT);
            TargetDataLine targetDataLine = (TargetDataLine) AudioSystem.getLine(dataLineInfo);

            targetDataLine.open(MIC_FORMAT);
            targetDataLine.start();

            return new AudioInputStream(targetDataLine);
        } catch (LineUnavailableException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void request(long demand) {
        // If a thread to write events has not been started, start it.
        if (eventWriterFuture == null) {
            eventWriterFuture = CompletableFuture.runAsync(eventWriter);
        }
        eventWriter.addDemand(demand);
    }

    @Override
    public void cancel() {
        subscriber.onError(new RuntimeException("stream was cancelled"));
        try {
            audioInputStream.close();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    public void configureConversation() {
        String eventId = "ConfigurationEvent-" + String.valueOf(eventIdGenerator.incrementAndGet());

        ConfigurationEvent configurationEvent = StartConversationRequestEventStream
                .configurationEventBuilder()
                .eventId(eventId)
                .clientTimestampMillis(System.currentTimeMillis())
                .responseContentType(RESPONSE_TYPE)
                .build();

        System.out.println("writing config event");
        eventWriter.writeConfigurationEvent(configurationEvent);
    }

    public void disconnect() {

        String eventId = "DisconnectionEvent-" + String.valueOf(eventIdGenerator.incrementAndGet());

        DisconnectionEvent disconnectionEvent = StartConversationRequestEventStream
                .disconnectionEventBuilder()
                .eventId(eventId)
                .clientTimestampMillis(System.currentTimeMillis())
                .build();

        eventWriter.writeDisconnectEvent(disconnectionEvent);

        try {
            audioInputStream.close();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }

    }
    //Notify the subscriber that we've finished.
    public void stop() {
        subscriber.onComplete();
    }

    public void playbackFinished() {
        String eventId = "PlaybackCompletion-" + String.valueOf(eventIdGenerator.incrementAndGet());

        PlaybackCompletionEvent playbackCompletionEvent = StartConversationRequestEventStream
                .playbackCompletionEventBuilder()
                .eventId(eventId)
                .clientTimestampMillis(System.currentTimeMillis())
                .build();

        eventWriter.writePlaybackFinishedEvent(playbackCompletionEvent);
    }

    private static class EventWriter implements Runnable {
        private final BlockingQueue<StartConversationRequestEventStream> eventQueue;
        private final AudioInputStream audioInputStream;
        private final AtomicLong demand;
        private final Subscriber subscriber;

        private boolean conversationConfigured;

        public EventWriter(Subscriber subscriber, AudioInputStream audioInputStream) {
            this.eventQueue = new LinkedBlockingQueue<>();

            this.demand = new AtomicLong(0);
            this.subscriber = subscriber;
            this.audioInputStream = audioInputStream;
        }

        public void writeConfigurationEvent(ConfigurationEvent configurationEvent) {
            eventQueue.add(configurationEvent);
        }

        public void writeDisconnectEvent(DisconnectionEvent disconnectionEvent) {
            eventQueue.add(disconnectionEvent);
        }

        public void writePlaybackFinishedEvent(PlaybackCompletionEvent playbackCompletionEvent) {
            eventQueue.add(playbackCompletionEvent);
        }

        void addDemand(long l) {
            this.demand.addAndGet(l);
        }

        @Override
        public void run() {
            try {

                while (true) {
                    long currentDemand = demand.get();

                    if (currentDemand > 0) {
                        // Try to read from queue of events.
                        // If nothing is in queue at this point, read the audio events directly from audio stream.
                        for (long i = 0; i < currentDemand; i++) {

                            if (eventQueue.peek() != null) {
                                subscriber.onNext(eventQueue.take());
                                demand.decrementAndGet();
                            } else {
                                writeAudioEvent();
                            }
                        }
                    }
                }
            } catch (InterruptedException e) {
                throw new RuntimeException("interrupted when reading data to be sent to server");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        private void writeAudioEvent() {
            byte[] bytes = new byte[BYTES_IN_AUDIO_CHUNK];

            int numBytesRead = 0;
            try {
                numBytesRead = audioInputStream.read(bytes);
                if (numBytesRead != -1) {
                    byte[] byteArrayCopy = Arrays.copyOf(bytes, numBytesRead);

                    String eventId = "AudioEvent-" + String.valueOf(eventIdGenerator.incrementAndGet());

                    AudioInputEvent audioInputEvent = StartConversationRequestEventStream
                            .audioInputEventBuilder()
                            .audioChunk(SdkBytes.fromByteBuffer(ByteBuffer.wrap(byteArrayCopy)))
                            .contentType(AUDIO_CONTENT_TYPE)
                            .clientTimestampMillis(System.currentTimeMillis())
                            .eventId(eventId).build();

                    //System.out.println("sending audio event:" + audioInputEvent);
                    subscriber.onNext(audioInputEvent);
                    demand.decrementAndGet();
                    //System.out.println("sent audio event:" + audioInputEvent);
                } else {
                    subscriber.onComplete();
                    System.out.println("audio stream has ended");
                }

            } catch (IOException e) {
                System.out.println("got an exception when reading from audio stream");
                System.err.println(e);
                subscriber.onError(e);
            }
        }
    }
}
```

下列 適用於 Java 的 AWS SDK 範例會設定 Amazon Lex V2 機器人，以提供輸入事件的音訊回應。

```
package com.lex.streaming.sample;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.Optional;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

public class AudioResponse extends InputStream{

    // Used to convert byte, which is signed in Java, to positive integer (unsigned)
    private static final int UNSIGNED_BYTE_MASK = 0xFF;
    private static final long POLL_INTERVAL_MS = 10;

    private final LinkedBlockingQueue<Integer> byteQueue = new LinkedBlockingQueue<>();

    private volatile boolean closed;

    @Override
    public int read() throws IOException {
        try {
            Optional<Integer> maybeInt;
            while (true) {
                maybeInt = Optional.ofNullable(this.byteQueue.poll(POLL_INTERVAL_MS, TimeUnit.MILLISECONDS));

                // If we get an integer from the queue, return it.
                if (maybeInt.isPresent()) {
                    return maybeInt.get();
                }

                // If the stream is closed and there is nothing queued up, return -1.
                if (this.closed) {
                    return -1;
                }
            }
        } catch (InterruptedException e) {
            throw new IOException(e);
        }
    }

    /**
     * Writes data into the stream to be offered on future read() calls.
     */
    public void write(byte[] byteArray) {
        // Don't write into the stream if it is already closed.
        if (this.closed) {
            throw new UncheckedIOException(new IOException("Stream already closed when attempting to write into it."));
        }

        for (byte b : byteArray) {
            this.byteQueue.add(b & UNSIGNED_BYTE_MASK);
        }
    }

    @Override
    public void close() throws IOException {
        this.closed = true;
        super.close();
    }
}
```