このドキュメントでは、2つのエンドツーエンドのデモを通じて、エージェントがllms.txt インデックスを使用して Alibaba Cloud ApsaraVideo VOD サービスにアクセスするためのベストプラクティスを説明します。
エージェントの入門ガイドをお読みいただき、 llms.txt の基本構造を理解しておいてください。
コーディングエージェントをインストールしていない場合は、「付録:コーディングエージェントの選定とセルフチェック」をご参照ください。
llms.txt を利用したエージェントのワークフロー
このセクションでは、開発者とエージェント間のワークフローと役割分担について概説します。これは、以降の 2 つのデモの基盤となります。各デモの Key Design Decisions テーブルでは、この方法論が特定のシナリオに適用される様子を示しています。
役割分担:開発者とエージェント
次の表に、役割分担の詳細を示します。開発者は要件を記述し、環境を準備し、結果を受け入れます。一方、エージェントはドキュメント検索、ソリューション設計、コード生成を担当します。
フェーズ | 開発者 | エージェント |
要件の起案 | 自然言語で要件を記述します。 | - |
ドキュメント検索 | - |
|
ドキュメントの理解 | - | 詳細なドキュメントをフェッチして、リクエスト/レスポンス パラメーター、サンプルコード、重要な注意点を抽出します。 |
ソリューション設計 | ソリューションを確認または調整します。 | ドキュメントに基づいて実装ソリューションを提案し、技術的な選択肢を説明します。 |
コード生成 | - | エラーハンドリングを含む実行可能なコードを生成します。 |
環境設定 | AccessKey を設定し、依存関係をインストールし、コンソールをセットアップします。 | コマンドと設定ガイダンスを提供します。 |
実行と検証 | コードを実行し、結果を確認します。 | 出力を説明し、問題をトラブルシューティングします。 |
エージェントの内部ワークフロー
エージェントは、4 つの連続したフェーズで要件を内部的に処理します。各フェーズの出力が、次のフェーズの入力になります。
フェーズ 1:llms.txt インデックスの読み取り
約 250 行のインデックスファイル全体を 1 回の操作でロードし、次の 3 つのタスクを実行します。
クイックスタートセクションを解析して、シナリオをドキュメントパスにマッピングします。Common mistakes to avoidセクションを抽出し、以降のコード生成における厳密な制約として使用します。プライマリモジュールとサブモジュールを含むドキュメントのトポロジを決定します。
フェーズ 2:シナリオの照合とドキュメントの特定
クイックスタートセクションのシナリオタイトルと要件のキーワードを照合します。シナリオが一致する場合、対応するドキュメントパスを直接使用します。
一致するシナリオがない場合、モジュールインデックスをスキャンして、最も関連性の高いサブドキュメントを 1~3 個特定します。
相対パスの先頭にある
./を削除してから URL エンコードを行い、ベース URL と連結して完全な URL を構築します。
フェーズ 3:ドキュメントのフェッチと情報の抽出
サブドキュメントから、コード生成の入力として次の要素を抽出します:API 名、リクエスト/レスポンス パラメーター、サンプルコード、QPS 制限、エラーコード、リージョン制限、その他の注意点。
フェーズ 4:コード生成
コードを生成する前に、エージェントは Common mistakes to avoid チェックリストに照らしてセルフチェックを実行します。
プライマリアカウントの AccessKey が使用されている場合は、代わりに RAM ユーザーを使用します。
ステータス ポーリングが実装されている場合は、イベント通知に切り替えます。
URL がエンコードされていない場合は、
url_encodeを呼び出します。リクエストごとに新しい認証情報が作成される場合は、代わりに
Credentialオブジェクトを再利用および更新します。
エージェントはまた、最終的なコードに QPS バックオフと非同期処理のロジックを追加します。
URL の構築ルール
インデックスファイルは、サブドキュメントの参照に相対パスを使用します。エージェントは、サブドキュメントをフェッチするために、これらのパスを絶対 URL に変換する必要があります。
ベース URL:https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/vod/llms/
ドキュメント URL = ベース URL + URL エンコーディング (相対パス、先頭の "./" は削除)
例:
llms.txt 内:[サーバーサイドアップロード](./媒体上传/服务端上传.md)
完全な URL:ベース URL + %E5%AA%92%E4%BD%93%E4%B8%8A%E4%BC%A0/%E6%9C%8D%E5%8A%A1%E7%AB%AF%E4%B8%8A%E4%BC%A0.mdエンドツーエンドデモ
このモジュールは、サーバー側とクライアント側の 2 つのエンドツーエンドデモを提供します。 GetPlayInfo オペレーションが返す PlayURL によって両側が結び付けられ、完全なクローズドループのワークフローが構築されます。
前提条件
ステップ | 場所 | アクション |
1 | Alibaba Cloud コンソール | ApsaraVideo VOD サービスを有効化します。 |
2 | RAM コンソール | RAM ユーザーを作成し、 |
3 | ApsaraVideo VOD コンソール | トランスコードテンプレートグループを設定します (TemplateGroupId を記録してください)。 |
4 | ApsaraVideo VOD コンソール | HTTP コールバック URL を設定します ([設定管理] > [コールバック設定])。 |
5 | ローカルターミナル |
|
6 | ローカルターミナル | 環境変数 |
サーバー側のデモ:バッチアップロード、トランスコード、再生 URL の取得
このデモでは、エージェントが llms.txt インデックスを使用して、 URL のバッチアップロード、 アップロード時にトランスコーディングテンプレートグループを指定した自動トランスコードと、イベント通知による進捗追跡、および トランスコード完了後の再生 URL の取得 を含む、完全なサーバー側のワークフローを構築する方法を説明します。
エージェントに送信されたプロンプト
llms.txt ドキュメント (https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/vod/llms/llms.txt) を使用して、バッチアップロード、自動トランスコード、再生 URL の取得を含む ApsaraVideo VOD の完全なデモワークフローを Python で実装してください。
具体的な要quirements:
1. ビデオファイルのセット (公開 URL のリストとして提供) を ApsaraVideo VOD にバッチアップロードすること。
2. 指定されたトランスコーディングテンプレートグループを使用して、アップロード完了後に自動的にトランスコードをトリガーすること。
3. トランスコードが完了したら、各画質の再生 URL を取得すること。
4. イベント通知を受信するために、HTTP コールバックサービスが必要です。エージェントの実行プロセス
フェーズ 1:llms.txt を読み取り、シナリオを照合
エージェントは、次のようにインデックス内のシナリオと制約を照合します:
クイックスタートのキーワード → 一致したドキュメントパス:
「URL のバッチアップロード」 → media-upload/url-based-upload.md
「非同期イベント通知の受信」 → event-notification/event-notification_2.md
「フロントエンド用の再生 URL の取得」 → api-reference/get-playback-url.md
「Python SDK の初期化」 → api-reference/initialization_2.md
「アップロード時の自動トランスコード」 → media-processing/workflow.md
一致した一般的な間違い:
#1 ルートアカウントの AccessKey を使用しない
#3 ステータスをポーリングしない
#5 URL はエンコードする必要がある
#6 QPS バックオフを使用するフェーズ 2:詳細ドキュメントをオンデマンドでロード
ドキュメント | 目的 |
| UploadMediaByURL のパラメーターと制限 |
| コールバックメカニズムとイベントタイプ |
| GetPlayInfo のパラメーターとレスポンス構造 |
| Python SDK の初期化メソッド |
| テンプレートグループを指定した、アップロード時の自動トランスコードのメカニズム |
フェーズ 3:キー情報の抽出
バッチアップロード: UploadMediaByURL
- 非同期、China (Shanghai) のみ、リクエストあたり最大 20 URL
- URL は URL エンコードが必要
- TemplateGroupId を指定可能 → アップロード後に自動トランスコード
再生 URL: GetPlayInfo
- 前提条件:ビデオステータス = 正常 (トランスコード完了後)
- 戻り値:各画質の PlayURL を含む PlayInfoList
イベント通知: HTTP POST コールバック
- UploadByURLComplete → アップロード完了
- StreamTranscodeComplete → 単一画質のトランスコード完了 (再生可能な最も早い状態)
- TranscodeComplete → 全てのトランスコード完了
- コールバックのタイムアウトは 5 秒、最大 3 回のリトライフェーズ 4:コード生成
エージェントは上記の情報に基づいてコアコードを生成します。
エージェントが出力したコアコード
バッチアップロード (主要ロジック): 環境変数から認証情報を読み取り、すべての URL のエンコードを強制します。
# 初期化 - AK を環境変数から読み取り、ハードコードしない (一般的な間違い #1)
client = AcsClient(region_id='cn-shanghai', credential=AccessKeyCredential(
os.environ['ALIBABA_CLOUD_ACCESS_KEY_ID'],
os.environ['ALIBABA_CLOUD_ACCESS_KEY_SECRET']
))
# URL のバッチアップロード + トランスコーディングテンプレートグループの指定 (アップロード後の自動トランスコード)
request = UploadMediaByURLRequest()
request.set_UploadURLs(','.join([urllib.parse.quote(url, safe='') for url in urls])) # URL はエンコードが必要!
request.set_UploadMetadatas(json.dumps([{'SourceURL': urllib.parse.quote(u, safe=''), 'Title': f'Video{i}'} for i, u in enumerate(urls)]))
request.set_TemplateGroupId('your_template_group_id') # キー:これを指定してアップロード後に自動トランスコード
response = json.loads(client.do_action_with_exception(request))
# JobId のリストを返します。イベント通知を介して進捗を追跡します。HTTP コールバックサービス (主要ロジック): サーバー側でのリトライを防ぐために、即座に 200 ステータスを返します。
class CallbackHandler(BaseHTTPRequestHandler):
def do_POST(self):
body = json.loads(self.rfile.read(int(self.headers['Content-Length'])))
self.send_response(200) # 即座に 200 を返す必要があります。5 秒のタイムアウト後にリトライがトリガーされます
self.end_headers()
if body['EventType'] == 'UploadByURLComplete' and body['Status'] == 'success':
print(f"Upload complete: VideoId={body['VideoId']}, waiting for transcoding...")
elif body['EventType'] == 'TranscodeComplete' and body['Status'] == 'success':
# すべてのトランスコードが完了 -> 再生 URL を取得
play_info = get_play_info(body['VideoId'])
for stream in play_info['PlayInfoList']['PlayInfo']:
print(f" [{stream['Definition']}] {stream['PlayURL']}")再生 URL の取得 (主要ロジック):
request = GetPlayInfoRequest()
request.set_VideoId(video_id)
request.set_Formats('mp4,m3u8')
request.set_AuthTimeout(3600) # URL は 1 時間有効
response = json.loads(client.do_action_with_exception(request))
# response['PlayInfoList']['PlayInfo'] → 各画質の再生 URL実行結果
バッチアップロードの送信:
$ python vod_batch_upload.py
[1/2] Initializing VOD client... ✓ (region: cn-shanghai)
[2/2] Submitting batch upload job (3 videos)... ✓
RequestId: 25818875-5F78-4AF6-D7393642CA58****
JobId: ad90a501b1b9**** ← sample1.mp4
JobId: bd81b612c2c8**** ← sample2.mp4
JobId: ce72c723d3d9**** ← sample3.mp4
以降のステップは、イベント通知によって自動的にトリガーされます:
UploadByURLComplete → Auto Transcode → TranscodeComplete → GetPlayInfoコールバックサービスがイベントを受信:
$ python callback_server.py
Callback service started: http://0.0.0.0:8080 | Waiting for callbacks...
[UploadByURLComplete] VideoId=93ab850b**** | Status=success
→ アップロード完了、自動トランスコードを待機中...
[StreamTranscodeComplete] VideoId=93ab850b**** | Status=success
→ LD 画質のトランスコード完了 (再生準備完了)
[TranscodeComplete] VideoId=93ab850b**** | Status=success
→ すべてのトランスコード完了、再生 URL を取得中:
[LD] https://vod.example.com/****/sample1-ld.mp4?auth_key=****
[SD] https://vod.example.com/****/sample1-sd.mp4?auth_key=****
[HD] https://vod.example.com/****/sample1-hd.mp4?auth_key=****エージェントのドキュメント検索の可視化
プロンプト:「バッチアップロード + トランスコード + 再生」
│
▼
┌── llms.txt (クイックスタートのマッチング + 一般的な間違いの制約) ──┐
│ ✓ 「URL のバッチアップロード」 → media-upload/url-based-upload.md │
│ ✓ 「イベント通知」 → event-notification/event-notification_2.md │
│ ✓ 「再生 URL」 → api-reference/get-playback-url.md │
│ ✓ 「Python 初期化」 → api-reference/initialization_2.md │
│ ✓ 「自動トランスコード」 → media-processing/workflow.md │
│ ✗ #1 ルート AK を使用しない ✗ #3 ポーリングしない ✗ #5 URL エンコード ✗ #6 QPS バックオフ │
└────────────────────────┬─────────────────────────────────────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
url-based-upload.md event-notification_2.md get-playback-url.md
- UploadMediaByURL - イベントタイプ一覧 - リクエスト/レスポンスパラメーター
- URL はエンコードが必要 - HTTP コールバック形式 - 画質列挙型
- リージョン制限 - タイムアウトとリトライポリシー - ステータス=正常
- 最大 20 URL - 5 秒タイムアウト - QPS=360/s
│ │ │
▼ ▼ ▼
└────► initialization_2.md (SDK クライアントコンストラクタ) ◄────┘
│
▼
workflow.md (TemplateGroupId での自動トランスコード)
│
▼
完全な実行可能コードを生成主要な設計判断
この表は、このデモにおける主要な設計判断と、そのドキュメント上の根拠を詳述したものです。
判断項目 | エージェントの選択 | ドキュメント上の根拠 |
アップロード方法 | UploadMediaByURL | ドキュメントでは、公開 URL のリストからバッチアップロードするためにこの API を指定しています。 |
トランスコードトリガー | アップロード時に TemplateGroupId を指定 | ドキュメントには、アップロード時に |
ステータス追跡 | HTTP イベントコールバック | 一般的な間違い #3: ポーリングしない。 |
再生 URL 取得のタイミング | TranscodeComplete 後 | ドキュメントには、 |
AccessKey 管理 | 環境変数 | 一般的な間違い #1: ハードコードしない。 |
URL の処理 |
| 一般的な間違い #5: エンコードが必要。 |
リージョン | cn-shanghai | ドキュメントには、URL ベースのアップロードは China (Shanghai) リージョンでのみサポートされていると記載されています。 |
フォールトトレランス | エクスポネンシャルバックオフによるリトライ | 一般的な間違い #6: スロットリングに対するバックオフ。 |
クライアント側のデモ:Android プレーヤーとライブビューワー
この実行可能な Android デモは、サーバー側のワークフローを基盤としています。最小限のコードで MP4 ファイルを再生する方法と、AUI Kits を使用してインタラクティブライブストリーミングビューワーを統合する方法を示します。
エージェントに送信されたプロンプト
クライアント側の llms インデックス (`client-side-player/`、`client-side-upload/`、`client-side-short-video/` などのディレクトリ内) に基づいて、Android 向けのデモアプリを実装してください。
具体的な要件:
1. サーバー側のデモの GetPlayInfo レスポンスで返される再生 URL を再生するために、必要最小限の依存パッケージを選択します。
2. 同じアプリに、AUI Kits を使用してインタラクティブライブストリーミングビューワー (ストリームプル、弾幕、いいね、ギフトを含む) を起動するための別のエントリーポイントを追加します。
3. ハードコーディングを避けるために、パッケージ名、ライセンス、AppServer ドメインを設定可能にします。エージェントの実行プロセス
フェーズ 1:クライアント側の llms インデックスを読み取り、シナリオを照合
クイックスタートのマッチング結果:
「Android で VOD 再生を開始」 → client-side-player/player-atomic-android.md
「パッケージ / 依存関係の選択方法」 → client-side-player/player-overview.md
「ライセンスの統合方法」 → client-side-player/player-common-license.md
「インタラクティブライブストリーミングビューワー」 → client-side-player/player-kits-aui.md
「エラーコード 4400 / 4013」 → client-side-player/player-common-error-codes.md
一致した一般的な間違い:
✗ プレーヤー作成後にライセンスを初期化する → Application.onCreate で行う必要があります
✗ Surface の準備が整う前に setSurface を呼び出す → surfaceCreated を待つ必要があります
✗ デフォルトでフル機能のパッケージを使用する → シナリオに合わせた最小パッケージを選択する
✗ AppServer ドメインをハードコーディングする → BuildConfig / gradle.properties に抽象化する
✗ PrivateService.init を複数回呼び出す → Application クラスで一度だけ初期化するフェーズ 2:詳細ドキュメントをオンデマンドでロード
ドキュメント | 目的 |
| パッケージ選択の決定 (BasicLive / UGC / InteractiveLive / Standard) |
| Gradle の依存関係、マニフェストのパーミッション、難読化、および最小限の再生コード |
| ライセンス統合のタイミングとエラー 4400 / 4013 のトラブルシューティング |
| AUI Kits のクライアント側モジュールと AppServer の連携モデル |
| 再生開始の失敗に関するクイックリファレンス |
フェーズ 3:主要な決定事項の抽出
パッケージの選択:
再生のみ → AliVCSDK_BasicLive
再生 + ショートビデオ → AliVCSDK_UGC
共同ホスティング / インタラクティブライブストリーミングを含む → AliVCSDK_InteractiveLive ← このデモではこれを選択
フル機能 → AliVCSDK_Standard / Premium
ライセンス:
PrivateService.initService(ctx, licenseFile, licenseKey)
createAliPlayer を最初に呼び出す前に完了している必要があります
AUI Kits:
AppServer + クライアント側。AppServer は Identity Token、Live Room Token、IMS Token を発行します。
クライアント:3 つのモジュール - AUILiveRoomCore、AUILiveRoomViewer、AUIInteraction。実行結果
クイックプレーヤー統合
プロジェクトの依存関係
プロジェクトレベルの build.gradle ファイルに:
allprojects {
repositories {
google()
mavenCentral()
maven { url "https://maven.aliyun.com/nexus/content/repositories/releases" }
}
}アプリレベルの build.gradle ファイルに:
android {
compileSdk 34
defaultConfig {
applicationId "com.example.aliplayer.sample"
minSdk 21
targetSdk 34
ndk { abiFilters "armeabi-v7a", "arm64-v8a" }
buildConfigField "String", "APP_SERVER_HOST",
"\"${project.findProperty('APP_SERVER_HOST') ?: 'https://your-appserver.example.com'}\""
buildConfigField "String", "APP_SERVER_API_PREFIX", "\"/api/v1/live/\""
buildConfigField "String", "LICENSE_KEY",
"\"${project.findProperty('LICENSE_KEY') ?: ''}\""
}
buildFeatures { buildConfig true }
}
dependencies {
implementation 'com.aliyun.aio:AliVCSDK_InteractiveLive:6.0.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.google.code.gson:gson:2.10.1'
}パーミッションと難読化
AndroidManifest.xml ファイルに:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />proguard-rules.pro ファイルに:
-keep class com.alivc.**{*;}
-keep class com.aliyun.**{*;}
-keep class com.cicada.**{*;}
-dontwarn com.alivc.**
-dontwarn com.aliyun.**
-dontwarn com.cicada.**ライセンスの初期化
public class SampleApp extends Application {
@Override
public void onCreate() {
super.onCreate();
// license.crt を app/src/main/assets/ に配置します
PrivateService.initService(getApplicationContext(),
"file:///android_asset/license.crt",
BuildConfig.LICENSE_KEY);
}
}AndroidManifest.xml ファイルに登録します:
<application
android:name=".SampleApp"
android:label="@string/app_name"
android:theme="@style/Theme.MaterialComponents.DayNight">
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SimplePlayerActivity" />
</application>単一ビデオ再生 UI
activity_player.xml ファイルを生成します:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000">
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="220dp"
android:layout_gravity="center" />
</FrameLayout>SimplePlayerActivity.java ファイルを生成します:
public class SimplePlayerActivity extends AppCompatActivity {
public static final String EXTRA_PLAY_URL = "play_url";
private AliPlayer aliPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_player);
SurfaceView surfaceView = findViewById(R.id.surfaceView);
aliPlayer = AliPlayerFactory.createAliPlayer(getApplicationContext());
surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override public void surfaceCreated(SurfaceHolder h) {
aliPlayer.setSurface(h.getSurface());
}
@Override public void surfaceChanged(SurfaceHolder h, int f, int w, int hh) {
aliPlayer.surfaceChanged();
}
@Override public void surfaceDestroyed(SurfaceHolder h) {
aliPlayer.setSurface(null);
}
});
aliPlayer.setOnPreparedListener(() -> aliPlayer.start());
aliPlayer.setOnErrorListener(err ->
Log.e("AliPlayer", "code=" + err.getCode() + " msg=" + err.getMsg()));
UrlSource source = new UrlSource();
// ソース:サーバー側のデモにある GetPlayInfo レスポンスで返される再生 URL (auth_key 付き)
String playUrl = getIntent().getStringExtra(EXTRA_PLAY_URL);
source.setUri(playUrl != null ? playUrl : "https://your.cdn/test.mp4");
aliPlayer.setDataSource(source);
aliPlayer.setAutoPlay(true);
aliPlayer.prepare();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (aliPlayer != null) {
aliPlayer.stop();
aliPlayer.release();
}
}
}ログに prepared イベントと最初のフレームイベントが表示され、SurfaceView が画像を正しくレンダリングしている場合は、下層のリンクに問題がないことを意味します。
AUI Kits ライブビューワー
AUI Kits モジュールのインポート
settings.gradle ファイルに:
include ':app',
':AUILiveRoomCore',
':AUILiveRoomViewer',
':AUIInteraction'アプリレベルの build.gradle ファイルに:
implementation project(':AUILiveRoomCore')
implementation project(':AUILiveRoomViewer')
implementation project(':AUIInteraction')AppServer 連携モデル
クライアントは IMS Token のような機密性の高い認証情報を直接保持しません。代わりに、認証後に AppServer がそれらを発行します。ライブルームに入室するための完全なワークフローは次のとおりです:
[Android アプリ] [AppServer]
│ │
│── GET /api/v1/live/viewer/token?roomId=xxx ─►│
│ ├── 認証して発行:
│ │ userId / nick / pullUrl / imsToken
│◄──────── 200 OK + ViewerTokenInfo ───────────┤
│
│── LiveRoomViewerActivity.start(params) ──►(AUI Kits 内部)
│ │
│ (ストリームプル用の AliPlayer + 弾幕用の IMS) │Android アプリは、ログイン状態で Appサーバーに
GET /api/v1/live/viewer/token?roomId=xxxリクエストを送信します。AppServer はログインステータスを検証し、
userId、nick、pullUrl、およびimsTokenを発行します。アプリは
ViewerTokenInfoを受信した後、LiveRoomViewerActivity.start(params)を呼び出してライブ ルームに入ります。内部的に、AUI Kits はストリームプルに AliPlayer を使用し、弾幕、いいね、ギフトなどの機能のために Interactive Messaging Service (IMS) に接続します。
AppServerClient (OkHttp 実装)
public class AppServerClient {
public interface Callback {
void onSuccess(ViewerTokenInfo info);
void onFail(String msg);
}
private static final OkHttpClient client = new OkHttpClient();
public static void fetchViewerToken(String roomId, Callback cb) {
HttpUrl url = HttpUrl.parse(
BuildConfig.APP_SERVER_HOST + BuildConfig.APP_SERVER_API_PREFIX + "viewer/token")
.newBuilder()
.addQueryParameter("roomId", roomId)
.build();
Request req = new Request.Builder()
.url(url)
.header("Authorization", "Bearer " + LoginManager.getToken())
.build();
client.newCall(req).enqueue(new okhttp3.Callback() {
@Override public void onFailure(Call call, IOException e) {
cb.onFail(e.getMessage());
}
@Override public void onResponse(Call call, Response resp) throws IOException {
if (!resp.isSuccessful() || resp.body() == null) {
cb.onFail("HTTP " + resp.code());
return;
}
ViewerTokenInfo info = new Gson().fromJson(
resp.body().string(), ViewerTokenInfo.class);
cb.onSuccess(info);
}
});
}
}
public class ViewerTokenInfo {
public String roomId;
public String userId;
public String nick;
public String pullUrl; // rtmp、flv、m3u8、または artc のいずれか
public String imsToken; // Interactive Messaging Service (IMS) トークン
}2 つのデモのエントリーを組み合わせる
activity_main.xml ファイルを生成します:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:padding="24dp"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:id="@+id/btnSimple"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Simple Playback" />
<Button android:id="@+id/btnLiveRoom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Interactive Live Viewer" />
</LinearLayout>MainActivity.java ファイルを生成します:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btnSimple).setOnClickListener(v -> {
Intent intent = new Intent(this, SimplePlayerActivity.class);
// 実際のアプリケーションでは、サーバー側のデモにある GetPlayInfo レスポンスで返される再生 URL をここに渡します
intent.putExtra(SimplePlayerActivity.EXTRA_PLAY_URL,
"https://vod.example.com/****/sample1-hd.mp4?auth_key=****");
startActivity(intent);
});
findViewById(R.id.btnLiveRoom).setOnClickListener(v -> enterRoom("demo_room_001"));
}
private void enterRoom(String roomId) {
AppServerClient.fetchViewerToken(roomId, new AppServerClient.Callback() {
@Override public void onSuccess(ViewerTokenInfo info) {
LiveRoomEnterParams params = new LiveRoomEnterParams.Builder()
.roomId(info.roomId)
.userId(info.userId)
.userNick(info.nick)
.pullUrl(info.pullUrl)
.imsToken(info.imsToken)
.build();
LiveRoomViewerActivity.start(MainActivity.this, params);
}
@Override public void onFail(String msg) {
Toast.makeText(MainActivity.this, "Failed to enter room: " + msg, Toast.LENGTH_LONG).show();
}
});
}
}検証チェックリスト
デモを実行した後、次の UI の動作とログ出力を確認します:
[シンプル再生] をクリック:
SimplePlayerActivityが起動し、ビデオがSurfaceView内で黒画面にならずにスムーズに再生されます。[インタラクティブライブ配信ビューワー] をクリックすると、AUI Kits の
LiveRoomViewerActivityが起動して AliPlayer にホストの映像が表示され、右下隅にある弾幕、いいね、ギフトパネルが機能します。主要な logcat ログ:
AliPlayer: prepared、AliPlayer: first frame、およびIMS: connectedが同時に表示されることは、ストリームのプルとインタラクションの両方の準備が完了したことを示します。
エージェントのドキュメント検索の可視化
プロンプト:「Android 再生 + インタラクティブライブルームへの入室」
│
▼
┌── クライアント側 llms.txt (クイックスタート + 一般的な間違い) ────────┐
│ ✓ 「Android 再生」 → player-atomic-android.md │
│ ✓ 「パッケージの選択方法」 → player-overview.md │
│ ✓ 「ライセンス」 → player-common-license.md │
│ ✓ 「AUI Kits インタラクティブ」 → player-kits-aui.md │
│ ✓ 「エラーコード 4400 / 4013」 → player-common-error-codes.md │
│ ✗ プレーヤー作成前にライセンスを初期化する必要がある │
│ ✗ Surface の準備が整う前に setSurface を呼び出さない │
│ ✗ デフォルトでフル機能のパッケージを使用しない │
│ ✗ AppServer ドメインをハードコーディングしない │
│ ✗ PrivateService.init は Application で一度だけ呼び出す │
└────────────────────────┬────────────────────────────────────┘
│
┌─────────────┬────────┴─────────┬─────────────┐
▼ ▼ ▼ ▼
player- player-atomic- player-common- player-kits-aui.md
overview.md android.md license.md - AppServer 連携
- パッケージマトリックス - gradle 依存関係 - PrivateService - 3つのトークンタイプ
- シナリオマップ - Manifest パーミッション - 4400 / 4013 - モジュール内訳
- SDK サイズ - 再生コード - パッケージ / BundleID - 4ステップのクライアント統合
└─────────────┴────────┬─────────┴─────────────┘
│
▼
player-common-error-codes.md
(4400 / 4013 / 4034 / 4036 のガイダンス)
│
▼
完全な実行可能な Android デモを生成主要な設計判断
判断項目 | エージェントの選択 | ドキュメントにおける根拠 |
SDK バンドル | AliVCSDK_InteractiveLive |
|
ライセンスのタイミング | Application.onCreate | player-common-license.md: |
サーフェスのバインド | surfaceCreated コールバック内の |
|
AppServer ドメイン | BuildConfig / gradle.properties | 一般的な間違い:ハードコードしない。 |
サーバー側統合 | サーバー側のデモにある GetPlayInfo レスポンスで返される再生 URL を再利用 | これにより、サーバー側とクライアント側のデモ間のエンドツーエンドの一貫性が保証されます。 |
インタラクティブライブビューワー | AUI Kits の 3 つのモジュール (Core / Viewer / Interaction) | player-kits-aui.md:主要なクライアント側統合ガイド。 |
エラーハンドリング |
|
|
リリース前チェックリスト (Android)
観点 | チェック項目 |
ライセンス | テスト用および本番用のライセンスが申請され、それぞれの BuildConfigs で設定されています。 |
パッケージサイズ |
|
再生開始 | 4400、4013、4034、4036 のリスナーがモニタリング用に実装されています。 |
ライブルーム | 期限切れの IMS トークンの自動更新が実装されており、AppServer で HTTPS と CORS が有効になっています。 |
プライバシー | 音声録音、ネットワークアクセス、メディア読み取りのパーミッションはすべてプライバシーポリシーで宣言されています。 |
カナリアリリース | 最初に、新しいバージョンをユーザーの 5% にリリースします。全面展開の前に、ファーストフレーム時間と障害率を 48 時間監視します。 |
Monitoring | Enable Alibaba Cloud's player quality monitoring service (QoS) and single-point tracing. |
最終的なディレクトリ構造
AliPlayerSampleApp/
├── app/
│ ├── src/main/
│ │ ├── assets/license.crt
│ │ ├── java/com/example/aliplayer/sample/
│ │ │ ├── SampleApp.java
│ │ │ ├── MainActivity.java
│ │ │ ├── SimplePlayerActivity.java ← シンプル再生
│ │ │ ├── AppServerClient.java ← インタラクティブライブストリーミング
│ │ │ ├── ViewerTokenInfo.java
│ │ │ └── LoginManager.java
│ │ └── AndroidManifest.xml
│ └── build.gradle
├── AUILiveRoomCore/ ← インタラクティブライブストリーミング
├── AUILiveRoomViewer/ ← インタラクティブライブストリーミング
├── AUIInteraction/ ← インタラクティブライブストリーミング
├── gradle.properties (APP_SERVER_HOST / LICENSE_KEY)
├── settings.gradle
└── build.gradleサーバーサイドとクライアントサイドのデモで、完全なクローズドループのワークフローが構成されるようになりました。このワークフローは、エージェントが llms.txt インデックスを使用して生成するもので、動画のアップロード、自動トランスコーディング、再生 URL の取得、基本的な動画再生、AUI Kits を使用したインタラクティブなライブストリーミングビューワーを網羅しています。
付録: エージェントの選定とセルフチェック
この付録は、コーディングエージェントを使用したことがない、またはインストールしていない開発者向けです。
コーディングエージェントを使用するタイミング
ユースケース | 必要か |
AI に | はい |
このドキュメントを読んでアーキテクチャを理解し、その後はコードをコピーして手動で修正するだけでよい場合。 | いいえ。Web ブラウザーで十分です。 |
AI にローカルリポジトリ内のファイルを直接追加、削除、変更させ、変更内容を検証するためにシェルコマンドを実行させたい場合。 | はい |
推奨オプション
カテゴリ | 名称 | タイプ | 説明 |
推奨 | Qoder / Cursor / Claude Code | デスクトップ IDE または CLI |
|
その他 | - | - | URL にアクセスでき、ローカルファイルを書き込み、シェルコマンドを呼び出せる任意のコーディングエージェント。 |
インストールは、公式プロダクト Web サイトのリンクを使用してください。サードパーティ製イメージの使用は避けてください。
最小限のセルフチェック
次のプロンプトをコーディングエージェントに送信し、機能を検証します:
Please read https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/vod/llms/llms.txt,
and tell me:
1. How many top-level modules are in this index? What are they?
2. Which sub-documents are involved in the "batch upload + automatic transcoding + get audio/video playback URL" workflow? Please list their relative paths.
3. In the "Common mistakes to avoid" section, what are the notes related to "AccessKey"?想定される回答:
llms.txtに記載されているトップレベルのモジュールを正しく列挙できること。サーバーサイドデモの オンデマンド読み込み 表 (バッチアップロード、イベント通知、音声/動画再生 URL の取得、Python SDK の初期化、メディア処理ワークフロー) と概ね整合するサブドキュメントのリストを提示できること。
Do not use an Alibaba Cloud account AccessKey. Use a RAM user instead. と明確に述べられること。
上記 3 つのチェックをすべて通過したエージェントは、このドキュメントのデモを実行する準備ができています。