All Products
Search
Document Center

ApsaraVideo VOD:Sistem strategi

Last Updated:Sep 10, 2026

Topik ini menjelaskan latar belakang desain, konsep inti, strategi bawaan, dan penggunaan Sistem Strategi AliPlayerKit.

Latar Belakang

Sistem Strategi dirancang untuk mengatasi konflik antara kustomisasi bisnis dan evolusi framework dalam proyek AUI Kit berskala besar.

Sebagai solusi low-code, AUI Kit harus mendukung kustomisasi UI dan proses inti guna memenuhi berbagai kebutuhan bisnis. Jika logika bisnis langsung disematkan ke dalam kode framework, hal ini menciptakan keterkaitan tinggi (high coupling), yang dapat menyebabkan konflik kode, merusak logika bisnis saat framework ditingkatkan, serta menghambat iterasi versi.

Logika semacam ini merupakan kebutuhan bisnis, bukan fitur inti framework. Tanpa mekanisme yang tepat untuk menanganinya, logika spesifik bisnis dapat merembes ke dalam framework, sehingga mengancam stabilitas sistem dan kemampuannya untuk berevolusi.

Sistem Strategi menyelesaikan masalah ini dengan:

  • Mengabstraksi logika spesifik bisnis menjadi unit strategi yang independen dan dapat dikembangkan.

  • Memisahkan logika bisnis dari inti framework (decoupling).

  • Memungkinkan bisnis memperluas sistem pada lapisan strategi tanpa mengubah framework.

Mekanisme ini menjadikan kemampuan bisnis sebagai fondasi yang sistematis dan dapat digunakan kembali, sehingga menciptakan dasar yang iteratif, reusable, dan extensible untuk mendukung evolusi jangka panjang AliPlayerKit.

Konsep

Apa itu sistem strategi?

Sistem Strategi adalah mekanisme arsitektural yang menyatukan pengelolaan komponen strategi. Sistem ini bertanggung jawab atas registrasi strategi, manajemen siklus hidup, injeksi konteks, dan langganan event, serta mengelola pembuatan, start, pause, reset, dan penghapusan strategi selama pemutar berjalan.
Dengan mekanisme ini, developer dapat mengaktifkan atau menonaktifkan strategi sesuai kebutuhan, menerapkan kebijakan kustom secara cepat, atau sepenuhnya mengganti strategi default untuk memenuhi kebutuhan kustomisasi bisnis.

Apa itu strategi?

Strategi adalah unit terenkapsulasi dari logika bisnis pemutar yang mengimplementasikan kemampuan bisnis atau logika kustom tertentu, seperti statistik time-to-first-frame (TTFF), deteksi tersendat, perlindungan trafik, atau melanjutkan pemutaran.
Fitur inti:

  • Tanggung jawab tunggal: Fokus pada tujuan fungsional yang independen.

  • Berdasar event: Merespons status pemutar melalui event bus.

  • Independen dan terisolasi: Strategi tidak saling mengganggu satu sama lain.

Melalui desain yang dapat dipasang (pluggable) dan dikomposisi, kemampuan non-inti diabstraksi menjadi unit strategi. Hal ini menjaga inti pemutar tetap sederhana sekaligus menyediakan kemampuan ekstensi yang fleksibel.

Fitur

Masalah yang diselesaikan

Sistem Strategi mengatasi permasalahan berikut:

  1. Pemeliharaan sulit: Logika bisnis yang tersebar sulit dipelihara dan digunakan kembali.

  2. Kurangnya konfigurasi: Skenario berbeda memerlukan kombinasi logika berbeda, tetapi tidak tersedia mekanisme konfigurasi yang fleksibel.

  3. Modifikasi kode sumber: Logika kustom memerlukan modifikasi kode sumber framework.

  4. Gangguan antar instans: Dalam skenario multi-pemutar, logika bisnis antar instans dapat saling mengganggu.

Nilai inti

Sistem Strategi mengabstraksi dan mengenkapsulasi logika pemantauan dan analisis pemutar, sehingga memungkinkan pelanggan mengonfigurasi dan mengombinasikan strategi sesuai kebutuhan.

Metode penggunaan

Deskripsi

