Best practices for agents

更新时间:
复制 MD 格式

This document walks through two end-to-end demos to demonstrate best practices for an agent to access the Alibaba Cloud ApsaraVideo VOD service using the llms.txt index.

Note

Ensure you have read the getting started guide for agents and understand the basic structure of llms.txt.

Note

If you have not installed a Coding Agent, refer to appendix: Coding Agent selection and self-check.

Agent workflow for using llms.txt

This section outlines the workflow and division of labor between the developer and the Agent. This provides the foundation for the two subsequent demos, where the Key Design Decisions table in each demo shows this methodology applied to a specific scenario.

Division of roles: developer vs. Agent

The following table details the division of responsibilities. The developer expresses requirements, prepares the environment, and accepts the results, while the Agent handles document retrieval, solution design, and code generation.

Phase

Developer

Agent

Requirement initiation

Describes requirements in natural language.

-

Document retrieval

-

Reads the llms.txt index to match the scenario and locate detailed documentation.

Document understanding

-

Fetches detailed documentation to extract request/response parameters, code samples, and important notes.

Solution design

Confirms or adjusts the solution.

Proposes an implementation solution based on documentation and explains the technology choices.

Code generation

-

Generates runnable code that includes error handling.

Environment configuration

Configures the AccessKey, installs dependencies, and sets up the console.

Provides commands and configuration guidance.

Execution and verification

Runs the code and confirms the results.

Explains the output and troubleshoots issues.

Agent internal workflow

The Agent processes requirements internally in four sequential phases. The output of each phase is the input for the next.

Phase 1: Read the llms.txt index

Loads the entire ~250-line index file in one operation to perform the following three tasks:

  • Parse the Quick start section to map scenarios to document paths.

  • Extract the Common mistakes to avoid section to use as strict constraints for subsequent code generation.

  • Determines the document topology, including primary modules and submodules.

Phase 2: Match scenarios and locate documentation

  • Match requirement keywords with scenario titles in the Quick start section.

  • If a scenario matches, use its corresponding document path directly.

  • If no scenario matches, scan the module index to find the one to three most relevant sub-documents.

  • Construct the URL: BASE_URL + url_encode(relative_path).

Phase 3: Fetch documents and extract information

Extract the following elements from sub-documents as input for code generation: API name, request/response parameters, code samples, QPS limits, error codes, region restrictions, and other notes.

Phase 4: Code generation

Before generating code, the Agent performs a self-check against the Common mistakes to avoid checklist:

  • If a primary account AccessKey is used, use a RAM user instead.

  • If status polling is implemented, switch to event notification.

  • If a URL is not encoded, call url_encode.

  • If a new credential is created for each request, reuse and refresh the Credential object instead.

The Agent also adds QPS backoff and asynchronous processing logic to the final code.

URL construction rules

The index file uses relative paths to reference sub-documents. The Agent must convert these to absolute URLs to fetch them.

Base URL: https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/vod/llms/
Document URL = Base URL + URL encoding(relative path, with the leading "./" removed)

Example:
  In llms.txt: [server-side upload](./媒体上传/服务端上传.md)
  Full URL: Base 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

End-to-end demos

This module provides two end-to-end demos for the server-side and client-side. The PlayURL returned by the GetPlayInfo operation links the two sides to create a complete, closed-loop workflow.

Prerequisites

Step

Location

Actions

1

Alibaba Cloud Console

Activate the ApsaraVideo VOD service

2

RAM Console

Create a RAM user, grant the AliyunVODFullAccess policy, and create an AccessKey

3

ApsaraVideo VOD Console

Configure a transcoding template group (record the TemplateGroupId)

4

ApsaraVideo VOD Console

Configure the HTTP callback URL (Configuration Management > Callback Settings)

5

Local terminal

pip install aliyun-python-sdk-core aliyun-python-sdk-vod

6

Local terminal

Set the ALIBABA_CLOUD_ACCESS_KEY_ID=xxx and ALIBABA_CLOUD_ACCESS_KEY_SECRET=xxx environment variables

