全部產品
Search
文件中心

ApsaraVideo VOD:策略系統

更新時間:Sep 10, 2026

本文介紹AliPlayerKit策略系統的設計背景、核心概念、內建策略及使用方法。

設計背景

策略系統的設計源於對AUI Kits規模化落地中業務定製與架構演化矛盾的思考。

作為面向客戶的低代碼方案,AUI Kits需支援不同業務方對UI及核心流程的定製。若商務邏輯直接嵌入架構代碼,將造成高度耦合:架構升級時易引發代碼衝突、商務邏輯失效,甚至阻礙版本迭代。

本質上,此類邏輯屬於客戶業務需求而非架構能力。缺乏合理承載機制將導致業務差異侵入架構,威脅系統穩定性與可演化性。

策略系統為此而生:

  • 將業務差異抽象為獨立擴充的策略單元。

  • 解耦商務邏輯與架構核心。

  • 業務方可在策略層擴充,避免侵入架構。

該機制使業務能力沉澱為系統化、可複用的體系,形成 可迭代、可複用、可擴充 的能力基礎,為AliPlayerKit長期演化提供架構支撐。

概念介紹

什麼是策略系統?

策略系統(Strategy System)是統一管理原則組件的架構機制,負責策略的註冊機制、生命週期管理、上下文注入及事件訂閱,並在播放器運行中統一管理原則的建立、啟動、暫停、重設與銷毀。
基於該機制,開發人員可按需啟用/禁用策略、快速實現自訂策略,或完全替換預設策略以滿足業務定製需求。

什麼是策略?

策略(Strategy)是播放器商務邏輯的封裝單元,實現特定業務能力或自訂邏輯(如首幀統計、卡頓檢測、流量保護、記憶播放)。
核心特徵:

  • 單一職責:聚焦獨立功能目標。

  • 事件驅動:通過事件匯流排響應播放器狀態。

  • 獨立隔離:策略間互不干擾。

通過可插拔可組合的設計,非核心能力被抽象為策略單元,保持播放器核心簡潔,同時提供靈活擴充能力。

功能特性

解決問題

策略系統需解決的痛點:

  1. 維護困難:商務邏輯分散,難維護複用。

  2. 配置缺失:不同情境需不同邏輯組合,缺乏靈活配置機制。

  3. 源碼入侵:定製邏輯需修改架構源碼。

  4. 執行個體幹擾:多播放器情境下商務邏輯易串台。

核心價值

策略系統抽離並封裝播放器的監控與分析邏輯,支援客戶按需自主配置與組合。

使用方式

說明

優勢

使用預設策略

播放器組件使用官方預設策略。

簡化接入流程,開箱即用。

自訂策略

實現特定業務需求的策略。

滿足各類定製化業務需求。

不使用原則

僅使用播放能力。

純播放情境,無邏輯依賴。

架構優勢:

  • 解耦:策略與播放器核心邏輯分離,職責清晰。

  • 複用:策略可在不同播放器執行個體間複用。

  • 可擴充:無需修改架構即可自訂策略。

  • 隔離:策略間相互獨立,互不影響。

核心能力

能力

說明

事件驅動

策略通過事件匯流排訂閱播放器事件,響應式執行。

生命週期管理

統一管理原則的啟動、停止、重設、銷毀。

上下文注入

策略可通過上下文訪問播放器狀態和資料。

原廠模式

支援全域註冊策略工廠,自動應用於所有播放器執行個體。

內建策略

AliPlayerKit 提供了三個開箱即用的內建策略。

首幀耗時策略(FirstFrameStrategy)

統計播放器從準備階段(PREPARING)到首幀渲染完成的耗時,細分三個階段:

階段

說明

準備階段

PREPARING → Prepared

渲染階段

Prepared → FirstFrameRendered

總耗時

PREPARING → FirstFrameRendered

使用情境:效能監控、使用者體驗最佳化、播放器調優。

卡頓檢測策略(StutterDetectStrategy)