Keuntungan

Gunakan strategi default

Komponen pemutar menggunakan strategi default resmi.

Menyederhanakan proses integrasi. Langsung berfungsi tanpa konfigurasi tambahan (out-of-the-box).

Kebijakan kustom

Implementasikan strategi untuk kebutuhan bisnis tertentu.

Memenuhi berbagai kebutuhan bisnis yang dikustomisasi.

Tidak gunakan strategi

Hanya gunakan fitur pemutaran.

Untuk skenario pemutaran murni tanpa ketergantungan logika.

Keunggulan arsitektur:

  • Penguraian keterkaitan (Decoupling): Strategi dipisahkan dari logika inti pemutar, menciptakan tanggung jawab yang jelas.

  • Dapat digunakan kembali (Reusability): Strategi dapat digunakan kembali di berbagai instans pemutar.

  • Dapat dikembangkan (Extensibility): Anda dapat mengkustomisasi strategi tanpa mengubah framework.

  • Isolasi: Strategi bersifat independen dan tidak saling mengganggu.

Kemampuan inti

Kemampuan

Deskripsi

Berdasar event (Event-driven)

Strategi berlangganan event pemutar melalui event bus dan dieksekusi secara responsif.

Manajemen siklus hidup

Mengelola start, stop, reset, dan penghapusan strategi.

Injeksi konteks

Strategi dapat mengakses status dan data pemutar melalui konteks.

Pola factory

Mendukung registrasi global factory strategi, yang secara otomatis diterapkan ke semua instans pemutar.

Strategi bawaan

AliPlayerKit menyediakan tiga strategi bawaan siap pakai.

Kebijakan Time-to-first-frame (TTFF) (FirstFrameStrategy)

Strategi ini menghitung waktu yang dibutuhkan pemutar untuk berpindah dari tahap PREPARING hingga rendering frame pertama. Strategi ini dibagi menjadi tiga fase:

Fase

Deskripsi

Preparing

PREPARING → Prepared

Rendering

Prepared → FirstFrameRendered

Total time

PREPARING → FirstFrameRendered

Skenario: Pemantauan kinerja, optimasi pengalaman pengguna, dan penyetelan pemutar.

Kebijakan deteksi tersendat (StutterDetectStrategy)

Strategi ini memantau tersendat selama pemutaran dan mengumpulkan statistik tersendat:

Metrik

Deskripsi

Stutter count

Jumlah kejadian tersendat.

Stutter duration

Durasi setiap kejadian tersendat.

Total stutter duration

Durasi total tersendat selama seluruh sesi pemutaran.

Effective playback duration

Waktu tonton aktual, tidak termasuk jeda dan tersendat.

Skenario: Pemantauan kualitas pemutaran dan analisis pengalaman pengguna.

Kebijakan Perlindungan Trafik (TrafficProtectionStrategy)

Strategi ini mendengarkan perubahan status jaringan dan memberi notifikasi kepada pengguna dalam skenario tertentu:

Skenario

Perilaku

Beralih dari Wi-Fi ke jaringan seluler

Memberi notifikasi bahwa pemutaran kini menggunakan data seluler.

Memulai pemutaran di jaringan seluler

Memberi notifikasi bahwa data seluler sedang digunakan.

Skenario: Skenario sensitif terhadap trafik dan notifikasi yang ramah pengguna.

Penggunaan dasar

Sistem Strategi menyediakan tiga cara untuk menggunakan strategi. Pilih yang paling sesuai dengan kebutuhan Anda:

Kebijakan

Deskripsi

Skenario

Gunakan strategi default

Metode paling sederhana. Komponen pemutar secara otomatis mendaftarkan strategi default.

Integrasi cepat dan skenario pemutaran standar.

Kustomisasi sebagian strategi

Hanya mendaftarkan strategi tertentu dan tidak menggunakan strategi lainnya.

Kustomisasi parsial dan mengaktifkan strategi sesuai kebutuhan.

Kustomisasi penuh strategi

Kustomisasi semua strategi untuk membuat sistem pemantauan yang sepenuhnya personal.

Kustomisasi mendalam dan kebutuhan bisnis spesifik.

Metode 1: Gunakan strategi bawaan