Server-side demo: Batch upload, transcode, and get playback URLs

This demo shows how an Agent uses the llms.txt index to build a complete server-side workflow, including batch URL upload, automatic transcoding by specifying a transcoding template group during upload and tracking progress with event notifications, and getting playback URLs after transcoding is complete.

Prompt submitted to the agent

Please use the llms.txt document (https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/vod/llms/llms.txt) to implement a complete demo workflow in Python for ApsaraVideo VOD, including batch upload, automatic transcoding, and getting playback URLs.
Specific requirements:
1. Batch upload a set of video files (provided as a list of public URLs) to ApsaraVideo VOD.
2. Automatically trigger transcoding after the upload is complete by using a specified transcoding template group.
3. After transcoding is complete, get the playback URLs for each video definition.
4. An HTTP callback service is required to receive event notifications.

Agent execution process

Phase 1: Read llms.txt to match scenarios

The Agent matches scenarios and constraints in the index as follows:

Quick start keywords → Matched document path:
  "Batch URL upload"         → media-upload/url-based-upload.md
  "Receive async event notifications" → event-notification/event-notification_2.md
  "Get playback URL for frontend"    → api-reference/get-playback-url.md
  "Python SDK initialization"       → api-reference/initialization_2.md
  "Auto-transcode on upload"        → media-processing/workflow.md

Common mistakes matched:
  #1 Do not use a root account AccessKey  #3 Do not poll for status  #5 URL must be encoded  #6 Use QPS backoff

Phase 2: Load detailed documentation on demand

Document

Purpose

media-upload/url-based-upload.md

UploadMediaByURL parameters and limitations

event-notification/event-notification_2.md

Callback mechanism and event types

api-reference/get-playback-url.md

GetPlayInfo parameters and response structure

api-reference/initialization_2.md

Python SDK initialization method

media-processing/workflow.md

Mechanism for automatic transcoding on upload by specifying a template group

Phase 3: Extract key information

Batch upload: UploadMediaByURL
  - Asynchronous, only in China (Shanghai), max 20 URLs per request
  - URLs must be URL-encoded
  - Can specify TemplateGroupId → Automatic transcoding after upload

Playback URL: GetPlayInfo
  - Prerequisite: Video Status=Normal (after transcoding is complete)
  - Returns: PlayInfoList containing PlayURL for each definition

Event notification: HTTP POST callback
  - UploadByURLComplete    → Upload complete
  - StreamTranscodeComplete → Single definition transcoding complete (earliest playable state)
  - TranscodeComplete       → All transcoding complete
  - Callback timeout is 5 seconds, with a maximum of 3 retries

Phase 4: Generate code

The Agent generates the core code based on the information above.

Core code output by the agent

Batch upload (key logic): Reads the credential from an environment variable and enforces encoding for all URLs.

# Initialize - Read AK from environment variables, do not hardcode (Common mistake #1)
client = AcsClient(region_id='cn-shanghai', credential=AccessKeyCredential(
    os.environ['ALIBABA_CLOUD_ACCESS_KEY_ID'],
    os.environ['ALIBABA_CLOUD_ACCESS_KEY_SECRET']
))

# Batch URL upload + specify transcoding template group (auto-transcode after upload)
request = UploadMediaByURLRequest()
request.set_UploadURLs(','.join([urllib.parse.quote(url, safe='') for url in urls]))  # URLs must be encoded!
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')  # Key: Specify this to auto-transcode after upload
response = json.loads(client.do_action_with_exception(request))
# Returns a list of JobIds. Track progress via event notifications.

HTTP callback service (key logic): Immediately returns a 200 status to prevent server-side retries.

class CallbackHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        body = json.loads(self.rfile.read(int(self.headers['Content-Length'])))
        self.send_response(200)  # Must return 200 immediately; retry is triggered after 5s timeout
        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':
            # All transcodings complete -> Get playback URLs
            play_info = get_play_info(body['VideoId'])
            for stream in play_info['PlayInfoList']['PlayInfo']:
                print(f"  [{stream['Definition']}] {stream['PlayURL']}")

