本文通过两个端到端 Demo 链路,演示 Agent 基于 llms.txt 索引接入阿里云视频点播(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 | 阿里云控制台 | 开通视频点播服务 |
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 实现视频点播批量上传、自动转码以及获取播放地址的完整流程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
"上传时自动转码" → 媒体处理/工作流.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 │
│ ✓ "自动转码" → 媒体处理/工作流.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 初始化、媒体处理工作流)。
明确指出不要使用主账号 AccessKey,应使用 RAM 子账号。
如果三项都通过,则Coding Agent 已具备运行本文 Demo 的能力。