Ini adalah metode paling sederhana. Komponen pemutar secara otomatis mendaftarkan strategi default:

// 1. Buat controller pemutar. Strategi default didaftarkan secara otomatis.
AliPlayerController controller = new AliPlayerController(this);

// 2. Siapkan data pemutaran.
AliPlayerModel model = new AliPlayerModel.Builder()
        .videoSource(videoSource)
        .build();

// 3. Konfigurasikan model data.
controller.configure(model);

// 4. Sambungkan controller ke tampilan.
playerView.attach(controller);

Strategi default yang terdaftar meliputi:

  • FirstFrameStrategy (TTFF)

  • StutterDetectStrategy (deteksi tersendat)

  • TrafficProtectionStrategy (perlindungan trafik)

Metode 2: Kustomisasi strategi

Hanya daftarkan strategi yang Anda butuhkan dan nonaktifkan strategi lainnya:

// 1. Buat controller pemutar.
AliPlayerController controller = new AliPlayerController(this);

// 2. Dapatkan manager strategi dan hapus strategi default.
StrategyManager strategyManager = controller.getStrategyManager();

// 3. Daftarkan hanya strategi yang diperlukan.
strategyManager.register(new FirstFrameStrategy());
strategyManager.register(new StutterDetectStrategy());

// 4. Siapkan data pemutaran.
AliPlayerModel model = new AliPlayerModel.Builder()
        .videoSource(videoSource)
        .build();
controller.configure(model);
playerView.attach(controller);

Metode 3: Kustomisasi penuh strategi

Implementasikan kebijakan kustom untuk membuat sistem pemantauan yang sepenuhnya personal:

// 1. Buat controller pemutar.
AliPlayerController controller = new AliPlayerController(this);

// 2. Dapatkan manager strategi.
StrategyManager strategyManager = controller.getStrategyManager();

// 3. Daftarkan kebijakan kustom.
strategyManager.register(new MyCustomStrategy());
strategyManager.register(new MyAnalyticsStrategy());

// 4. Siapkan data pemutaran.
AliPlayerModel model = new AliPlayerModel.Builder()
        .videoSource(videoSource)
        .build();
controller.configure(model);
playerView.attach(controller);

Penggunaan lanjutan

Cara mengimplementasikan kebijakan kustom

AliPlayerKit menyediakan dua metode implementasi. Pilih salah satu berdasarkan kebutuhan Anda:

Metode

Fitur

Skenario

Warisi BaseStrategy

Kode lebih sedikit. Framework mengelola langganan event.

Sebagian besar strategi pemantauan.

Implementasikan antarmuka IStrategy

Fleksibilitas tinggi. Kontrol penuh otonom.

Memerlukan logika kontrol khusus.

Metode 1: Warisi BaseStrategy (Direkomendasikan)

Mewarisi BaseStrategy adalah metode paling sederhana karena framework telah mengenkapsulasi langganan event dan manajemen siklus hidup.

Skenario: Sebagian besar strategi pemantauan dan analisis, seperti statistik, deteksi, dan pencatatan log.

  1. Buat kelas strategi.

    Warisi BaseStrategy dan implementasikan metode yang diperlukan:

    public class MyAnalyticsStrategy extends BaseStrategy {
    
        private static final String TAG = "MyAnalyticsStrategy";
    
        @NonNull
        @Override
        public String getName() {
            return TAG;  // Mengembalikan nama kebijakan untuk logging dan debugging.
        }
    
        @Nullable
        @Override
        protected List<Class<? extends PlayerEvent>> observedEvents() {
            // Deklarasikan jenis event yang akan dilanggankan.
            return Arrays.asList(
                PlayerEvents.StateChanged.class,
                PlayerEvents.Prepared.class,
                PlayerEvents.Info.class
            );
        }
    
        @Override
        public void onEvent(@NonNull PlayerEvent event) {
            // Filter event dari pemutar lain.
            if (!isCurrentPlayer(event)) return;
    
            // Proses event.
            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();
            // Reset status internal untuk mempersiapkan video baru.
        }
    
        private void handleStateChanged(PlayerEvents.StateChanged event) {
            // Tangani perubahan status.
        }
    
        private void handlePrepared(PlayerEvents.Prepared event) {
            // Tangani penyelesaian persiapan.
        }
    
        private void handleInfo(PlayerEvents.Info event) {
            // Tangani pembaruan informasi pemutaran.
        }
    }
  2. Mulai penggunaan.

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