Get playback URL (key logic):

request = GetPlayInfoRequest()
request.set_VideoId(video_id)
request.set_Formats('mp4,m3u8')
request.set_AuthTimeout(3600)  # URL is valid for 1 hour
response = json.loads(client.do_action_with_exception(request))
# response['PlayInfoList']['PlayInfo'] → playback URLs for each definition

Execution results

Submitting batch upload:

$ 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

Subsequent steps are triggered automatically by event notifications:
  UploadByURLComplete → Auto Transcode → TranscodeComplete → GetPlayInfo

Callback service receives events:

$ python callback_server.py
Callback service started: http://0.0.0.0:8080 | Waiting for callbacks...

[UploadByURLComplete] VideoId=93ab850b**** | Status=success
  → Upload complete, waiting for auto-transcoding...

[StreamTranscodeComplete] VideoId=93ab850b**** | Status=success
  → LD definition transcoding complete (ready for playback)

[TranscodeComplete] VideoId=93ab850b**** | Status=success
  → All transcodings complete, getting playback URLs:
    [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 document retrieval visualization

Prompt: "Batch upload + transcode + playback"
  │
  ▼
┌── llms.txt (Quick start matching + Common mistakes constraints) ──┐
│  ✓ "Batch URL upload"         → media-upload/url-based-upload.md  │
│  ✓ "Event notification"       → event-notification/event-notification_2.md  │
│  ✓ "Playback URL"             → api-reference/get-playback-url.md   │
│  ✓ "Python init"              → api-reference/initialization_2.md   │
│  ✓ "Auto transcode"           → media-processing/workflow.md        │
│  ✗ #1 Don't use root AK ✗ #3 Don't poll ✗ #5 URL encode ✗ #6 QPS backoff │
└────────────────────────┬─────────────────────────────────────┘
                         │
   ┌─────────────────────┼─────────────────────┐
   ▼                     ▼                     ▼
url-based-upload.md  event-notification_2.md get-playback-url.md
- UploadMediaByURL  - Event type list       - Request/response params
- URL must be encoded - HTTP callback format    - Definition enums
- Region limit      - Timeout & retry policy - QPS=360/s
- Max 20 URLs       - 5 sec timeout         - Status=Normal
   │                     │                     │
   ▼                     ▼                     ▼
   └────► initialization_2.md (SDK client constructor) ◄────┘
                         │
                         ▼
                  workflow.md (Auto-transcode with TemplateGroupId)
                         │
                         ▼
                Generate complete runnable code

Key design decisions

This table details the key design decisions for this demo and their basis in the documentation.

Decision point

Agent's choice

Basis in documentation

Upload method

UploadMediaByURL

The documentation specifies this API for batch uploading from a list of public URLs.

Transcoding trigger

Specify TemplateGroupId during upload

The documentation states that specifying TemplateGroupId during upload triggers automatic transcoding, eliminating the need for a separate API call.

Status tracking

HTTP event callback

Common Mistake #3: Do not poll.

Timing for getting playback URL

After TranscodeComplete

The documentation states that GetPlayInfo requires the video status to be 'Normal'.

AccessKey management

environment variables

Common Mistake #1: Do not hardcode.

URL handling

quote(url, safe='')

Common Mistake #5: Must be encoded.

Region

cn-shanghai

The documentation states that URL-based uploads are only supported in the China (Shanghai) region.

Fault tolerance

Exponential backoff retry

Common Mistake #6: Backoff for throttling.

Client-side demo: Android player and live viewer

This runnable Android demo builds on the server-side workflow. It shows how to play an MP4 file with minimal code and how to integrate an interactive live streaming viewer using AUI Kits.

Prompt submitted to the agent

Based on the client-side llms index (in directories like `client-side-player/`, `client-side-upload/`, `client-side-short-video/`), implement a demo app for Android.
Specific requirements:
1. Select the minimum required dependency package to play the playback URL returned by the server-side demo's GetPlayInfo response.
2. In the same app, add another entry point to enter an interactive live streaming viewer using AUI Kits (with stream pulling, bullet screen, likes, and gifts).
3. Make the package name, license, and AppServer domain configurable to avoid hardcoding.

Agent execution process

Phase 1: Read the client-side llms index and match scenarios

Quick start matching results:
  "Start VOD playback on Android"   → client-side-player/player-atomic-android.md
  "How to choose packages/dependencies" → client-side-player/player-overview.md
  "How to integrate the license"      → client-side-player/player-common-license.md
  "Interactive live streaming viewer" → client-side-player/player-kits-aui.md
  "Error codes 4400/4013"           → client-side-player/player-common-error-codes.md

Common mistakes matched:
  ✗ Initializing the license after creating the player → Must be done in Application.onCreate
  ✗ Calling setSurface before the Surface is ready      → Must wait for surfaceCreated
  ✗ Using the full-featured package by default        → Choose the minimal package for the scenario
  ✗ Hardcoding the AppServer domain                   → Abstract to BuildConfig / gradle.properties
  ✗ Calling PrivateService.init multiple times        → Initialize only once in the Application class

Phase 2: Load detailed documentation on demand

Document

Purpose

client-side-player/player-overview.md

Package selection decision (BasicLive / UGC / InteractiveLive / Standard)

client-side-player/player-atomic-android.md

Gradle dependencies, Manifest permissions, obfuscation, and minimal playback code

client-side-player/player-common-license.md

License integration timing and troubleshooting for errors 4400/4013

client-side-player/player-kits-aui.md

AUI Kits client-side modules and AppServer collaboration model

client-side-player/player-common-error-codes.md

Quick reference for playback start failures

Phase 3: Extract key decisions

Package selection:
  Playback only          → AliVCSDK_BasicLive
  Playback + short video   → AliVCSDK_UGC
  Includes co-hosting/interactive live streaming  → AliVCSDK_InteractiveLive   ← This demo chooses this one
  Full-featured          → AliVCSDK_Standard / Premium

License:
  PrivateService.initService(ctx, licenseFile, licenseKey)
  Must be completed before the first call to createAliPlayer

AUI Kits:
  AppServer + client-side; AppServer issues Identity Token, Live Room Token, and IMS Token.
  Client: Three modules - AUILiveRoomCore, AUILiveRoomViewer, and AUIInteraction.

Execution results

Quick player integration

Project dependencies

In the project-level build.gradle file:

allprojects {
    repositories {
        google()
        mavenCentral()
        maven { url "https://maven.aliyun.com/nexus/content/repositories/releases" }
    }
}

In the app-level build.gradle file:

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'
}