監控播放過程中的卡頓狀態,統計卡頓情況:

指標

說明

卡頓次數

卡頓發生的次數。

卡頓時間長度

每次卡頓的期間。

卡頓總時間長度

整個播放會話的卡頓時間長度總和。

有效播放時間長度

實際觀看時間長度(不含暫停和卡頓)。

使用情境:播放品質監控、使用者體驗分析。

流量保護原則(TrafficProtectionStrategy)

監聽網路狀態變化,在特定情境下提醒使用者:

情境

行為

WiFi轉換為移動網路

提示使用者正在使用流量播放。

起播時為移動網路。

提示使用者當前使用流量。

用情境:流量敏感情境、使用者關懷。

基礎使用

策略系統提供三種使用原則,開發人員可根據需求選擇合適的方式:

策略

說明

適用情境

使用預設策略

最簡單的使用方式,播放器組件自動註冊預設策略。

快速整合、標準播放情境。

自訂部分策略

只註冊特定策略,其他不使用。

局部定製、按需啟用。

完全自訂策略

自訂所有策略,建立完全個人化的監控體系。

深度定製、特定業務需求。

策略一:使用內建策略

最簡單的使用方式,播放器組件將自動註冊預設策略:

// 1. 建立播放器控制器(自動註冊預設策略)
AliPlayerController controller = new AliPlayerController(this);

// 2. 準備播放資料
AliPlayerModel model = new AliPlayerModel.Builder()
        .videoSource(videoSource)
        .build();

// 3. 配置資料模型
controller.configure(model);

// 4. 綁定到視圖
playerView.attach(controller);

預設註冊的策略包括:

  • FirstFrameStrategy(首幀耗時)。

  • StutterDetectStrategy(卡頓檢測)。

  • TrafficProtectionStrategy(流量保護)。

策略二:自訂策略

只註冊需要的策略,其他不啟用:

// 1. 建立播放器控制器
AliPlayerController controller = new AliPlayerController(this);

// 2. 擷取策略管理器並清除預設策略
StrategyManager strategyManager = controller.getStrategyManager();

// 3. 只註冊需要的策略
strategyManager.register(new FirstFrameStrategy());
strategyManager.register(new StutterDetectStrategy());

// 4. 準備播放資料
AliPlayerModel model = new AliPlayerModel.Builder()
        .videoSource(videoSource)
        .build();
controller.configure(model);
playerView.attach(controller);

策略三:完全自訂策略

實現自訂策略,建立完全個人化的監控體系:

// 1. 建立播放器控制器
AliPlayerController controller = new AliPlayerController(this);

// 2. 擷取策略管理器
StrategyManager strategyManager = controller.getStrategyManager();

// 3. 註冊自訂策略
strategyManager.register(new MyCustomStrategy());
strategyManager.register(new MyAnalyticsStrategy());

// 4. 準備播放資料
AliPlayerModel model = new AliPlayerModel.Builder()
        .videoSource(videoSource)
        .build();
controller.configure(model);
playerView.attach(controller);

進階使用

如何?自訂策略?

AliPlayerKit提供兩種實現方式,根據需求選擇:

方式

特點

適用情境

繼承 BaseStrategy

代碼量少,架構管理事件訂閱。

大多數監控策略。

實現 IStrategy 介面

靈活性高,完全自主控制。

需要特殊控制邏輯。

方式一:繼承 BaseStrategy(推薦)

繼承 BaseStrategy 是最簡單的方式,架構已封裝好事件訂閱和生命週期管理。