Contoh: Untuk informasi lebih lanjut, lihat strategies/FirstFrameStrategy.java.

Cara 2: Implementasikan IStrategy interface

Mengimplementasikan langsung antarmuka IStrategy memberikan fleksibilitas lebih besar, tetapi Anda perlu menangani langganan event sendiri.

Skenario: Anda memerlukan kontrol penuh atas perilaku strategi atau memerlukan logika inisialisasi khusus.

  1. Buat kelas strategi.

    Implementasikan antarmuka IStrategy dan kelola langganan event sendiri:

    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;
            // Langganan event secara manual.
            PlayerEventBus.getInstance().subscribe(PlayerEvents.StateChanged.class, this);
        }
    
        @Override
        public void onStop() {
            // Batalkan langganan event secara manual.
            PlayerEventBus.getInstance().unsubscribe(PlayerEvents.StateChanged.class, this);
            mContext = null;
        }
    
        @Override
        public void onReset() {
            // Reset status internal.
        }
    
        @Override
        public void onEvent(@NonNull PlayerEvent event) {
            // Proses event.
        }
    }
  2. Daftarkan akun.

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

Cara mengimplementasikan strategi dengan callback

Strategi dapat menggunakan antarmuka callback untuk memberi notifikasi hasil ke lapisan bisnis:

public class MyAnalyticsStrategy extends BaseStrategy {

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

    @Nullable
    private final Callback mCallback;

    // Mendukung konstruktor tanpa parameter.
    public MyAnalyticsStrategy() {
        this(null);
    }

    // Mendukung meneruskan callback.
    public MyAnalyticsStrategy(@Nullable Callback callback) {
        mCallback = callback;
    }

    @Override
    public void onEvent(@NonNull PlayerEvent event) {
        // ... logika bisnis

        // Beri notifikasi hasil melalui callback.
        if (mCallback != null) {
            mCallback.onAnalysisComplete(result);
        }
    }
}

Penggunaan:

strategyManager.register(new MyAnalyticsStrategy(result -> {
    // Proses hasil analisis.
    Log.d("Analytics", "Hasil: " + result);
}));

Contoh: Untuk informasi lebih lanjut, lihat strategies/FirstFrameStrategy.java.

Cara mengakses status pemutar

Strategi dapat mengakses status dan data pemutar melalui StrategyContext:

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

    // Dapatkan ID pemutar.
    String playerId = getPlayerId();

    // Dapatkan model data pemutaran.
    AliPlayerModel model = mContext.getModel();
    if (model != null) {
        String title = model.getVideoTitle();
        VideoSource source = model.getVideoSource();
    }

    // Dapatkan penyimpan status pemutar.
    IPlayerStateStore stateStore = mContext.getPlayerStateStore();
    long position = stateStore.getCurrentPosition();
    long duration = stateStore.getDuration();
    PlayerState state = stateStore.getPlayState();
}
Penting

mContext bernilai null di konstruktor. Nilai ini diinisialisasi hanya setelah onStart() dipanggil.

Cara mendaftarkan strategi global

Anda dapat menggunakan StrategyRegistry untuk mendaftarkan strategi global. Strategi ini akan secara otomatis diterapkan ke semua instans pemutar yang baru dibuat:

// Daftarkan strategi global.
StrategyRegistry.addGlobalStrategy(() -> new MyAnalyticsStrategy());

// Semua instans AliPlayerController yang dibuat setelah ini akan secara otomatis mendaftarkan strategi ini.
AliPlayerController controller = new AliPlayerController(this);   

Kembalikan ke konfigurasi default:

StrategyRegistry.resetToDefault();

Praktik terbaik

Manajemen siklus hidup

public class MyStrategy extends BaseStrategy {

    @Overridepublic void onStart(@NonNull StrategyContext context) {
        super.onStart(context);  // Harus dipanggil.
        // Inisialisasi resource.
    }

