本文通過兩個端到端 Demo 鏈路,示範 Agent 基於 llms.txt 索引接入阿里雲ApsaraVideo for VOD(VOD)服務的可複用最佳實務。
請確保已閱讀面向智能體的入門指南並理解 llms.txt 的基本結構。如果首次接觸,請先閱讀該指南。
如果尚未安裝任何Coding Agent,請先閱讀本文末尾附錄:Coding Agent選型與自檢完成準備。
Agent 使用 llms.txt 的完整工作流程
本模組定義 Agent 與開發人員的通用分工與工作流程,是後續兩個 Demo 實踐的方法論基礎。Demo實踐 中的關鍵設計決策表為該方法論在具體情境下的落地,可對照閱讀。
角色分工:開發人員 vs Agent
人與 Agent 的分工邊界如下表。開發人員負責需求表達、環境準備與結果驗收,Agent 負責文檔檢索、方案設計與代碼產生。
階段 | 開發人員 | Agent |
需求發起 | 用自然語言描述需求 | - |
文檔檢索 | - | 讀取 |
文檔理解 | - | 擷取詳細文檔,提取 API 參數、樣本、注意事項 |
方案設計 | 確認或調整方案 | 基於文檔提出實現方案,說明技術選型理由 |
代碼產生 | - | 產生可運行代碼,含錯誤處理 |
環境配置 | 配置 AccessKey、安裝依賴、控制台設定 | 提供命令和配置指引 |
執行驗證 | 運行代碼,確認結果 | 解釋輸出,排查問題 |
Agent 內部工作流程
Agent 在收到需求後,內部按以下四個階段處理。每個階段的產出會作為下一階段的輸入。
階段 1:讀取 llms.txt 索引
一次性載入約 250 行索引檔案,完成以下三件事:
解析
Quick start章節,建立情境到文檔路徑映射。提取
Common mistakes to avoid,作為後續代碼產生的硬性約束。理解文檔拓撲結構(一級模組、子模組)。
階段 2:情境匹配與文檔定位
用需求關鍵詞匹配
Quick start的情境標題。命中情境:直接取對應文檔路徑。
未命中情境:掃描模組索引,找出最相關的 1~3 個子文檔。
構造 URL: BASE_URL + url_encode(相對路徑)。
階段 3:擷取詳細文檔並提取實現資訊
從子文檔中提取以下要素,作為代碼產生的輸入:API 名稱、請求/返回參數、程式碼範例、QPS 限制、錯誤碼、地區限制、注意事項。
階段 4:代碼產生
在產生代碼前對照 Common mistakes 清單逐條自檢:
使用了主帳號 AccessKey?則改為 RAM 子帳號。
寫了輪詢狀態?則改為事件通知。
URL 未編碼?則調用
url_encode。每次請求重建憑證?則複用
Credential並 Refresh。
並為最終代碼補充 QPS 退避與非同步處理邏輯。
URL 構造規則
索引檔案以相對路徑引用子文檔,Agent 在拉取子文檔前需把相對路徑拼成絕對 URL:
基礎 URL:https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/vod/llms/
文檔 URL = 基礎 URL + URL 編碼(相對路徑,去掉前置 "./")
樣本:
llms.txt 中:[服務端上傳](./媒體上傳/服務端上傳.md)
完整 URL:基礎 URL + 媒體上傳/服務端上傳.md端到端實戰Demo
本模組給出兩個端到端 Demo,覆蓋服務端與用戶端兩側,二者通過 GetPlayInfo 返回的 PlayURL 形成完整閉環。
運行前置條件(人工操作)
步驟 | 位置 | 操作 |
1 | 阿里雲控制台 | 開通ApsaraVideo for VOD服務 |
2 | RAM 控制台 | 建立子帳號 + 授予 |
3 | 點播控制台 | 配置轉碼模板組(記錄 TemplateGroupId) |
4 | 點播控制台 | 配置 HTTP 回調地址(組態管理 > 回調設定) |
5 | 本地終端 |
|
6 | 本地終端 |
|
服務端 Demo:批量上傳 + 自動轉碼 + 擷取播放地址
本 Demo 示範 Agent 如何基於 llms.txt 索引完成批量 URL上傳、上傳時指定轉碼模板組自動轉碼、通過事件回調感知進度、轉碼完成後擷取播放地址的完整服務端鏈路。
提交給Agent的Prompt
請基於llms.txt文檔(https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/vod/llms/llms.txt),用 Python 實現ApsaraVideo for VOD批量上傳、自動轉碼以及擷取播放地址的完整流程Demo。
具體需求為:
1.將一批視頻檔案(通過公網 URL 列表提供)批量上傳到阿里雲點播。
2.上傳完成後自動觸發轉碼(使用指定的轉碼模板組)。
3.轉碼完成後擷取各清晰度的播放地址。
4.需要一個HTTP回調服務接收事件通知。Agent的執行過程
階段 1:讀取 llms.txt 完成情境匹配
Agent 在索引中匹配到的情境與命中的約束如下:
Quick start 關鍵詞 → 命中的文檔路徑:
"URL列表批量上傳" → 媒體上傳/URL拉取上傳.md
"接收非同步事件通知" → 事件通知/事件通知_2.md
"擷取播放地址給前端" → API參考/擷取音視頻播放地址.md
"Python SDK 初始化" → API參考/初始化_2.md
"上傳時自動轉碼" → ApsaraVideo for Media Processing/工作流程.md
Common mistakes 命中的約束:
#1 不用主帳號 AK #3 不輪詢狀態 #5 URL 需編碼 #6 QPS 退避階段 2:按需載入詳細文檔
文檔 | 擷取目的 |
| UploadMediaByURL 參數和限制 |
| 回調機制、事件類型 |
| GetPlayInfo 參數和返回結構 |
| Python SDK 初始化方式 |
| 上傳時指定模板組自動轉碼機制 |
階段 3:提取關鍵資訊
批量上傳:UploadMediaByURL
- 非同步執行,僅華東 2(上海),單次最多 20 個 URL
- URL 必須 URLEncode
- 可指定 TemplateGroupId → 上傳完成後自動轉碼
播放地址:GetPlayInfo
- 前提:視頻 Status=Normal(轉碼完成後)
- 返回:PlayInfoList 含各清晰度 PlayURL
事件通知:HTTP POST 回調
- UploadByURLComplete → 上傳完成
- StreamTranscodeComplete → 單清晰度轉碼完成(最早可播放)
- TranscodeComplete → 全部轉碼完成
- 回調逾時 5 秒,最多重試 3 次階段 4:產生代碼
Agent 基於以上資訊產生核心代碼。
Agent輸出的核心代碼
批量上傳(關鍵邏輯):從環境變數讀取憑證,並對所有 URL 強製做編碼:
# 初始化 - AK從環境變數讀取,不寫入程式碼(Common mistakes #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'視頻{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"上傳完成: VideoId={body['VideoId']},等待轉碼...")
elif body['EventType'] == 'TranscodeComplete' and body['Status'] == 'success':
# 全部轉碼完成 → 擷取播放地址
play_info = get_play_info(body['VideoId'])
for stream in play_info['PlayInfoList']['PlayInfo']:
print(f" [{stream['Definition']}] {stream['PlayURL']}")擷取播放地址(關鍵邏輯):
request = GetPlayInfoRequest()
request.set_VideoId(video_id)
request.set_Formats('mp4,m3u8')
request.set_AuthTimeout(3600) # 地址有效期間 1 小時
response = json.loads(client.do_action_with_exception(request))
# response['PlayInfoList']['PlayInfo'] → 各清晰度的 PlayURL執行結果
提交批量上傳:
$ python vod_batch_upload.py
[1/2] 初始化VOD用戶端... ✓ (region: cn-shanghai)
[2/2] 提交批量上傳任務 (3個視頻)... ✓
RequestId: 25818875-5F78-4AF6-D7393642CA58****
JobId: ad90a501b1b9**** ← sample1.mp4
JobId: bd81b612c2c8**** ← sample2.mp4
JobId: ce72c723d3d9**** ← sample3.mp4
後續流程通過事件通知自動觸發:
UploadByURLComplete → 自動轉碼 → TranscodeComplete → GetPlayInfo回調服務接收事件:
$ python callback_server.py
回調服務啟動: http://0.0.0.0:8080 | 等待回調...
[UploadByURLComplete] VideoId=93ab850b**** | Status=success
→ 上傳完成,等待自動轉碼...
[StreamTranscodeComplete] VideoId=93ab850b**** | Status=success
→ LD清晰度轉碼完成(已可播放)
[TranscodeComplete] VideoId=93ab850b**** | Status=success
→ 全部轉碼完成,擷取播放地址:
[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=****Agent文檔檢索可視化
Prompt: "批量上傳+轉碼+播放"
│
▼
┌── llms.txt (Quick start 匹配 + Common mistakes 約束) ────────┐
│ ✓ "URL 批量上傳" → 媒體上傳/URL拉取上傳.md │
│ ✓ "事件通知" → 事件通知/事件通知_2.md │
│ ✓ "播放地址" → API參考/擷取音視頻播放地址.md │
│ ✓ "Python 初始化" → API參考/初始化_2.md │
│ ✓ "自動轉碼" → ApsaraVideo for Media Processing/工作流程.md │
│ ✗ #1 不用主帳號 ✗ #3 不輪詢 ✗ #5 URL編碼 ✗ #6 QPS退避 │
└────────────────────────┬─────────────────────────────────────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
URL拉取上傳.md 事件通知_2.md 擷取音視頻播放地址.md
- UploadMediaByURL - 事件類型列表 - 請求/返回參數
- URL必須編碼 - HTTP回調格式 - 清晰度枚舉
- 地區限制 - 逾時&重試策略 - QPS=360/s
- 最多20個URL - 5秒逾時 - Status=Normal
│ │ │
▼ ▼ ▼
└────► 初始化_2.md(SDK 用戶端構造) ◄────┘
│
▼
工作流程.md(TemplateGroupId 自動轉碼)
│
▼
產生完整可運行代碼關鍵設計決策(Agent 依據文檔做出)
下表為本Demo的具體決策追溯,與角色分工對應:角色分工說明誰做什麼,本表說明針對本情境,為什麼這樣選。
決策點 | Agent 的選擇 | 文檔依據 |
上傳方式 | UploadMediaByURL | 需求是 URL 列表,文檔明確此 API 適用公網 URL 情境 |
轉碼觸發 | 上傳時指定 TemplateGroupId | 文檔:設定後上傳完自動轉碼,無需額外 API 呼叫 |
狀態感知 | HTTP 事件回調 | Common Mistakes 第 3 條:不要輪詢 |
播放地址時機 | TranscodeComplete 後 | 文檔:Status=Normal 才能 GetPlayInfo |
AccessKey 管理 | 環境變數 | Common Mistakes 第 1 條:不寫入程式碼 |
URL 處理 |
| Common Mistakes 第 5 條:必須編碼 |
地區 | cn-shanghai | URL 拉取僅支援華東2(上海) |
容錯 | 指數退避重試 | Common Mistakes 第 6 條:Throttling 退避 |
用戶端Demo:Android播放器與互動直播觀眾端
在打通服務端鏈路的基礎上,本模組以Android平台為例提供用戶端可運行 Demo:涵蓋“20行代碼起播 MP4”到“基於AUI Kits接入互動直播觀眾端”。同時示範了Agent如何利用用戶端llms.txt索引按情境定位文檔並產生代碼。
提交給Agent的Prompt
請基於用戶端llms索引(端側播放器/、端側上傳/、端側短視頻/ 等目錄),用 Android 實現一個 Demo App。
具體需求為:
1.選取最小依賴包,播放服務端Demo GetPlayInfo 返回的播放地址。
2.在同一個 App 中再加一個入口,使用 AUI Kits 進入互動直播觀眾端(拉流播放 + 彈幕 + 點贊 + 禮物)。
3.包名、License、AppServer 網域名稱均做成可配置,避免寫入程式碼。Agent的執行過程
階段 1:讀取用戶端 llms 索引,情境匹配
Quick start 匹配結果:
"Android 起播 VOD" → 端側播放器/player-atomic-android.md
"包/依賴怎麼選" → 端側播放器/player-overview.md
"License 怎麼接" → 端側播放器/player-common-license.md
"互動直播觀眾端" → 端側播放器/player-kits-aui.md
"錯誤碼 4400/4013" → 端側播放器/player-common-error-codes.md
Common mistakes 命中的約束:
✗ License 在播放器建立之後才 init → 必須在 Application.onCreate
✗ Surface 還沒 ready 就 setSurface → 必須等 surfaceCreated
✗ 直接拿全功能包 → 按情境選最小包
✗ AppServer 網域名稱寫入程式碼 → 抽到 BuildConfig / gradle.properties
✗ 同一直播間多次 PrivateService.init → 僅在 SampleApp 裡 init 一次階段 2:按需載入詳細文檔
文檔 | 擷取目的 |
| 選包決策(BasicLive / UGC / InteractiveLive / Standard) |
| gradle 依賴、Manifest 許可權、混淆、最小起播代碼 |
| License 接入時機與 4400/4013 錯誤排查 |
| AUI Kits 用戶端模組、AppServer 協作模型 |
| 起播失敗錯誤碼速查 |
階段 3:提取關鍵決策
選包:
僅播放 → AliVCSDK_BasicLive
播放 + 短視頻 → AliVCSDK_UGC
含連麥/互動直播 → AliVCSDK_InteractiveLive ← 本 Demo 選它
全功能 → AliVCSDK_Standard / Premium
License:
PrivateService.initService(ctx, licenseFile, licenseKey)
必須在第一次 createAliPlayer 之前完成
AUI Kits:
AppServer + 用戶端雙側;AppServer 頒發 身份Token / 直播間Token / IMS Token
用戶端:AUILiveRoomCore + AUILiveRoomViewer + AUIInteraction 三個 module執行結果
播放器快速整合
工程依賴
在專案級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.**License初始化
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>單視頻起播介面
產生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();
// 來源:2.1 節 GetPlayInfo 返回的 PlayURL(已帶 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 App] [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 攜帶登入態向 AppServer 發起
GET /api/v1/live/viewer/token?roomId=xxx。AppServer 校正登入態後,頒發
userId/nick/pullUrl/imsToken。App 收到
ViewerTokenInfo後,調用LiveRoomViewerActivity.start(params)進入直播間。AUI Kits 內部用 AliPlayer 拉流,並通過 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; // 互動訊息 SDK Token
}兩個Demo入口合一
產生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="單視頻起播" />
<Button android:id="@+id/btnLiveRoom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="互動直播觀眾端" />
</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);
// 實際使用時把 2.1 節 GetPlayInfo 的 PlayURL 透傳過來
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, "進入失敗:" + msg, Toast.LENGTH_LONG).show();
}
});
}
}驗證清單
完整 Demo 跑通後預期看到的介面與日誌:
點擊單視頻起播:進入
SimplePlayerActivity,黑色背景上的 220dpSurfaceView正常出畫,畫面流暢、無黑屏。點擊互動直播觀眾端:AUI Kits 的
LiveRoomViewerActivity被拉起;AliPlayer 顯示主播畫面;右下方彈幕、點贊、禮物面板均可正常使用。logcat 關鍵日誌:
AliPlayer: prepared、AliPlayer: first frame與IMS: connected三條同時出現,代表拉流互動兩側均就緒。
Agent 文檔檢索可視化
Prompt: "Android 起播 + 進入互動直播間"
│
▼
┌── 用戶端llms.txt(Quick start + Common mistakes) ───────────┐
│ ✓ "Android 起播" → player-atomic-android.md │
│ ✓ "包怎麼選" → player-overview.md │
│ ✓ "License" → player-common-license.md │
│ ✓ "AUI Kits 互動直播" → player-kits-aui.md │
│ ✓ "錯誤碼 4400/4013" → player-common-error-codes.md │
│ ✗ License 必須在播放器建立前 init │
│ ✗ Surface 未 ready 不可 setSurface │
│ ✗ 不要直接拿全功能包 │
│ ✗ AppServer 網域名稱不要寫入程式碼 │
│ ✗ PrivateService.init 僅在 Application 內一次 │
└────────────────────────┬────────────────────────────────────┘
│
┌─────────────┬────────┴─────────┬─────────────┐
▼ ▼ ▼ ▼
player- player-atomic- player-common- player-kits-aui.md
overview.md android.md license.md - AppServer 協作
- 選包矩陣 - gradle 依賴 - PrivateService - 三類 Token
- 情境對應 - Manifest 許可權 - 4400/4013 - 模組拆分
- SDK 體積 - 起播代碼 - 包名/BundleID - 用戶端 4 步整合
└─────────────┴────────┬─────────┴─────────────┘
│
▼
player-common-error-codes.md
(4400/4013/4034/4036 處理指引)
│
▼
產生完整可運行 Android Demo關鍵設計決策(Agent依據文檔做出)
決策點 | Agent 的選擇 | 文檔依據 |
SDK 組合包 | AliVCSDK_InteractiveLive | player-overview.md 選包矩陣:含連麥/互動直播 |
License 時機 | Application.onCreate | player-common-license.md:必須早於 createAliPlayer |
Surface 綁定 | surfaceCreated回調內 setSurface | player-atomic-android.md 標準模板 |
AppServer 網域名稱 | BuildConfig + gradle.properties | Common Mistake:不寫入程式碼 |
與服務端聯動 | 複用服務端Demo GetPlayInfo 返回的PlayURL | 端到端一致性 |
互動直播觀眾端 | AUI Kits 三模組(Core / Viewer / Interaction) | player-kits-aui.md:用戶端整合主線 |
錯誤處理 |
| player-common-error-codes.md |
上線前 Checklist(Android)
維度 | 檢查項 |
License | 測試與生產 License 已分別申請並填入對應 BuildConfig |
包體 | abiFilters 限制為 armv7 + arm64;按情境選擇最小 SDK 組合包 |
起播 | 監聽 4400/4013/4034/4036 並打點 |
直播間 | IMS Token 到期自動續簽;AppServer 已開啟 HTTPS 與 CORS |
隱私 | 錄音、網路、媒體讀取許可權均於隱私協議明示 |
灰階 | 新版本先 5% 灰階,48 小時觀察首幀與失敗率再放量 |
監控 | 啟用阿里雲播放品質服務(QoS)與單點追查 |
最終目錄結構
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至此,服務端Demo與用戶端Demo 構成完整閉環:涵蓋上傳、自動轉碼、擷取播放地址、單視頻起播及AUI Kits 互動直播觀眾端,全流程均由 Agent 基於 llms.txt 索引按需檢索文檔產生。
附錄:Coding Agent 選型與自檢
本附錄面向尚未使用或安裝 Coding Agent的開發人員。
何時需要Coding Agent
使用情境 | 是否需要 |
想讓 AI 直接讀 llms.txt、按需要展開子文檔、產生可運行專案工程 | 需要 |
僅想看本文檔瞭解架構、複製代碼後人工修改 | 不需要,瀏覽器即可 |
想讓 AI 在本地倉庫裡直接增刪改檔案、運行命令進行驗證 | 需要 |
推薦選項
分類 | 名稱 | 形態 | 說明 |
推薦 | Qoder / Cursor / Claude Code | 案頭 IDE 或 CLI | 原生支援 llms.txt 類長文檔檢索,對國內雲廠商 SDK 適配較好;本文 Demo 基於 Qoder 驗證。 |
其他 | - | - | 任何能連網讀取 URL、能在本地寫檔案、能調用 Shell 的 Coding Agent 均可。 |
安裝連結以產品官網為準,避免使用第三方鏡像。
安裝後的最小自檢
將以下Prompt發送給安裝好的Coding Agent,對照預期答案驗證能力:
請幫我讀取 https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/vod/llms/llms.txt,
告訴我:
1.這份索引一共有幾個一級模組?分別是什嗎?
2."批量上傳 + 自動轉碼 + 擷取播放地址"這條鏈路涉及哪幾個子文檔?請列出相對路徑。
3.Common mistakes to avoid 中和 "AccessKey" 相關的有哪幾條?預期答案:
正確列出llms.txt 的一級模組清單。
給出與服務端Demo中按需載入詳細文檔表大致一致的子文檔清單(媒體上傳、事件通知、擷取音視頻播放地址、Python SDK 初始化、ApsaraVideo for Media Processing工作流程)。
明確指出不要使用主帳號 AccessKey,應使用 RAM 子帳號。
如果三項都通過,則Coding Agent 已具備運行本文 Demo 的能力。