適用情境:大多數監控和分析策略,如統計、檢測、日誌等。

  1. 建立策略類。

    繼承 BaseStrategy 並實現必要方法:

    public class MyAnalyticsStrategy extends BaseStrategy {
    
        private static final String TAG = "MyAnalyticsStrategy";
    
        @NonNull
        @Override
        public String getName() {
            return TAG;  // 返回策略名稱稱,用於日誌和調試
        }
    
        @Nullable
        @Override
        protected List<Class<? extends PlayerEvent>> observedEvents() {
            // 聲明需要訂閱的事件類型
            return Arrays.asList(
                PlayerEvents.StateChanged.class,
                PlayerEvents.Prepared.class,
                PlayerEvents.Info.class
            );
        }
    
        @Override
        public void onEvent(@NonNull PlayerEvent event) {
            // 過濾非當前播放器事件
            if (!isCurrentPlayer(event)) return;
    
            // 處理事件
            if (event instanceof PlayerEvents.StateChanged) {
                handleStateChanged((PlayerEvents.StateChanged) event);
            } else if (event instanceof PlayerEvents.Prepared) {
                handlePrepared((PlayerEvents.Prepared) event);
            } else if (event instanceof PlayerEvents.Info) {
                handleInfo((PlayerEvents.Info) event);
            }
        }
    
        @Override
        public void onReset() {
            super.onReset();
            // 重設內部狀態,準備處理新的視頻
        }
    
        private void handleStateChanged(PlayerEvents.StateChanged event) {
            // 處理狀態變化
        }
    
        private void handlePrepared(PlayerEvents.Prepared event) {
            // 處理準備完成
        }
    
        private void handleInfo(PlayerEvents.Info event) {
            // 處理播放資訊更新
        }
    }
  2. 註冊使用。

    StrategyManager strategyManager = controller.getStrategyManager();
    strategyManager.register(new MyAnalyticsStrategy());

樣本參考:strategies/FirstFrameStrategy.java。

方式二:實現IStrategy介面

直接實現 IStrategy 介面可以獲得更高的靈活性,但需要自行處理事件訂閱。

適用情境:需要完全控制策略行為,或需要特殊初始化邏輯。

  1. 建立策略類。

    實現 IStrategy 介面,並自行管理事件訂閱:

    public class MyCustomStrategy implements IStrategy, PlayerEventBus.EventListener<PlayerEvent> {
    
        private static final String TAG = "MyCustomStrategy";
    
        private StrategyContext mContext;
    
        @NonNull
        @Override
        public String getName() {
            return TAG;
        }
    
        @Override
        public void onStart(@NonNull StrategyContext context) {
            mContext = context;
            // 手動訂閱事件
            PlayerEventBus.getInstance().subscribe(PlayerEvents.StateChanged.class, this);
        }
    
        @Override
        public void onStop() {
            // 手動取消訂閱
            PlayerEventBus.getInstance().unsubscribe(PlayerEvents.StateChanged.class, this);
            mContext = null;
        }
    
        @Override
        public void onReset() {
            // 重設內部狀態
        }
    
        @Override
        public void onEvent(@NonNull PlayerEvent event) {
            // 處理事件
        }
    }
  2. 註冊使用。

    StrategyManager strategyManager = controller.getStrategyManager();
    strategyManager.register(new MyAnalyticsStrategy());

如何?帶回調的策略?

策略可以通過回調介面將結果通知給業務層:

public class MyAnalyticsStrategy extends BaseStrategy {

    public interface Callback {
        void onAnalysisComplete(AnalysisResult result);
    }

    @Nullable
    private final Callback mCallback;

    // 支援無參構造
    public MyAnalyticsStrategy() {
        this(null);
    }

    // 支援傳入回調
    public MyAnalyticsStrategy(@Nullable Callback callback) {
        mCallback = callback;
    }

    @Override
    public void onEvent(@NonNull PlayerEvent event) {
        // ... 商務邏輯

        // 通過回調通知結果
        if (mCallback != null) {
            mCallback.onAnalysisComplete(result);
        }
    }
}

使用方式:

strategyManager.register(new MyAnalyticsStrategy(result -> {
    // 處理分析結果
    Log.d("Analytics", "Result: " + result);
}));

樣本參考:strategies/FirstFrameStrategy.java。

如何訪問播放器狀態?

策略可以通過 StrategyContext 訪問播放器狀態和資料:

@Override
public void onEvent(@NonNull PlayerEvent event) {
    if (!isCurrentPlayer(event)) return;

    // 擷取播放器 ID
    String playerId = getPlayerId();

    // 擷取播放資料模型
    AliPlayerModel model = mContext.getModel();
    if (model != null) {
        String title = model.getVideoTitle();
        VideoSource source = model.getVideoSource();
    }

    // 擷取播放器狀態儲存
    IPlayerStateStore stateStore = mContext.getPlayerStateStore();
    long position = stateStore.getCurrentPosition();
    long duration = stateStore.getDuration();
    PlayerState state = stateStore.getPlayState();
}
重要

mContext在建構函式中為null,需在onStart()之後才被初始化。

如何註冊全域原則?

通過 StrategyRegistry 可以註冊全域原則,自動應用於所有新建立的播放器執行個體:

// 註冊全域原則
StrategyRegistry.addGlobalStrategy(() -> new MyAnalyticsStrategy());

// 之後建立的所有 AliPlayerController 都會自動註冊此策略
AliPlayerController controller = new AliPlayerController(this);   

恢複預設配置:

StrategyRegistry.resetToDefault();

最佳實務

生命週期管理

public class MyStrategy extends BaseStrategy {

    @Overridepublic void onStart(@NonNull StrategyContext context) {
        super.onStart(context);  // 必須調用// 初始化資源
    }

    @Overridepublic void onStop() {
        // 清理資源super.onStop();  // 必須調用
    }

    @Overridepublic void onReset() {
        super.onReset();
        // 重設內部狀態,準備處理新視頻
    }
}

事件過濾

在多播放器情境下,務必過濾事件來源:

@Overridepublic void onEvent(@NonNull PlayerEvent event) {
    // 過濾非當前播放器事件,避免串台if (!isCurrentPlayer(event)) return;

    // 處理當前播放器的事件
}

資源清理

策略中如果有 Handler、Runnable 等非同步資源,務必在 onStop() 中清理:

public class MyStrategy extends BaseStrategy {

    private final Handler mHandler = new Handler();
    private Runnable mRunnable;

    @Overridepublic void onStart(@NonNull StrategyContext context) {
        super.onStart(context);
        mRunnable = () -> doSomething();
        mHandler.postDelayed(mRunnable, 1000);
    }

    @Overridepublic void onStop() {
        // 清理非同步任務,避免記憶體流失
        mHandler.removeCallbacks(mRunnable);
        super.onStop();
    }
}

注意事項

情境

推薦做法

原因

多播放器情境

使用 isCurrentPlayer() 過濾事件。

避免事件串台。

長時間運行任務

在 onStop() 中取消任務。

避免記憶體流失。

視頻切換

在 onReset() 中重設狀態。

確保狀態正確。

訪問 Context

通過 mContext.getModel() 擷取。

確保上下文可用。

樣本參考

專案提供了完整的樣本,位於 playerkit-examples/example-strategy-system。

樣本功能

功能

說明

記憶播放策略

自動記錄播放進度,下次播放時恢複。

策略註冊示範

示範如何註冊自訂策略。

運行樣本

在Demo App中選擇 Strategy System 樣本查看效果。

API 參考

核心介面

介面/類

說明

IStrategy

策略介面,定義生命週期方法。

BaseStrategy

策略基類,封裝事件訂閱管理。

StrategyManager

策略管理器,管理原則生命週期。

StrategyContext

策略上下文,提供唯讀播放器資訊。

StrategyRegistry

策略註冊中心,管理全域原則。

BaseStrategy 方法

方法

說明

getName()

返回策略名稱稱,用於日誌和調試。

observedEvents()

返回需要訂閱的事件類型列表。

onEvent(event)

事件回調,處理訂閱的事件。

isCurrentPlayer(event)

檢查事件是否來自當前播放器。

getPlayerId()

擷取當前播放器 ID。

StrategyManager 方法

方法

說明

register(strategy)

註冊策略。

unregister(strategy)

登出策略。

getStrategy(name)

根據名稱擷取策略。

start(context)

啟動所有策略。