Permissions and obfuscation

In the AndroidManifest.xml file:

<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" />

In the proguard-rules.pro file:

-keep class com.alivc.**{*;}
-keep class com.aliyun.**{*;}
-keep class com.cicada.**{*;}
-dontwarn com.alivc.**
-dontwarn com.aliyun.**
-dontwarn com.cicada.**

License initialization

public class SampleApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        // Place license.crt in app/src/main/assets/
        PrivateService.initService(getApplicationContext(),
                "file:///android_asset/license.crt",
                BuildConfig.LICENSE_KEY);
    }
}

Register it in the AndroidManifest.xml file:

<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>
Single video playback UI

Generate the activity_player.xml file:

<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>

Generate the SimplePlayerActivity.java file:

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();
        // Source: The playback URL (with auth_key) from the server-side demo's GetPlayInfo response
        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();
        }
    }
}

If the prepared event and the first frame event appear in the logs and the SurfaceView renders the image correctly, it means the underlying link is clear.

AUI Kits live viewer

Import AUI Kits modules

In the settings.gradle file:

include ':app',
        ':AUILiveRoomCore',
        ':AUILiveRoomViewer',
        ':AUIInteraction'

In the app-level build.gradle file:

implementation project(':AUILiveRoomCore')
implementation project(':AUILiveRoomViewer')
implementation project(':AUIInteraction')

