Sistem Slot merupakan arsitektur inti AliPlayerKit yang membagi UI pemutar menjadi komponen-komponen independen melalui komponenisasi. Sistem terpadu mengelola komponen-komponen ini untuk mencapai penguraian keterkaitan UI, fleksibilitas pencampuran, serta kustomisasi yang dapat diperluas.
Pengenalan Konsep
Apa itu Sistem Slot?
Sistem Slot adalah salah satu arsitektur inti AliPlayerKit yang berfungsi sebagai mekanisme manajemen plugin untuk komponen UI.
Sistem ini memungkinkan developer menambah, menghapus, atau mengganti komponen UI secara dinamis di posisi mana pun pada antarmuka pemutar, sehingga menghasilkan UI pemutar yang sangat dapat dikustomisasi.
Apa itu Slot?
Slot adalah kontainer tampilan yang dicadangkan untuk menampung sekelompok komponen UI terkait.
Setiap slot memiliki siklus hidup, logika tampil/sembunyi, serta manajemen event sendiri. Slot dapat secara otomatis menampilkan atau menyembunyikan komponen internal berdasarkan kondisi seperti status pemutaran atau jenis skenario.
Fitur
Masalah yang Diselesaikan
Keterkaitan erat antara komponen UI dan logika pemutar.
Kustomisasi antarmuka memerlukan modifikasi kode sumber framework.
Fleksibilitas terbatas saat menggabungkan komponen di berbagai skenario.
Manajemen siklus hidup komponen yang kompleks.
Nilai Inti
Sistem Slot menguraikan komponen UI pemutar secara independen. Developer dapat memilih komponen mana yang akan digunakan dan cara mengonfigurasinya.
Penggunaan | Deskripsi | Keuntungan |
Gunakan antarmuka default | Komponen pemutar menggunakan UI default resmi | Menyederhanakan integrasi, mengurangi biaya integrasi, dan memungkinkan integrasi low-code. |
Penggunaan kustom | Ganti beberapa atau seluruh implementasi slot | Memenuhi berbagai kebutuhan UI yang kaya. |
Jangan gunakan slot | Gunakan hanya kemampuan pemutaran | Skenario pemutaran murni tanpa ketergantungan UI. |
Keunggulan Arsitektur:
Penguraian keterkaitan: Komponen UI terpisah dari logika inti pemutar dengan tanggung jawab yang jelas.
Fleksibilitas: Gabungkan dan ganti komponen UI secara dinamis saat runtime.
Dapat diperluas: Kustomisasi UI tanpa memodifikasi framework, memungkinkan ekstensibilitas kuat.
Pemisahan tanggung jawab: Gaya UI (layout XML) terpisah dari logika bisnis (penanganan kelas Slot).
Kemampuan Inti
Kemampuan | Deskripsi |
Pencampuran dinamis | Gabungkan komponen UI secara bebas saat runtime. |
Hot swapping | Ganti implementasi komponen tanpa perlu restart. |
Adaptasi skenario | Otomatis beralih perilaku UI untuk skenario pemutaran berbeda. |
Komponen Bawaan
AliPlayerKit menyediakan serangkaian komponen bawaan siap pakai yang mencakup kebutuhan umum UI pemutar.
Jenis Slot
Jenis Slot | Deskripsi | Implementasi Default |
PLAYER_SURFACE | Tampilan video, mendukung berbagai tampilan rendering | DisplayViewSlot / SurfaceViewSlot / TextureViewSlot |
FULLSCREEN | Manajemen layar penuh, menangani peralihan orientasi layar | FullscreenSlot |
GESTURE_CONTROL | Kontrol gesture, menangani klik/klik ganda/tahan lama/geser | GestureControlSlot |
LANDSCAPE_HINT | Petunjuk tampilan lanskap, memandu pengguna ke layar penuh | LandscapeHintSlot |
COVER | Gambar sampul, menampilkan sampul video sebelum pemutaran | CoverSlot |
CENTER_DISPLAY | Tampilan tengah, menunjukkan status seperti kecepatan pemutaran/kecerahan/volume | CenterDisplaySlot |
PLAY_STATE | Status pemutaran, menampilkan prompt loading/error, dll. | PlayStateSlot |
LOG_PANEL | Panel log, menampilkan log pemutar saat debugging | LogPanelSlot |
TOP_BAR | Bilah kontrol atas, menampilkan tombol kembali/judul/pengaturan, dll. | TopBarSlot |
BOTTOM_BAR | Bilah kontrol bawah, menampilkan kontrol pemutaran/bilah progres, dll. | BottomBarSlot |
SETTING_MENU | Menu pengaturan, menampilkan pengaturan seperti kecepatan pemutaran/definisi/pencerminan | SettingMenuSlot |
Adaptasi Skenario
AliPlayerKit mendefinisikan lima skenario pemutaran. Perilaku slot secara otomatis menyesuaikan setiap skenario:
Skenario | Deskripsi | Aplikasi Khas |
VOD | Skenario video-on-demand yang mendukung semua fitur. | Pemutaran video reguler. |
LIVE | Skenario streaming langsung dengan operasi garis waktu dinonaktifkan. | Streaming langsung real-time. |
VIDEO_LIST | Skenario pemutaran daftar dengan gesture vertikal dinonaktifkan. | Feed informasi dan daftar video pendek. |
RESTRICTED | Skenario pemutaran terbatas yang membatasi pencarian (seeking). | Edukasi dan pelatihan, pemantauan ujian. |
MINIMAL | Skenario pemutaran minimal yang hanya menampilkan layar video. | Video latar belakang dan video dekoratif. |
Aturan Visibilitas Slot:
Slot | VOD | LIVE | VIDEO_LIST | RESTRICTED | MINIMAL |
PLAYER_SURFACE | ✓ | ✓ | ✓ | ✓ | ✓ |
FULLSCREEN | ✓ | ✓ | ✓ | ✓ | ✓ |
GESTURE_CONTROL | ✓ | ✓ | ✓ | ✓ | ✗ |
LANDSCAPE_HINT | ✓ | ✓ | ✓ | ✓ | ✗ |
COVER | ✓ | ✗ | ✓ | ✗ | ✗ |
CENTER_DISPLAY | ✓ | ✓ | ✓ | ✓ | ✗ |
PLAY_STATE | ✓ | ✓ | ✓ | ✓ | ✓ |
LOG_PANEL | Dapat dikonfigurasi | Dapat dikonfigurasi | Dapat dikonfigurasi | Dapat dikonfigurasi | ✗ |
TOP_BAR | ✓ | ✓ | ✓ | ✓ | ✗ |
BOTTOM_BAR | ✓ | ✓ | ✓ | ✓ | ✗ |
SETTING_MENU | ✓ | ✓ | ✓ | ✓ | ✗ |
Perbedaan Perilaku Gesture:
Gesture | VOD | LIVE | VIDEO_LIST | RESTRICTED | MINIMAL |
Click | Tampilkan/sembunyikan bilah kontrol | Tampilkan/sembunyikan bilah kontrol | Tampilkan/sembunyikan bilah kontrol | Tampilkan/sembunyikan bilah kontrol | Disabled |
Double-click | Alihkan putar/jeda | Alihkan putar/jeda | Alihkan putar/jeda | Alihkan putar/jeda | Disabled |
Long press | Pemutaran kecepatan 2× | Disabled | Pemutaran kecepatan 2× | Disabled | Disabled |
Horizontal drag | Navigasi progres | Disabled | Navigasi progres | Disabled | Disabled |
Left vertical drag | Atur kecerahan | Atur kecerahan | Disabled | Atur kecerahan | Disabled |
Right vertical drag | Atur volume | Atur volume | Disabled | Atur volume | Disabled |
Perbedaan Bilah Kontrol Bawah:
Control | VOD | LIVE | VIDEO_LIST | RESTRICTED | MINIMAL |
Tombol play/pause | ✓ | ✓ | ✓ | ✓ | ✗ |
Progress bar | Draggable | Not draggable | Draggable | Not draggable | ✗ |
Tampilan waktu | ✓ | ✓ | ✓ | ✓ | ✗ |
Tombol refresh | ✗ | ✓ | ✗ | ✗ | ✗ |
Tombol full screen | ✓ | ✓ | ✓ | ✓ | ✗ |
Penggunaan Dasar
Sistem Slot menawarkan tiga strategi penggunaan. Developer dapat memilih metode yang sesuai berdasarkan kebutuhan mereka:
Strategi | Deskripsi | Skenario Penerapan |
Gunakan antarmuka default | Metode paling sederhana; komponen pemutar menggunakan antarmuka default. | Integrasi cepat, skenario pemutaran standar. |
Kustomisasi beberapa slot | Hanya kustomisasi slot tertentu; sisanya menggunakan antarmuka default. | Kustomisasi parsial, mempertahankan interaksi default. |
Kustomisasi antarmuka sepenuhnya | Kustomisasi semua slot untuk membuat antarmuka pemutar yang sepenuhnya personal. | Kustomisasi mendalam, UI khusus merek. |
Strategi Satu: Gunakan Konfigurasi Default
Pendekatan paling sederhana. Langsung gunakan konfigurasi slot bawaan:
// 1. Dapatkan tampilan pemutar
AliPlayerView playerView = findViewById(R.id.player_view);
// 2. Buat controller dan data pemutaran
AliPlayerController controller = new AliPlayerController(this);
AliPlayerModel model = new AliPlayerModel.Builder()
.videoSource(videoSource)
.build();
// 3. Ikat ke tampilan (otomatis menggunakan slot default)
playerView.attach(controller, model);Strategi Dua: Kustomisasi Beberapa Slot
Hanya kustomisasi slot tertentu; sisanya mempertahankan antarmuka default. Misalnya, hanya kustomisasi bilah atas:
// 1. Buat registri
SlotRegistry registry = new SlotRegistry();
// 2. Daftarkan hanya slot yang akan dikustomisasi
registry.register(SlotType.TOP_BAR, parent -> new MyTopBarSlot(parent.getContext()));
// 3. Teruskan registri saat mengikat (slot yang tidak terdaftar menggunakan implementasi default)
playerView.attach(controller, model, registry);Strategi Tiga: Kustomisasi Antarmuka Secara Penuh
Kustomisasi semua slot untuk membuat antarmuka pemutar yang sepenuhnya personal:
// 1. Buat registri
SlotRegistry registry = new SlotRegistry();
// 2. Daftarkan semua slot
registry.register(SlotType.PLAYER_SURFACE, parent -> new MySurfaceSlot(parent.getContext()));
registry.register(SlotType.COVER, parent -> new MyCoverSlot(parent.getContext()));
registry.register(SlotType.TOP_BAR, parent -> new MyTopBarSlot(parent.getContext()));
registry.register(SlotType.BOTTOM_BAR, parent -> new MyBottomBarSlot(parent.getContext()));
// ... Daftarkan slot lainnya
// 3. Teruskan registri saat mengikat
playerView.attach(controller, model, registry); Penggunaan Lanjutan
Cara Mengimplementasikan Switching Slot Dinamis?
Alihkan implementasi slot secara dinamis saat runtime:
// Dapatkan manajer
ISlotManager slotManager = playerView.getSlotManager();
// Alihkan jenis Surface
registry.register(SlotType.PLAYER_SURFACE,
parent -> new TextureViewSlot(parent.getContext()));
slotManager.rebuildSlots(); Cara Mengimplementasikan Slot Kustom?
AliPlayerKit menyediakan dua cara untuk mengimplementasikan slot kustom. Developer dapat memilih metode yang sesuai berdasarkan kebutuhan mereka.
Metode Satu: Warisi BaseSlot (Direkomendasikan)
Mewarisi BaseSlot adalah metode paling sederhana. Framework mengenkapsulasi logika umum seperti manajemen siklus hidup dan langganan event.
Skenario Penerapan: Sebagian besar slot UI, seperti sampul, bilah kontrol, dan tampilan status.
Buat file layout.
Buat file layout di direktori
res/layout/:<!-- res/layout/my_cover_layout.xml --> <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/cover_image" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" /> </FrameLayout>Buat kelas Slot.
Warisi
BaseSlotdan override metode yang diperlukan:public class MyCoverSlot extends BaseSlot { public MyCoverSlot(@NonNull Context context) { super(context); } @Override protected int getLayoutId() { return R.layout.my_cover_layout; // Kembalikan ID layout } @Override public void onBindData(@NonNull AliPlayerModel model) { // Ikat data ImageView coverImage = findViewByIdCompat(R.id.cover_image); Glide.with(getContext()).load(model.getCoverUrl()).into(coverImage); } @Override public void onUnbindData() { // Bersihkan resource ImageView coverImage = findViewByIdCompat(R.id.cover_image); Glide.with(getContext()).clear(coverImage); } }Daftarkan dan gunakan.
SlotRegistry registry = new SlotRegistry(); registry.register(SlotType.COVER, parent -> new MyCoverSlot(parent.getContext())); playerView.attach(controller, model, registry);
Metode Dua: Implementasikan Antarmuka ISlot
Langsung mengimplementasikan antarmuka ISlot memberikan fleksibilitas lebih besar. Namun, Anda harus mengelola siklus hidup secara manual.
Skenario Penerapan: Memerlukan kontrol penuh atas hierarki tampilan atau perlu mewarisi jenis View tertentu (seperti SurfaceView atau TextureView).
Buat file layout.
Buat file layout di direktori
res/layout/:<!-- res/layout/my_cover_layout.xml --> <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/cover_image" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" /> </FrameLayout>Buat kelas Slot.
Implementasikan antarmuka
ISlotdan dapatkan kemampuan slot dengan menyusunSlotBehavior:public class MySurfaceSlot extends FrameLayout implements ISlot, ISurfaceProvider { // Dapatkan kemampuan inti slot melalui penyusunan private final SlotBehavior slotBehavior = new SlotBehavior(); private SlotHost host; public MySurfaceSlot(@NonNull Context context) { super(context); View.inflate(context, R.layout.my_surface_layout, this); } @Override public void onAttach(@NonNull SlotHost host) { // 1. Delegasikan penanganan siklus hidup ke SlotBehavior slotBehavior.attach(host); // 2. Simpan referensi host this.host = host; // 3. Siapkan Surface setupSurfaceProvider(host); } @Override public void onDetach() { if (host != null) { onSurfaceDestroyed(host); } slotBehavior.detach(); host = null; } @Override public void onBindData(@NonNull AliPlayerModel model) { // Logika pengikatan data } @Override public void onUnbindData() { // Logika pembersihan data } @Override public void setupSurfaceProvider(@Nullable SlotHost host) { // Logika penyiapan Surface } }Daftarkan dan gunakan.
SlotRegistry registry = new SlotRegistry(); registry.register(SlotType.COVER, parent -> new MyCoverSlot(parent.getContext())); playerView.attach(controller, model, registry);
Perbandingan Metode
Fitur | Warisi BaseSlot | Implementasikan Antarmuka ISlot |
Volume kode | Lebih sedikit, fokus hanya pada logika bisnis | Lebih banyak, perlu penanganan siklus hidup manual |
Fleksibilitas | Medium, mewarisi FrameLayout | Tinggi, dapat mewarisi jenis View apa pun |
Siklus hidup | Framework mengelola secara otomatis | Harus mendelegasikan secara manual ke SlotBehavior |
Skenario yang direkomendasikan | Sebagian besar slot UI | Memerlukan jenis tampilan khusus |
Cara Memulihkan UI?
Saat memulihkan UI pemutar dalam proyek, tangani skenario berbeda sebagai berikut:
Skenario Satu: Slot Resmi Tidak Tersedia
Jika komponen pemutar resmi tidak menyediakan jenis slot yang Anda butuhkan, implementasikan sendiri:
Buat slot kustom.
Tempatkan slot kustom di direktori
ui/slots/customuntuk membedakannya dari implementasi resmi:your-module/src/main/java/com/yourpackage/ └── ui/ └── slots/ └── custom/ ├── MyDanmakuSlot.java // Slot komentar langsung ├── MySubtitleSlot.java // Slot takarir └── MyWatermarkSlot.java // Slot watermark
Skenario Dua: Slot Resmi Tersedia Tetapi Tidak Diperlukan
Jika komponen pemutar resmi menyediakan slot tetapi skenario Anda tidak memerlukannya, tangani dengan salah satu dari dua cara berikut:
Metode Satu: Nonaktifkan melalui konfigurasi (direkomendasikan).
Hapus item konfigurasi terkait dalam metode
createDefaultConfigs()dariSlotConstants:// Sebelum modifikasi: SlotRegistry menyuntikkan komponen default configs.add(new SlotConfig.Builder() .type(SlotType.LOG_PANEL) .excludeScenes(createSet(SceneType.MINIMAL)) .condition(AliPlayerKit::isLogPanelEnabled) .build()); // Setelah modifikasi: Beri komentar atau hapus konfigurasi ini; SlotRegistry tidak akan lagi menyuntikkan komponen default // configs.add(new SlotConfig.Builder() // .type(SlotType.LOG_PANEL) // ... // .build());Metode Dua: Penghapusan fisik (tidak direkomendasikan).
Langsung hapus file kelas Slot terkait beserta resource-nya. Hal ini tidak direkomendasikan.
Skenario Tiga: Gaya UI Slot Resmi Tidak Memenuhi Persyaratan
Jika gaya UI slot resmi tidak memenuhi persyaratan Anda, modifikasi langsung file layout XML untuk memulihkan UI. Berkat pemisahan gaya dan logika, modifikasi layout tidak memengaruhi logika bisnis.
Temukan file layout terkait.
File layout berada di direktori
playerkit/src/main/res/layout/, dengan konvensi penamaanlayout_{slot_type}.xml:Jenis Slot
File Tata Letak
TOP_BAR
layout_top_bar_slot.xmlBOTTOM_BAR
layout_bottom_bar_slot.xmlCOVER
layout_cover_slot.xmlPLAY_STATE
layout_play_state_slot.xml…
…
Modifikasi file layout.
Modifikasi langsung atribut gaya dalam file XML. Pastikan ID kontrol tetap tidak berubah:
<!-- Sebelum modifikasi: Gaya default resmi --> <LinearLayout android:background="#80000000" android:padding="8dp"> <ImageView android:id="@+id/iv_back" android:layout_width="40dp" android:layout_height="40dp" /> </LinearLayout> <!-- Setelah modifikasi: Gaya kustom (pertahankan ID) --> <LinearLayout android:background="@drawable/custom_top_bar_bg" android:padding="12dp"> <ImageView android:id="@+id/iv_back" android:layout_width="48dp" android:layout_height="48dp" android:src="@drawable/ic_custom_back" /> </LinearLayout>Batasan arsitektur.
Batasan berikut memastikan kode kustom tidak bertentangan dengan kode resmi:
ID kontrol harus tetap. Kelas Slot menemukan kontrol dan mengikat event menggunakan ID-nya.
Struktur layout dapat disesuaikan, tetapi kontrol inti harus dipertahankan.
Logika bisnis tidak perlu dimodifikasi; secara otomatis menyesuaikan dengan layout baru.
Rekomendasi kompatibilitas upgrade.
Untuk melakukan modifikasi UI secara ekstensif, kami merekomendasikan menggunakan strategi Slot Kustom untuk menghindari konflik selama upgrade AliPlayerKit di masa depan:
Jangan langsung memodifikasi file layout resmi atau kelas Slot.
Buat versi V2 (seperti
TopBarSlotV2.javadanlayout_top_bar_slot_v2.xml).Daftarkan melalui
SlotRegistryatau modifikasiSlotConstantsuntuk mengganti implementasi default.
public class TopBarSlotV2 extends BaseSlot { @Override protected int getLayoutId() { return R.layout.layout_top_bar_slot_v2; } }
Cara Mengimplementasikan Slot Headless?
Sistem Slot mendukung baik komponenisasi komponen UI maupun komponenisasi logika bisnis. Selain slot UI, Anda dapat memperluas fungsionalitas melalui slot headless, yang mengimplementasikan logika bisnis tanpa merender UI apa pun.
Contoh: Slot Manajemen Layar Penuh
FullscreenSlot resmi adalah contoh khas slot headless. Slot ini hanya mengelola logika alih layar penuh dan tidak memiliki kemampuan rendering UI.
public class FullscreenSlot extends BaseSlot {
@Override
protected int getLayoutId() {
return 0; // Kembalikan 0 untuk tanpa layout, tanpa rendering UI
}
@Override
public void onAttach(@NonNull SlotHost host) {
super.onAttach(host);
// Dengarkan event alih layar penuh
}
@Override
protected void onEvent(@NonNull PlayerEvent event) {
if (event instanceof FullscreenEvents.Toggle) {
// Tangani logika alih layar penuh
toggleFullscreen();
}
}
private void toggleFullscreen() {
// Penanganan logika murni: ubah orientasi Activity, sesuaikan UI sistem, dll.
}
}Metode Implementasi:
Metode | Deskripsi | Skenario Penerapan |
Warisi | Sederhana, mewarisi FrameLayout tetapi tidak merender. | Sebagian besar slot headless. |
Implementasikan antarmuka | Fleksibel, sama sekali tidak mewarisi View. | Komponen logika murni. |
Nilai Desain:
Slot headless memberikan keunggulan yang sama dari sistem slot kepada logika bisnis:
Manajemen siklus hidup terpadu.
Sinkronisasi otomatis dengan status pemutar.
Penggantian yang dapat dipasang.
Kolaborasi setara dengan slot UI.
Praktik Terbaik
Manajemen Siklus Hidup
public class MySlot extends BaseSlot {
@Override
public void onAttach(@NonNull SlotHost host) {
super.onAttach(host);
// Inisialisasi tampilan
}
@Override
public void onDetach() {
// Bersihkan resource
super.onDetach();
}
}Langganan Event
@Override
protected List<Class<? extends PlayerEvent>> observedEvents() {
return Arrays.asList(PlayerEvents.StateChanged.class);
}
@Override
protected void onEvent(@NonNull PlayerEvent event) {
if (event instanceof PlayerEvents.StateChanged) {
// Tangani perubahan status
}
}Seleksi Permukaan
Skenario | Jenis yang Direkomendasikan | Alasan |
Pemutaran normal | SurfaceView | Kinerja lebih baik |
Memerlukan animasi | TextureView | Mendukung transformasi |
Pemutaran latar belakang | No Surface | Menghemat resource |
Referensi Contoh
Proyek menyediakan contoh lengkap yang terletak di playerkit-examples/example-slot-system.
Fitur Contoh
Feature | Description |
SurfaceView switching | Cocok untuk skenario pemutaran normal |
TextureView switching | Cocok untuk skenario yang memerlukan animasi |
Empty slot switching | Cocok untuk pemutaran hanya audio |
Jalankan Contoh
Pada Aplikasi Demo, pilih contoh Sistem Slot untuk melihat efeknya.
Referensi API
Antarmuka Inti
Antarmuka/Kelas | Deskripsi |
| Antarmuka slot, mendefinisikan siklus hidup |
| Kelas dasar slot, mengenkapsulasi logika umum |
| Registri layanan, mengelola builder |
| Manajer, menyediakan fungsi rebuild dan kueri |
Metode BaseSlot
Metode | Deskripsi |
| Mengembalikan ID resource layout |
| Mendapatkan host slot |
| Mengontrol visibilitas |
| Menerbitkan event |
Prinsip Teknis
Siklus Hidup
Slot menggunakan sistem siklus hidup ganda:
Siklus Hidup | Deskripsi |
Siklus hidup tampilan |
|
Siklus hidup data |
|
Hubungan dengan Siklus Hidup Android:
Siklus hidup slot bersifat independen dari siklus hidup Activity atau Fragment Android. Untuk melakukan operasi tertentu (seperti menjeda animasi atau menghentikan polling) saat aplikasi beralih antara latar depan dan latar belakang, tangani sebagai berikut:
Metode Satu: Dengarkan Event Status Pemutaran
Dengarkan perubahan status pemutaran menggunakan observedEvents(). Hentikan animasi saat pemutar dijeda:
@Override
protected List<Class<? extends PlayerEvent>> observedEvents() {
return Arrays.asList(PlayerEvents.StateChanged.class);
}
@Override
protected void onEvent(@NonNull PlayerEvent event) {
if (event instanceof PlayerEvents.StateChanged) {
PlayerState state = ((PlayerEvents.StateChanged) event).newState;
if (state == PlayerState.PAUSED || state == PlayerState.STOPPED) {
stopAnimation(); // Jeda animasi
} else if (state == PlayerState.PLAYING) {
startAnimation(); // Lanjutkan animasi
}
}
}Metode Dua: Kontrol di Activity/Fragment
Pada metode onPause() atau onResume() Activity, ambil slot melalui manajer slot dan panggil metodenya:
@Override
protected void onPause() {
super.onPause();
MyAnimationSlot slot = playerView.getSlotManager().getSlot(SlotType.CUSTOM);
if (slot != null) {
slot.pauseAnimation();
}
}
@Override
protected void onResume() {
super.onResume();
MyAnimationSlot slot = playerView.getSlotManager().getSlot(SlotType.CUSTOM);
if (slot != null) {
slot.resumeAnimation();
}
}Aliran Data Unidireksional
Sistem Slot menggunakan arsitektur aliran data unidireksional. Slot tidak menyimpan referensi langsung ke controller. Sebaliknya, mereka berinteraksi secara terurai melalui antarmuka yang disediakan oleh host:
Controller → State / Event → Slot (Status mengalir ke bawah, read-only)
Slot → Command → Controller (Perintah mengalir ke atas, hanya kirim)Status mengalir ke bawah: Dua cara untuk mendapatkan status.
Kueri aktif: Kueri status saat ini menggunakan
getPlayerStateStore().Dengarkan pasif: Terima notifikasi perubahan status dengan berlangganan event.
Perintah mengalir ke atas: Slot mengirim perintah menggunakan
postEvent(). Controller mengeksekusinya, dan slot tidak bergantung pada detail eksekusi.
Kueri status aktif: Dapatkan akses read-only ke status pemutar menggunakan host.getPlayerStateStore():
@Override
public void onAttach(@NonNull SlotHost host) {
super.onAttach(host);
// Kueri status pemutaran
PlayerState state = host.getPlayerStateStore().getPlayState();
// Kueri posisi pemutaran saat ini dan durasi total
long position = host.getPlayerStateStore().getCurrentPosition();
long duration = host.getPlayerStateStore().getDuration();
}Dengarkan status secara pasif: Terima perubahan status dengan berlangganan event:
@Override
protected List<Class<? extends PlayerEvent>> observedEvents() {
return Arrays.asList(
PlayerEvents.StateChanged.class, // Perubahan status pemutaran
PlayerEvents.Info.class // Pembaruan progres pemutaran
);
}
@Override
protected void onEvent(@NonNull PlayerEvent event) {
if (event instanceof PlayerEvents.StateChanged) {
// Tangani perubahan status pemutaran
updatePlayPauseIcon(((PlayerEvents.StateChanged) event).newState);
} else if (event instanceof PlayerEvents.Info) {
// Tangani pembaruan progres pemutaran
PlayerEvents.Info info = (PlayerEvents.Info) event;
updateProgress(info.currentPosition, info.bufferedPosition, info.duration);
}
}Kirim perintah: Kirim event perintah menggunakan postEvent() untuk memicu perilaku pemutar:
// Alihkan putar/jeda
postEvent(new PlayerCommand.Toggle(mPlayerId));
// Cari ke posisi tertentu
postEvent(new PlayerCommand.Seek(mPlayerId, 30000));
// Atur kecepatan pemutaran
postEvent(new PlayerCommand.SetSpeed(mPlayerId, 1.5f));
// Ambil tangkapan layar
postEvent(new PlayerCommand.Snapshot(mPlayerId));Arsitektur aliran data unidireksional sepenuhnya menguraikan keterkaitan antara slot dan controller. Slot tidak perlu menyimpan referensi controller. Mereka hanya perlu fokus pada kueri status dan pengiriman perintah, mencapai pemisahan tanggung jawab yang jelas.
Isolasi Horizontal dan Spesifikasi Event Antar Slot
Untuk memastikan stabilitas arsitektur, slot tidak boleh langsung mendapatkan instance slot lain atau melakukan komunikasi titik-ke-titik.
Saat Slot A (seperti slot overlay pengaturan) memicu perubahan status yang memengaruhi Slot B (seperti slot bilah kontrol bawah):
Event bubbling: Slot A menggunakan
postEvent()untuk meneruskan event keController.State sinking:
ControlleratauStateStoremenangani perubahan status secara terpusat dan menyiarkan status baru ke semua slot yang berlangganan.
Tautan berbentuk U ini (**event bubbling, state sinking**) menghindari coupling mesh dan risiko deadlock.
Petunjuk intersepsi event: Kontainer slot yang menutupi lapisan atas halaman harus menangani pengiriman event gesture untuk menghindari pemblokiran deteksi gesture oleh GestureControlSlot yang berada di bawahnya.
FAQ
Kapan onAttach dipanggil?
Terpicu saat Anda memanggil playerView.attach() atau slotManager.rebuildSlots().
Pertimbangan Slot Kustom?
Panggil
super.onAttach(host)dionAttach.Bersihkan resource sebelum
onDetach.
Cara Men-debug?
Gunakan LogHub untuk melihat log. Format TAG adalah ClassName.BaseSlot.
Contoh Crash Umum
Berikut adalah masalah paling umum yang menyebabkan crash menurut laporan pelanggan. Hindari secara ketat:
Error 1: Tidak Memanggil super.onAttach() Menyebabkan Gangguan Siklus Hidup
Kode salah:
public class MySlot extends BaseSlot {
@Override
public void onAttach(@NonNull SlotHost host) {
// Lupa memanggil super.onAttach(host)
// Langsung inisialisasi tampilan
ImageView iv = findViewByIdCompat(R.id.iv_icon);
iv.setOnClickListener(v -> postEvent(...)); // NullPointerException!
}
} Kode benar:
@Override
public void onAttach(@NonNull SlotHost host) {
super.onAttach(host); // Harus dipanggil terlebih dahulu
// Kemudian inisialisasi tampilan
ImageView iv = findViewByIdCompat(R.id.iv_icon);
iv.setOnClickListener(v -> postEvent(...));
} Penyebab crash: Jika super.onAttach(host) tidak dipanggil, slotBehavior tidak dapat diinisialisasi, event tidak dapat berlangganan, dan Surface tidak dapat diatur. Hal ini menyebabkan getHost() mengembalikan null, postEvent() gagal, dan langganan event menjadi tidak valid.
Error 2: Timpa ID Saat XML Kustom Menyebabkan NullPointerException
Kode salah:
<!-- Saat menyesuaikan layout, ID bertentangan dengan ID default resmi -->
<LinearLayout ...>
<!-- Resmi menggunakan @+id/iv_back, dan Anda menimpanya -->
<ImageView
android:id="@+id/iv_back"
android:src="@drawable/my_icon" /> <!-- Ekspektasi resmi adalah tombol kembali -->
</LinearLayout> Praktik benar:
Pertahankan ID kontrol inti tidak berubah: ID kontrol yang digunakan oleh kelas Slot harus dipertahankan dalam layout.
Pencocokan jenis kontrol: Jenis kontrol yang sesuai dengan ID harus cocok dengan jenis dalam kode.
<!-- Pertahankan ID inti tidak berubah -->
<LinearLayout ...>
<ImageView
android:id="@+id/iv_back" <!-- Pertahankan ID, tetapi Anda dapat mengubah gaya -->
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/custom_back" /> <!-- Anda dapat mengubah ikon -->
</LinearLayout> Penyebab crash: Jika layout tidak memiliki ID kontrol yang dibutuhkan oleh kelas Slot (R.id.iv_back) atau jenis kontrol salah, findViewByIdCompat() menyebabkan cast gagal atau pengikatan event klik salah.
Error 3: Memanggil getHost() di Konstruktor
Kode salah:
public class MySlot extends BaseSlot {
public MySlot(@NonNull Context context) {
super(context);
// getHost() mengembalikan null di konstruktor
SlotHost host = getHost(); // null!
}
}Kode benar:
@Override
public void onAttach(@NonNull SlotHost host) {
super.onAttach(host);
// Host hanya dapat diakses setelah onAttach
SlotHost host = getHost(); // Diperoleh secara normal
}Penyebab crash: Instance slot belum terikat ke host selama konstruksi. Hal ini menyebabkan getHost() mengembalikan null.
Error 4: Tidak Membersihkan Resource di onDetach Menyebabkan Kebocoran Memori
Kode salah:
public class MySlot extends BaseSlot {
private Handler handler = new Handler();
@Override
public void onAttach(@NonNull SlotHost host) {
super.onAttach(host);
handler.postDelayed(() -> updateUI(), 1000); // Tugas tertunda
}
@Override
public void onDetach() {
// Lupa menghapus tugas tertunda
super.onDetach();
}
}Kode benar:
@Override
public void onDetach() {
handler.removeCallbacksAndMessages(null); // Bersihkan semua tugas tertunda
super.onDetach();
}Penyebab crash: Jika tugas tertunda menyimpan referensi Slot, memori tidak dapat dikembalikan (kebocoran memori). Selain itu, Slot mungkin sudah berada dalam status detached saat tugas dieksekusi.
Error 5: Langganan Event Tidak Dideklarasikan di observedEvents()
Kode salah:
public class MySlot extends BaseSlot {
@Override
public void onAttach(@NonNull SlotHost host) {
super.onAttach(host);
// Diharapkan menerima event, tetapi tidak dideklarasikan di observedEvents()
}
@Override
protected void onEvent(@NonNull PlayerEvent event) {
// Tidak pernah dipanggil!
}
}Kode benar:
@Override
protected List<Class<? extends PlayerEvent>> observedEvents() {
return Arrays.asList(
PlayerEvents.StateChanged.class, // Deklarasikan event yang akan berlangganan
PlayerEvents.Info.class
);
}
@Override
protected void onEvent(@NonNull PlayerEvent event) {
// Sekarang event dapat diterima secara normal
}Penyebab crash: BaseSlot mendeklarasikan event yang akan berlangganan melalui observedEvents(). Jika tidak dideklarasikan, tidak akan berlangganan.