stop()

停止所有策略。

reset()

重設所有策略。

destroy()

銷毀管理器。

StrategyContext 方法

方法

說明

getPlayerId()

擷取播放器 ID。

getModel()

擷取播放資料模型。

getPlayerStateStore()

擷取播放器狀態儲存(唯讀)。

技術原理

整體架構

策略系統採用 事件驅動 + 管理器調度 的架構模式。

設計要點:

  • 策略不持有播放器引用:通過事件驅動,徹底解耦。

  • 策略只關注事件:只需聲明訂閱什麼和如何處理。

  • 管理器統一調度:生命週期、事件分發由管理器控制。

生命週期

策略採用三階段生命週期:

生命週期

觸發時機

說明

onStart(context)

playerView.attach()

策略啟動,訂閱事件,初始化資源。

onReset()

播放內容切換

重設內部狀態,準備處理新視頻。

onStop()

playerView.detach() 或銷毀

策略停止,取消訂閱,清理資源。

事件驅動機制

策略通過事件匯流排訂閱播放器事件,採用發布-訂閱模式。

事件訂閱流程:

  1. 策略在 observedEvents() 中聲明需要訂閱的事件類型

  2. BaseStrategy 在 onStart() 中自動訂閱這些事件

  3. BaseStrategy 在 onStop() 中自動取消訂閱

  4. 策略在 onEvent() 中處理接收到的事件

設計理念:事件驅動機制徹底解耦策略與播放器—策略無需持有播放器引用,只需關注事件訂閱與處理邏輯,實現關注點分離。

多播放器隔離

在多播放器情境下,策略系統通過以下機制確保隔離:

  1. 獨立 StrategyManager:每個 AliPlayerController 擁有獨立的 StrategyManager。

  2. 獨立 StrategyContext:每個策略上下文綁定特定播放器 ID。

  3. 事件過濾:策略通過 isCurrentPlayer(event) 過濾事件來源。

@Overridepublic void onEvent(@NonNull PlayerEvent event) {
    // 每個播放器執行個體的事件都帶有 playerId// isCurrentPlayer() 檢查事件是否來自當前播放器if (!isCurrentPlayer(event)) return;

    // 只處理當前播放器的事件
}

常見問題

策略什麼時候啟動?

調用 playerView.attach() 時自動啟動所有登入的策略。

如何擷取播放器目前狀態?

通過 StrategyContext 的 getPlayerStateStore() 方法:

IPlayerStateStore stateStore = mContext.getPlayerStateStore();
PlayerState state = stateStore.getPlayState();
long position = stateStore.getCurrentPosition();

如何調試策略?

使用 LogHub 查看日誌,TAG 格式:策略名稱稱.BaseStrategy 或 StrategyManager。

高頻崩潰錯例

以下是客戶回函中最常導致崩潰的問題,請務必避免:

錯例 1:在建構函式中訪問 mContext

錯誤碼:

public class MyStrategy extends BaseStrategy {

    public MyStrategy() {
        super();
        // 建構函式中 mContext 為 null
        String playerId = getPlayerId();  // 返回Null 字元串
        AliPlayerModel model = mContext.getModel();  // NullPointerException!
    }
}

正確代碼:

@Overridepublic void onStart(@NonNull StrategyContext context) {
    super.onStart(context);  // ✅ 必須首先調用// 在 onStart 之後才能訪問 mContext
    String playerId = getPlayerId();  // 正常擷取
    AliPlayerModel model = mContext.getModel();  // 正常擷取
}

崩潰原因:mContext 在 onStart() 調用後才被賦值,建構函式執行時還未啟動策略。

錯例 2:忘記調用 super.onStart() 導致事件訂閱失效

錯誤碼:

public class MyStrategy extends BaseStrategy {

    @Overridepublic void onStart(@NonNull StrategyContext context) {
        // 忘記調用 super.onStart(context)// mContext 為 null,事件訂閱不會執行
        initMyResources();
    }