    @Overridepublic void onStop() {
        // Bersihkan resource.
        super.onStop();  // Harus dipanggil.
    }

    @Overridepublic void onReset() {
        super.onReset();
        // Reset status internal untuk mempersiapkan video baru.
    }
}

Filtering event

Dalam skenario multi-pemutar, selalu filter sumber event:

@Overridepublic void onEvent(@NonNull PlayerEvent event) {
    // Filter event dari pemutar lain untuk menghindari gangguan.
    if (!isCurrentPlayer(event)) return;

    // Proses event untuk pemutar saat ini.
}

Pembersihan resource

Jika strategi memiliki resource asinkron seperti Handler atau Runnable, bersihkan di 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() {
        // Bersihkan tugas asinkron untuk menghindari kebocoran memori.
        mHandler.removeCallbacks(mRunnable);
        super.onStop();
    }
}

Catatan

Skenario

Praktik yang direkomendasikan

Alasan

Skenario multi-pemutar

Gunakan isCurrentPlayer() untuk memfilter event.

Menghindari gangguan event.

Tugas jangka panjang

Batalkan tugas di onStop().

Menghindari kebocoran memori.

Pergantian video

Reset status di onReset().

Memastikan status benar.

Mengakses Context

Dapatkan melalui mContext.getModel().

Memastikan konteks tersedia.

Referensi contoh

Proyek menyediakan contoh lengkap yang terletak di playerkit-examples/example-strategy-system.

Fitur contoh

Fitur

Deskripsi

Strategi melanjutkan pemutaran

Otomatis mencatat progres pemutaran dan melanjutkan dari titik tersebut saat berikutnya.

Demo registrasi strategi

Menunjukkan cara mendaftarkan kebijakan kustom.

Jalankan contoh

Di aplikasi demo, pilih contoh Sistem Strategi untuk melihatnya berjalan.

Referensi API

Antarmuka inti

Antarmuka/Kelas

Deskripsi

IStrategy

Antarmuka strategi. Mendefinisikan metode siklus hidup.

BaseStrategy

Kelas dasar untuk strategi. Mengenkapsulasi manajemen langganan event.

StrategyManager

Manager strategi. Mengelola siklus hidup strategi.

StrategyContext

Konteks strategi. Menyediakan informasi pemutar read-only.

StrategyRegistry

Registrasi layanan untuk strategi. Mengelola strategi global.

Metode BaseStrategy

Metode

Deskripsi

getName()

Mengembalikan nama kebijakan untuk logging dan debugging.

observedEvents()

Mengembalikan daftar jenis event yang akan dilanggankan.

onEvent(event)

Callback event. Callback ini memproses event yang dilanggan.

isCurrentPlayer(event)

Memeriksa apakah event berasal dari pemutar saat ini.

getPlayerId()

Mendapatkan ID pemutar saat ini.

Metode StrategyManager

Metode

Deskripsi

register(strategy)

Mendaftarkan strategi.

unregister(strategy)

Membatalkan pendaftaran strategi.

getStrategy(name)

Mendapatkan strategi berdasarkan nama.

start(context)

Memulai semua strategi.

stop()

Menghentikan semua strategi.

reset()

Meriset semua strategi.

destroy()

Menghapus manager.

Metode StrategyContext

Metode

Deskripsi

getPlayerId()

Mendapatkan ID pemutar.

getModel()

Mendapatkan model data pemutaran.

getPlayerStateStore()

Mendapatkan penyimpan status pemutar (read-only).

Prinsip teknis

Arsitektur keseluruhan

Sistem Strategi menggunakan pola arsitektur berdasar event dan dijadwalkan oleh manager.

Poin desain:

  • Strategi tidak menyimpan referensi pemutar: Penguraian keterkaitan lengkap dicapai melalui desain berdasar event.

  • Strategi hanya fokus pada event: Strategi hanya perlu mendeklarasikan event apa yang akan dilanggankan dan bagaimana menanganinya.

  • Manager menangani penjadwalan: Manager mengontrol siklus hidup dan pengiriman event.

Siklus hidup

Strategi menggunakan siklus hidup tiga tahap:

Siklus hidup

Kondisi pemicu

Deskripsi

onStart(context)

