ソラカメのLIVE映像をWebブラウザで再生するページをつくってみた

# 概要
ソラカメで撮影した映像をブラウザで再生するページを作成してみた。
いろいろと注意して実装する必要があるので本記事にまとめる。

[ソラカメとは](https://soracom.jp/sora_cam/)

# ブラウザでLIVE映像を再生させるためには
再生までにやらないといけないこと。

1. SORACOM APIで認証情報を取得する
[SORACOM APIリファレンス-認証](https://users.soracom.io/ja-jp/tools/api/reference/#/Auth/auth)

2. SORACOM APIを使用して視聴URLを取得する
[SORACOM APIリファレンス-視聴URL取得](https://users.soracom.io/ja-jp/tools/api/reference/#/SoraCam/getSoraCamDeviceStreamingVideo)

3. 視聴URLを動画プレイヤーに設定する

# やってみた
以下、試行錯誤。

## 下準備
### SAMユーザーの作成
SORACOM APIを実行するために下記を実施

1. ソラコム管理画面からSAMユーザーを作成
[SAMユーザーの作成](https://users.soracom.io/ja-jp/docs/sam/create-sam-user/)

2. 作成したSAMユーザーの権限設定で「OAuth2:authorize」「SoraCam:getSoraCamDeviceStreamingVideo」を追加
[SAMユーザーの権限追加](https://users.soracom.io/ja-jp/docs/sam/set-permissions/)

```
{
  "statements": [
    {
      "api": [
        "SoraCam:getSoraCamDeviceStreamingVideo",
        "OAuth2:authorize"
      ],
      "effect": "allow"
    }
  ]
}
```

## JavaScriptで実装してみた
まずはお手軽にJavaScriptで。

### JavaScriptのコード
下記関数を作成。
```
// 認証情報を取得する
async function authenticate() {
  const url = 'https://api.soracom.io/v1/auth';

  try {
    const response = await axios.post(url, {
      authKeyId: 'secret-XXXX',
      authKey: 'keyId-XXXX'
    }, {
      headers: {
        'Content-Type': 'application/json'
      }
    });

    console.log("認証成功:", response.data);

    // 認証 API のレスポンスに適切なキーがあるか確認
    if (!response.data.apiKey || !response.data.token) {
      throw new Error("APIキーまたはトークンがレスポンスに含まれていません");
    }

    return response.data;

  } catch (error) {
    console.error("認証エラー:", error);
    return null; // 認証失敗時は null を返す
  }
}

// 視聴URLを取得する
async function getMpd(api_key, api_token) {
  const url = 'https://api.soracom.io/v1/sora_cam/devices/[device-id]/images/exports';

  try {
    const response = await axios.get(url, {
      headers: {
        'Content-Type': 'application/json',
        'X-Soracom-API-Key': api_key,
        'X-Soracom-Token': api_token
      }
    });

    console.log("MPD取得成功:", response.data);

    // MPD の URL を適切に取得する（レスポンス構造に合わせて変更）
    if (!response.data || !response.data.mpd_url) {
      throw new Error("MPD URLがレスポンスに含まれていません");
    }

    return response.data.mpd_url; // MPD URL を返す

  } catch (error) {
    console.error("MPD取得エラー:", error);
    return null; // エラー時は null を返す
  }
}

// 動画プレイヤーに視聴URLを設定する
async function setSrc() {
  let data = await authenticate(); // 認証を実行

  if (!data) {
    console.error("認証情報の取得に失敗しました");
    return;
  }

  let mpdUrl = await getMpd(data.apiKey, data.token);

  if (!mpdUrl) {
    console.error("MPDデータの取得に失敗しました");
    return;
  }

  const myPlayer = videojs("example-video");
  myPlayer.src({ type: 'application/dash+xml', src: mpdUrl });
  console.log("動画ソースを設定:", mpdUrl);
}

setSrc();

```

### JavaScriptでの問題点
CORSのエラーとなってしまいSORACOM APIの実行ができない。

## SORACOM APIの実行処理をAWSのAPI Gatewey+Lambdaへ移植

### lambda(python)に移植したコード

```
import json
import os
import urllib.request

# 環境変数の取得
AUTH_KEY_ID = os.environ['authKeyId']
AUTH_KEY = os.environ['authKey']
DEVICE_ID = os.environ['device_id']

def lambda_handler(event, context):
    try:
        # 1. 認証してAPIキーとトークンを取得する
        auth_response = authenticate()
        api_key = auth_response['apiKey']
        api_token = auth_response['token']

        print("1:OK")

        # 2. 視聴URLを返却する
        export_url = get_mpd_url(api_key, api_token)

        print("2:OK")

        return {
            'statusCode': 200,
            'body': json.dumps({'url': export_url})
        }
    
    except Exception as e:
        return {
            'statusCode': 500,
            'body': json.dumps(f'Error: {str(e)}')
        }

# 認証を行い、APIキーとトークンを取得
def authenticate():
    url = 'https://api.soracom.io/v1/auth'
    headers = {
        'Content-Type': 'application/json'
    }
    data = json.dumps({
        'authKeyId': AUTH_KEY_ID,
        'authKey': AUTH_KEY
    }).encode('utf-8')

    req = urllib.request.Request(url, headers=headers, data=data, method='POST')
    with urllib.request.urlopen(req) as response:
        response_code = response.getcode()
        if response_code != 200:
            raise Exception(f'Authentication failed: {response_code}')
        return json.loads(response.read().decode('utf-8'))

# 視聴URLを返却する
def get_mpd_url(api_key, api_token):
    url = f'https://api.soracom.io/v1/sora_cam/devices/{DEVICE_ID}/stream'
    headers = {
        'Content-Type': 'application/json',
        'X-Soracom-API-Key': api_key,
        'X-Soracom-Token': api_token
    }

    req = urllib.request.Request(url, headers=headers, method='GET')
    with urllib.request.urlopen(req) as response:
        response_code = response.getcode()
        if response_code != 200:
            raise Exception(f'Image export request failed: {response_code}')
        export_response = json.loads(response.read().decode('utf-8'))
        print(export_response)
        return export_response["playList"][0]["url"]

```

※lambdaの環境変数にソラコムの管理画面から発行した認証キーID(authKeyId)、認証キーシークレット(authKey)を設定しておく
[認証キー/認証キーシークレットの取得](https://blog.soracom.com/ja-jp/2023/02/13/soracom-api-tips/)

※lambdaの環境変数にソラカメの管理画面より取得したデバイスID(device_id)を設定しておく
[デバイスIDの取得](https://users.soracom.io/ja-jp/docs/soracom-cloud-camera-services/check-device-information-on-console/)

[実装の参考記事-ソラカメで撮影した画像をwordpressで作成したwebサイトにスライドバーで時刻を選んで表示する！](https://zenn.dev/takao2704/articles/soracamimagetos3towp)

### API Gatewey(REST API) とLambdaを繋げる
別記事参照。
[リンク準備中]()

作成したREST API
https://hoge.execute-api.ap-northeast-1.amazonaws.com/dev/stream

### htmlページの用意
動画プレイヤーはvideo.jsを使用
[video.jsとは](https://videojs.com/)

index.htmlとしてS3に配置

```
<!DOCTYPE html>
<html>
  <head>
    <title>LIVE TEST</title>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <link href="https://vjs.zencdn.net/8.16.1/video-js.css" rel="stylesheet" />
    <script src="https://vjs.zencdn.net/8.16.1/videojs-contrib-dash/videojs-dash.js"></script>
  </head>

  <body>
    <div>
      <video-js id=example-video width=640 height=360
                class="vjs-default-skin" controls>
      </video-js>
    </div>
    <script type="text/javascript" src="https://vjs.zencdn.net/8.16.1/video.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/videojs-contrib-dash/5.1.1/videojs-dash.min.js"></script>
    <script>
      async function getData() {
        try {
          const response = await fetch("https://hoge.execute-api.ap-northeast-1.amazonaws.com/dev/stream");

          if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status}`);
          }

          const data = await response.json();
          console.log(JSON.parse(data["body"]).url); // 取得したデータを表示
          return JSON.parse(data["body"]).url;
        } catch (error) {
          console.error("Error:", error);
        }
      }

      async function setupPlayer() {
        const myPlayer = videojs("example-video");

        try {
          const data = await getData(); // 非同期関数の結果を取得
          console.log(data);
          myPlayer.src({ type: 'application/dash+xml', src: data });
        } catch (error) {
          console.error("Failed to set video source:", error);
        }
      }

      setupPlayer();
    </script>
  </body>
</html>
```

### まとめ
* SAMの設定を忘れずに
* JavaScriptだとSORACOM APIを呼び出すときにCORSエラーとなるため、APIを呼ぶ場合は中間サーバーの用意が必用
* ソラカメの視聴URLはMPEG-Dashという配信方式なので一部環境で再生できないので注意
   windows Chrome : OK
   mac Safari : NG
   Android Chrome : OK
   iOS Sarari : NG