AppServer collaboration model

The client does not directly hold sensitive credentials like the IMS Token. Instead, the AppServer issues them after authentication. A complete workflow for entering a live room is as follows:

[Android App]                                [AppServer]
   │                                              │
   │── GET /api/v1/live/viewer/token?roomId=xxx ─►│
   │                                              ├── Authenticates and issues:
   │                                              │     userId / nick / pullUrl / imsToken
   │◄──────── 200 OK + ViewerTokenInfo ───────────┤
   │
   │── LiveRoomViewerActivity.start(params) ──►(Inside AUI Kits)
   │                                              │
   │   (AliPlayer for stream pulling + IMS for bullet screen)                 │
  1. The Android app sends a GET /api/v1/live/viewer/token?roomId=xxx request to the AppServer with the login state.

  2. The AppServer verifies the login status and issues the userId, nick, pullUrl, and imsToken.

  3. After the app receives ViewerTokenInfo, it calls LiveRoomViewerActivity.start(params) to enter the live room.

  4. Internally, AUI Kits uses AliPlayer for stream pulling and connects to the Interactive Messaging Service (IMS) for bullet screen, like, and gift features.

AppServerClient (OkHttp implementation)

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;   // Any of rtmp, flv, m3u8, or artc
    public String imsToken;  // Interactive Messaging Service (IMS) Token
}
Combine the two demo entries

Generate the activity_main.xml file:

<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>

Generate the MainActivity.java file:

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);
            // In a real application, pass the playback URL from the server-side demo's GetPlayInfo response here
            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();
            }
        });
    }
}

Verification checklist

After running the demo, verify the following UI behavior and log output:

  • Click Simple Playback: The SimplePlayerActivity starts, and the video plays smoothly in the SurfaceView without a black screen.

  • Click Interactive Live Streaming Viewer: The AUI Kits LiveRoomViewerActivity is launched; AliPlayer displays the host's video feed; the bullet screen, like, and gift panels in the bottom right corner are functional.

  • Key logcat logs: The simultaneous appearance of AliPlayer: prepared, AliPlayer: first frame, and IMS: connected indicates that both stream pulling and interaction are ready.

Agent document retrieval visualization

Prompt: "Android playback + enter interactive live room"
  │
  ▼
┌── Client-side llms.txt (Quick start + Common mistakes) ────────┐
│  ✓ "Android playback"        → player-atomic-android.md         │
│  ✓ "How to choose package"   → player-overview.md               │
│  ✓ "License"                 → player-common-license.md          │
│  ✓ "AUI Kits interactive"    → player-kits-aui.md               │
│  ✓ "Error codes 4400/4013"   → player-common-error-codes.md     │
│  ✗ License must be initialized before player creation           │
│  ✗ Do not call setSurface before Surface is ready               │
│  ✗ Do not use the full-featured package by default              │
│  ✗ Do not hardcode the AppServer domain                         │
│  ✗ Call PrivateService.init only once in Application          │
└────────────────────────┬────────────────────────────────────┘
                         │
  ┌─────────────┬────────┴─────────┬─────────────┐
  ▼             ▼                  ▼             ▼
player-      player-atomic-   player-common-  player-kits-aui.md
overview.md  android.md       license.md      - AppServer collab
- Package matrix - gradle deps    - PrivateService - Three token types
- Scenario map - Manifest perms - 4400/4013     - Module breakdown
- SDK size     - Playback code  - Package/BundleID - 4-step client integration
  └─────────────┴────────┬─────────┴─────────────┘
                         │
                         ▼
              player-common-error-codes.md
              (Guidance for 4400/4013/4034/4036)
                         │
                         ▼
              Generate complete runnable Android Demo

Key design decisions

Decision point

Agent's choice

Basis in documentation

SDK bundle

AliVCSDK_InteractiveLive

The selection matrix in player-overview.md recommends this bundle for interactive live streaming scenarios.

License timing

Application.onCreate