playerView.attach()

Strategi dimulai, berlangganan event, dan menginisialisasi resource.

onReset()

Isi pemutaran berubah

Meriset status internal untuk mempersiapkan video baru.

onStop()

playerView.detach() atau penghapusan

Strategi dihentikan, membatalkan langganan, dan membersihkan resource.

Mekanisme berdasar event

Strategi berlangganan event pemutar melalui event bus, menggunakan pola publish-subscribe.

Alur langganan event:

  1. Strategi mendeklarasikan jenis event yang perlu dilanggankan di observedEvents().

  2. BaseStrategy secara otomatis berlangganan event tersebut di onStart().

  3. BaseStrategy secara otomatis membatalkan langganan di onStop().

  4. Strategi memproses event yang diterima di onEvent().

Filosofi desain: Mekanisme berdasar event sepenuhnya memisahkan strategi dari pemutar. Strategi tidak perlu menyimpan referensi pemutar. Strategi hanya perlu fokus pada logika langganan dan penanganan event. Hal ini mencapai pemisahan tanggung jawab.

Isolasi multi-pemutar

Dalam skenario multi-pemutar, Sistem Strategi memastikan isolasi melalui mekanisme berikut:

  1. StrategyManager independen: Setiap AliPlayerController memiliki StrategyManager sendiri yang independen.

  2. Independent StrategyContext: Setiap StrategyContext terikat pada ID pemain tertentu.

  3. Filtering event: Strategi memfilter sumber event menggunakan isCurrentPlayer(event).

@Overridepublic void onEvent(@NonNull PlayerEvent event) {
    // Event dari setiap instans pemutar membawa playerId.
    // isCurrentPlayer() memeriksa apakah event berasal dari pemutar saat ini.
    if (!isCurrentPlayer(event)) return;

    // Proses hanya event dari pemutar saat ini.
}

FAQ

Kapan strategi dimulai?

Semua strategi yang terdaftar akan otomatis dimulai saat Anda memanggil playerView.attach().

Bagaimana cara mendapatkan status pemutar saat ini?

Gunakan metode getPlayerStateStore() dari StrategyContext:

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

Bagaimana cara melakukan debug strategi?

Gunakan LogHub untuk melihat log. Format TAG adalah Nama Kebijakan.BaseStrategy atau StrategyManager.

Kesalahan umum yang menyebabkan crash

Berikut adalah masalah paling umum yang dilaporkan pelanggan yang menyebabkan crash. Hindari kesalahan ini.

Kesalahan 1: Mengakses mContext di konstruktor

Kode salah:

public class MyStrategy extends BaseStrategy {

    public MyStrategy() {
        super();
        // mContext bernilai null di konstruktor.
        String playerId = getPlayerId();  // Mengembalikan string kosong.
        AliPlayerModel model = mContext.getModel();  // NullPointerException!
    }
}

Kode yang benar:

@Overridepublic void onStart(@NonNull StrategyContext context) {
    super.onStart(context);  // ✅ Harus dipanggil terlebih dahulu.
    // mContext dapat diakses setelah onStart.
    String playerId = getPlayerId();  // Mendapatkan ID secara normal.
    AliPlayerModel model = mContext.getModel();  // Mendapatkan model secara normal.
}

Alasan crash: mContext diberikan setelah onStart() dipanggil. Strategi belum dimulai saat konstruktor dieksekusi.

Kesalahan 2: Lupa memanggil super.onStart() menyebabkan langganan event gagal

Kode salah:

public class MyStrategy extends BaseStrategy {

    @Overridepublic void onStart(@NonNull StrategyContext context) {
        // Lupa memanggil super.onStart(context).
        // mContext akan bernilai null, dan langganan event tidak dieksekusi.
        initMyResources();
    }

    @Overridepublic void onEvent(@NonNull PlayerEvent event) {
        // Ini tidak akan pernah dipanggil karena langganan event tidak dieksekusi.
    }
}

Kode yang benar:

@Overridepublic void onStart(@NonNull StrategyContext context) {
    super.onStart(context);  // ✅ Harus dipanggil terlebih dahulu.
    initMyResources();
}

