Amazon Connect にて録音した音声に別録音の音声が入る

## 起きたこと
先日 Amazon Connect を駆使して構築したIVRシステムに、
以下の記事を参考にして留守録機能を搭載しました。
https://dev.classmethod.jp/articles/amazon-connect-voice-mail-from-kinesis-video-stream/

しかし、どうやら録音した音声に別の通話の音声が混ざるケースがあるようなので調査しました。

## まとめたスライド
<iframe src="https://docs.google.com/presentation/d/e/2PACX-1vSflV2od9aQXZPT75ZDX3_UR3T6stLPjofiA3hh7no-0KLQrHBOh7Tn-vdcFwwE8XdE-T45fB0Cp7Yn/embed?start=false&loop=false&delayms=3000" frameborder="0" width="960" height="569" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe>

## 原因
StreamARNが重複することがあり、そのStreamARNに`GetMedia`で開始契機だけを指定することで発生していました。
(StreamARNは1通話ごとに1つ作成されると思っていましたが、AWSへ問い合わせてみると再利用されることがあるようです。)

`GetMedia`は`StartSelector`(開始契機)しか指定できない。
GetMedia - Amazon Kinesis Video Streams
https://docs.aws.amazon.com/ja_jp/kinesisvideostreams/latest/dg/API_dataplane_GetMedia.html

## 対応方法
`ListFragments` と `GetMediaForFragmentList` を併用することで対応しました。

[![Image from Gyazo](https://i.gyazo.com/a89e0f39433c4416369bdce0084724e3.png)](https://gyazo.com/a89e0f39433c4416369bdce0084724e3)

コード抜粋
```javascript:index.js
async function getMediaData(streamName, startTimestamp, endTimestamp) {

    // ListFragments用のEndpointの取得
    const kinesisvideo = new AWS.KinesisVideo({region: region});
    let listFragmentsEndParams = {
        APIName: "LIST_FRAGMENTS",
        StreamName: streamName
    };
    const listFragmentsEnd = await kinesisvideo.getDataEndpoint(listFragmentsEndParams).promise();

    const listFragmentsClient = new AWS.KinesisVideoArchivedMedia({endpoint: listFragmentsEnd.DataEndpoint, region:region});
    let listFragmentsParams = {
        FragmentSelector: {
            FragmentSelectorType: "PRODUCER_TIMESTAMP",
            TimestampRange: {
                StartTimestamp: startTimestamp,
                EndTimestamp: endTimestamp,
            }
        },
        StreamName: streamName
    };
    // ListFragmentsデータの取得
    const listFragments = await listFragmentsClient.listFragments(listFragmentsParams).promise();

    // GetMediaForFragmentList用のEndpointの取得
    let mediaEndParams = {
        APIName: "GET_MEDIA_FOR_FRAGMENT_LIST",
        StreamName: streamName
    };
    const mediaEnd = await kinesisvideo.getDataEndpoint(mediaEndParams).promise();

    const mediaClient = new AWS.KinesisVideoArchivedMedia({endpoint: mediaEnd.DataEndpoint, region:region});

    // listFragmentsで取得されるFragmentは順不同なのでProducerTimestampで昇順に並び替える
    listFragments.Fragments.sort(function(a, b){
        return (a.ProducerTimestamp > b.ProducerTimestamp ? 1 : -1);
    });
    let fragmentNumberArray = [];
    listFragments.Fragments.forEach(function(fragment) {
        fragmentNumberArray.push(fragment["FragmentNumber"]);
    });

    const mediaParams = {
        Fragments: fragmentNumberArray,
        StreamName: streamName
    };
    // GetMediaForFragmentListデータの取得
    const media = await mediaClient.getMediaForFragmentList(mediaParams).promise();

```

※参考
Amazon Kinesis Video Streams API/SDK による開発
https://qiita.com/yh1224/items/cb922f15b791e7f31677
Class: AWS.KinesisVideoArchivedMedia
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/KinesisVideoArchivedMedia.html
ListFragments - Amazon Kinesis Video Streams
https://docs.aws.amazon.com/ja_jp/kinesisvideostreams/latest/dg/API_reader_ListFragments.html 
GetMediaForFragmentList - Amazon Kinesis Video Streams
https://docs.aws.amazon.com/ja_jp/kinesisvideostreams/latest/dg/API_reader_GetMediaForFragmentList.html

### ハマった箇所
#### listFragmentsが永遠に空の状態で取得される
`GetDataEndpoint` の `APIName` が誤っていました。
`listFragments`用の`LIST_FRAGMENTS`があるのに、`GET_MEDIA`を流用していたことが原因でした。

リファレンスによると下記のようなものが返ってきてもいいはずなのに返却値はずっと`{}`
```
{
    "Fragments": [],
    "NextToken": "",
}
```
エラーも起きておらず、paramの指定の仕方が悪くてヒットしなかっただけなのか、原因特定にだいぶハマりました。

GetDataEndpoint - Amazon Kinesis Video Streams
https://docs.aws.amazon.com/ja_jp/kinesisvideostreams/latest/dg/API_GetDataEndpoint.html

#### GetMediaForFragmentListで取得した音声がぐちゃぐちゃ
下記のようにソートしてあげないと、順番がぐちゃぐちゃでした笑
```javascript:index.js
    // listFragmentsで取得されるFragmentは順不同なのでProducerTimestampで昇順に並び替える
    listFragments.Fragments.sort(function(a, b){
        return (a.ProducerTimestamp > b.ProducerTimestamp ? 1 : -1);
    });
```
[![Image from Gyazo](https://i.gyazo.com/d2213594b32d6ddb0cd0ed2132fdd57b.png)](https://gyazo.com/d2213594b32d6ddb0cd0ed2132fdd57b)