player-common-license.md: Must be called before createAliPlayer.

Surface binding

setSurface within the surfaceCreated callback

player-atomic-android.md standard template.

AppServer domain

BuildConfig + gradle.properties

Common Mistake: Do not hardcode.

Server-side integration

Reuse the playback URL from the server-side demo's GetPlayInfo response

This ensures end-to-end consistency between the server-side and client-side demos.

Interactive live viewer

AUI Kits three modules (Core / Viewer / Interaction)

player-kits-aui.md: Main client-side integration guide.

Error handling

setOnErrorListener is a general callback for event reporting. Codes 4400, 4013, 4034, and 4036 are reserved for custom client-side handling.

player-common-error-codes.md

Pre-launch checklist (Android)

Dimension

Check item

License

Test and production licenses have been applied for and configured in their respective BuildConfigs.

Package size

abiFilters are limited to armv7 + arm64; the minimal SDK bundle is selected based on the scenario.

Playback start

Listeners for 4400/4013/4034/4036 are implemented for monitoring.

Live room

Automatic renewal for expired IMS Tokens is implemented; AppServer has HTTPS and CORS enabled.

Privacy

Permissions for audio recording, network access, and media reading are all declared in the privacy policy.

Canary release

First, release the new version to 5% of users. Monitor the first-frame time and failure rates for 48 hours before a full rollout.

Monitoring

Enable Alibaba Cloud's player quality monitoring service (QoS) and single-point tracing.

Final directory structure

AliPlayerSampleApp/
├── app/
│   ├── src/main/
│   │   ├── assets/license.crt
│   │   ├── java/com/example/aliplayer/sample/
│   │   │   ├── SampleApp.java
│   │   │   ├── MainActivity.java
│   │   │   ├── SimplePlayerActivity.java         ← Simple Playback
│   │   │   ├── AppServerClient.java              ← Interactive Live Streaming
│   │   │   ├── ViewerTokenInfo.java
│   │   │   └── LoginManager.java
│   │   └── AndroidManifest.xml
│   └── build.gradle
├── AUILiveRoomCore/                               ← Interactive Live Streaming
├── AUILiveRoomViewer/                             ← Interactive Live Streaming
├── AUIInteraction/                                ← Interactive Live Streaming
├── gradle.properties (APP_SERVER_HOST / LICENSE_KEY)
├── settings.gradle
└── build.gradle

The server-side and client-side demos now form a complete, closed-loop workflow. This workflow, generated by the Agent using the llms.txt index, covers video uploading, automatic transcoding, playback URL retrieval, basic video playback, and an interactive live streaming viewer using AUI Kits.

Appendix: Agent selection and self-check

This appendix is for developers who have not used or installed a Coding Agent.

When to use a Coding Agent

Use case

Is it needed?

You want the AI to read llms.txt, expand sub-documents as needed, and generate a runnable project.

Yes

You only want to read this document to understand the architecture, then copy and manually modify the code.

No. A web browser is sufficient.

You want the AI to directly add, delete, and modify files in your local repository and run commands to validate the changes.

Yes

Recommended options

Category

Name

Type

Description

Recommended

Qoder / Cursor / Claude Code

Desktop IDE or CLI

Natively supports long-document retrieval using llms.txt-style indexes and works well with SDKs from Chinese cloud providers. We used Qoder to verify the demos in this document.

Other

-

-

Any Coding Agent that can access URLs, write local files, and call shell commands.

Note

Use the installation links from the official product website. Avoid using third-party images.

Minimum self-check

Send the following prompt to your Coding Agent to verify its capabilities:

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"?

Expected Answers:

  • Correctly lists the top-level modules from llms.txt.

  • Provides a list of sub-documents that is largely consistent with the on-demand loading table in the server-side demo (media upload, event notification, get audio/video playback URL, Python SDK initialization, media processing workflow).

  • Clearly states: Do not use an Alibaba Cloud account AccessKey. Use a RAM user instead.

An agent that passes all three checks is ready for the demos in this document.