Alasan crash: super.onStart(context) menginisialisasi mContext dan berlangganan event yang dikembalikan oleh observedEvents(). Tidak memanggilnya akan menghasilkan mContext bernilai null dan langganan event tidak valid.

Kesalahan 3: Tidak memfilter event menyebabkan gangguan di skenario multi-pemutar

Kode salah:

public class MyStrategy extends BaseStrategy {

    @Overridepublic void onEvent(@NonNull PlayerEvent event) {
            // Sumber event tidak difilter, yang menyebabkan crosstalk di skenario multi-pemutar.
            if (event instanceof PlayerEvents.StateChanged) {
            // Perubahan status dari semua pemutar akan memicu logika ini.
            updateUI();  // Ini dapat memperbarui UI pemutar yang salah.
        }
    }
}

Kode benar:

@Overridepublic void onEvent(@NonNull PlayerEvent event) {
    // Pertama, filter event yang bukan dari pemutar saat ini. if (!isCurrentPlayer(event)) return;

    if (event instanceof PlayerEvents.StateChanged) {
        updateUI();  // Hanya proses event dari pemutar saat ini.
    }
}

Penyebab crash: Dalam skenario multi-pemutar, event dari semua pemutar dikirim ke semua kebijakan. Kegagalan memfilter menyebabkan kebijakan memproses event dari pemutar lain.

Kesalahan 4: Tidak membersihkan tugas asinkron di onStop() menyebabkan kebocoran memori

Kode salah:

public class MyStrategy extends BaseStrategy {

    private Handler handler = new Handler();

    @Overridepublic void onStart(@NonNull StrategyContext context) {
        super.onStart(context);
        handler.postDelayed(() -> updateProgress(), 1000);  // Tugas tertunda.
    }

    @Overridepublic void onStop() {
        // Lupa menghapus tugas tertunda.
        super.onStop();
    }
}

Kode benar:

@Overridepublic void onStop() {
    handler.removeCallbacksAndMessages(null);  // Hapus semua tugas tertunda
    super.onStop();
}

Penyebab crash: Tugas tertunda menyimpan referensi ke instans kebijakan. Setelah kebijakan dihentikan, instans tidak dapat dikumpulkan oleh garbage collector, yang menyebabkan kebocoran memori. Saat tugas dieksekusi, tugas tersebut dapat mengakses resource yang telah dihancurkan.

Kesalahan 5: observedEvents mengembalikan null, menyebabkan langganan event gagal

Kode salah:

public class MyStrategy extends BaseStrategy {

    @Nullable@Overrideprotected List<Class<? extends PlayerEvent>> observedEvents() {
        // Mengembalikan null tidak menyebabkan crash, tetapi langganan event tidak dieksekusi.
        return null;
    }

    @Overridepublic void onEvent(@NonNull PlayerEvent event) {
        // Tidak akan pernah dipanggil.
    }
}

Kode yang benar:

@Nullable@Overrideprotected List<Class<? extends PlayerEvent>> observedEvents() {
    return Arrays.asList(
        PlayerEvents.StateChanged.class,  // Deklarasikan event yang akan dilanggankan
        PlayerEvents.Prepared.class
    );
}

@Overridepublic void onEvent(@NonNull PlayerEvent event) {
    // Anda sekarang dapat menerima event.
}

Alasan crash: BaseStrategy memeriksa nilai kembalian observedEvents() saat berlangganan event. Jika nilai kembalian adalah `null` atau daftar kosong, tidak ada event yang dilanggankan.

Kesalahan 6: Tidak meriset status internal di onReset() menyebabkan data salah

Kode salah:

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++;  // Hitungan tidak direset saat video berganti, sehingga terakumulasi.
            Log.d(TAG, "Jumlah pemutaran: " + mPlayCount);
        }
    }

    @Overridepublic void onReset() {
        super.onReset();
        // Lupa mereset mPlayCount.
    }
}

Kode benar:

@Overridepublic void onReset() {
    super.onReset();
    mPlayCount = 0;  // Reset status internal.
}

Penyebab crash: onReset() dipanggil saat pergantian video untuk meriset status internal kebijakan. Jika status tidak direset, status tersebut terakumulasi dan memengaruhi akurasi statistik.