    @Overridepublic void onEvent(@NonNull PlayerEvent event) {
        // 永遠不會被調用!因為事件訂閱未執行
    }
}

正確代碼:

@Overridepublic void onStart(@NonNull StrategyContext context) {
    super.onStart(context);  // ✅ 必須首先調用
    initMyResources();
}

崩潰原因:super.onStart(context) 會初始化 mContext 並訂閱 observedEvents() 返回的事件。不調用會導致 mContext 為 null、事件訂閱無效。

錯例 3:未過濾事件導致多播放器串台

錯誤碼:

public class MyStrategy extends BaseStrategy {

    @Overridepublic void onEvent(@NonNull PlayerEvent event) {
            // 未過濾事件來源,多播放器情境會串台if (event instanceof PlayerEvents.StateChanged) {
            // 所有播放器的狀態變化都會觸發此邏輯
            updateUI();  // 可能操作錯誤的播放器 UI
        }
    }
}

正確代碼:

@Overridepublic void onEvent(@NonNull PlayerEvent event) {
    // 首先過濾非當前播放器事件if (!isCurrentPlayer(event)) return;

    if (event instanceof PlayerEvents.StateChanged) {
        updateUI();  // 只處理當前播放器的事件
    }
}

崩潰原因:在多播放器情境下,所有播放器的事件都會分發到所有策略。不進行過濾會導致策略處理其他播放器的事件。

錯例 4:onStop 未清理非同步任務導致記憶體流失

錯誤碼:

public class MyStrategy extends BaseStrategy {

    private Handler handler = new Handler();

    @Overridepublic void onStart(@NonNull StrategyContext context) {
        super.onStart(context);
        handler.postDelayed(() -> updateProgress(), 1000);  // 延遲任務
    }

    @Overridepublic void onStop() {
        // 忘記移除延遲任務super.onStop();
    }
}

正確代碼:

@Overridepublic void onStop() {
    handler.removeCallbacksAndMessages(null);  // 清理所有延遲任務super.onStop();
}

崩潰原因:延遲任務持有策略執行個體引用,策略停止後無法被 GC,導致記憶體流失。任務執行時可能訪問已銷毀的資源。

錯例 5:observedEvents 返回 null 導致事件訂閱失敗

錯誤碼:

public class MyStrategy extends BaseStrategy {

    @Nullable@Overrideprotected List<Class<? extends PlayerEvent>> observedEvents() {
        // 返回 null,雖然不會崩潰,但事件訂閱不會執行return null;
    }

    @Overridepublic void onEvent(@NonNull PlayerEvent event) {
        // 永遠不會被調用
    }
}

正確代碼:

@Nullable@Overrideprotected List<Class<? extends PlayerEvent>> observedEvents() {
    return Arrays.asList(
        PlayerEvents.StateChanged.class,  // 聲明要訂閱的事件
        PlayerEvents.Prepared.class
    );
}

@Overridepublic void onEvent(@NonNull PlayerEvent event) {
    // 現在可以正常收到事件了
}

崩潰原因:BaseStrategy 在訂閱事件時會檢查 observedEvents() 傳回值,null 或空列表都不會訂閱任何事件。

錯例 6:onReset 未重設內部狀態導致資料錯誤

錯誤碼:

public class MyStrategy extends BaseStrategy {

    private int mPlayCount = 0;

    @Overridepublic void onEvent(@NonNull PlayerEvent event) {
        if (!isCurrentPlayer(event)) return;

        if (event instanceof PlayerEvents.Prepared) {
            mPlayCount++;  // 視頻切換時未重設,會累積
            Log.d(TAG, "Play count: " + mPlayCount);
        }
    }

    @Overridepublic void onReset() {
        super.onReset();
        // 忘記重設 mPlayCount
    }
}

正確代碼:

@Overridepublic void onReset() {
    super.onReset();
    mPlayCount = 0;  // 重設內部狀態
}

崩潰原因:onReset() 在視頻切換時調用,用於重設策略內部狀態。不重設會導致狀態累積,影響統計準確性。