Le système de slots constitue une architecture fondamentale d'AliPlayerKit. Il décompose l'interface utilisateur du lecteur en composants indépendants et enfichables grâce à la modularisation. Un système unifié gère ces composants afin d'assurer le découplage de l'interface utilisateur, une composition flexible et une personnalisation extensible.
Introduction aux concepts
Qu'est-ce que le système de slots ?
Le système de slots représente l'une des architectures centrales d'AliPlayerKit. Il s'agit d'un mécanisme de gestion de plugins dédié aux composants d'interface utilisateur.
Il permet aux développeurs d'ajouter, de supprimer ou de remplacer dynamiquement des composants d'interface utilisateur à n'importe quel emplacement de l'interface du lecteur, offrant ainsi une personnalisation poussée de l'expérience visuelle.
Qu'est-ce qu'un slot ?
Un slot est un conteneur de vue réservé qui héberge un ensemble de composants d'interface utilisateur connexes.
Chaque slot dispose de son propre cycle de vie, de sa logique d'affichage et de masquage, ainsi que de sa gestion des événements. Il peut afficher ou masquer automatiquement les composants internes en fonction de conditions telles que l'état de la lecture ou le type de scénario.
Fonctionnalités
Problèmes résolus
Couplage étroit entre les composants d'interface utilisateur et la logique du lecteur.
La personnalisation de l'interface nécessite la modification du code source du framework.
Flexibilité limitée lors de la combinaison de composants dans différents scénarios.
Gestion complexe du cycle de vie des composants.
Valeur principale
Le système de slots décompose indépendamment les composants d'interface utilisateur du lecteur. Les développeurs peuvent choisir ceux qu'ils souhaitent utiliser et définir leur configuration.
|
Utilisation |
Description |
Avantages |
|
Utiliser l'interface par défaut |
Le composant du lecteur utilise l'interface utilisateur officielle par défaut |
Simplifie l'intégration, réduit les coûts d'intégration et permet une intégration low-code. |
|
Utilisation personnalisée |
Remplacer certaines ou toutes les implémentations de slots |
Répond à diverses exigences d'interface utilisateur enrichie. |
|
Ne pas utiliser les slots |
Utiliser uniquement les capacités de lecture |
Scénarios de lecture pure sans dépendances d'interface utilisateur. |
Avantages architecturaux :
Découplage : les composants d'interface utilisateur sont séparés de la logique centrale du lecteur, avec des responsabilités clairement définies.
Flexibilité : combinez et remplacez dynamiquement les composants d'interface utilisateur au moment de l'exécution.
Extensibilité : personnalisez l'interface utilisateur sans modifier le framework, ce qui offre une grande extensibilité.
Séparation des préoccupations : les styles d'interface utilisateur (dispositions XML) sont distincts de la logique métier (gestion de la classe Slot).
Capacités principales
|
Capacité |
Description |
|
Combinaison dynamique |
Combinez librement les composants d'interface utilisateur au moment de l'exécution. |
|
Remplacement à chaud |
Remplacez les implémentations de composants sans redémarrage. |
|
Adaptation au scénario |
Basculez automatiquement le comportement de l'interface utilisateur pour différents scénarios de lecture. |
Composants intégrés
AliPlayerKit fournit un ensemble de composants intégrés prêts à l'emploi qui couvrent les besoins courants en matière d'interface utilisateur de lecteur.
Types de slots
|
Type de slot |
Description |
Implémentation par défaut |
|
PLAYER_SURFACE |
Affichage vidéo, prend en charge diverses vues de rendu |
DisplayViewSlot / SurfaceViewSlot / TextureViewSlot |
|
FULLSCREEN |
Gestion du plein écran, gère le basculement de l'orientation de l'écran |
FullscreenSlot |
|
GESTURE_CONTROL |
Contrôle gestuel, gère les clics, doubles clics, appuis longs et glissements |
GestureControlSlot |
|
LANDSCAPE_HINT |
Indication de visionnage en mode paysage, guide les utilisateurs vers le plein écran |
LandscapeHintSlot |
|
COVER |
Image de couverture, affiche la vignette vidéo avant la lecture |
CoverSlot |
|
CENTER_DISPLAY |
Affichage central, indique l'état tel que la vitesse de lecture, la luminosité ou le volume |
CenterDisplaySlot |
|
PLAY_STATE |
État de lecture, affiche les invites de chargement ou d'erreur, etc. |
PlayStateSlot |
|
LOG_PANEL |
Panneau de journaux, affiche les journaux du lecteur pendant le débogage |
LogPanelSlot |
|
TOP_BAR |
Barre de contrôle supérieure, affiche le retour, le titre, les paramètres, etc. |
TopBarSlot |
|
BOTTOM_BAR |
Barre de contrôle inférieure, affiche les commandes de lecture, la barre de progression, etc. |
BottomBarSlot |
|
SETTING_MENU |
Menu des paramètres, affiche les réglages tels que la vitesse de lecture, la définition ou la mise en miroir |
SettingMenuSlot |
Adaptation au scénario
AliPlayerKit définit cinq scénarios de lecture. Le comportement des slots s'adapte automatiquement à chaque scénario :
|
Scénario |
Description |
Applications typiques |
|
VOD |
Scénario de vidéo à la demande, prend en charge toutes les fonctionnalités. |
Lecture vidéo standard. |
|
LIVE |
Scénario de diffusion en direct, opérations sur la timeline désactivées. |
Flux en direct en temps réel. |
|
VIDEO_LIST |
Scénario de lecture en liste, gestes verticaux désactivés. |
Fils d'actualités, listes de vidéos courtes. |
|
RESTRICTED |
Scénario de lecture restreinte, limite le positionnement. |
Formation éducative, surveillance d'examens. |
|
MINIMAL |
Scénario de lecture minimaliste, affiche uniquement l'écran vidéo. |
Vidéos d'arrière-plan, vidéos décoratives. |
Règles de visibilité des slots :
|
Slot |
VOD |
LIVE |
VIDEO_LIST |
RESTRICTED |
MINIMAL |
|
PLAYER_SURFACE |
✓ |
✓ |
✓ |
✓ |
✓ |
|
FULLSCREEN |
✓ |
✓ |
✓ |
✓ |
✓ |
|
GESTURE_CONTROL |
✓ |
✓ |
✓ |
✓ |
✗ |
|
LANDSCAPE_HINT |
✓ |
✓ |
✓ |
✓ |
✗ |
|
COVER |
✓ |
✗ |
✓ |
✗ |
✗ |
|
CENTER_DISPLAY |
✓ |
✓ |
✓ |
✓ |
✗ |
|
PLAY_STATE |
✓ |
✓ |
✓ |
✓ |
✓ |
|
LOG_PANEL |
Configurable |
Configurable |
Configurable |
Configurable |
✗ |
|
TOP_BAR |
✓ |
✓ |
✓ |
✓ |
✗ |
|
BOTTOM_BAR |
✓ |
✓ |
✓ |
✓ |
✗ |
|
SETTING_MENU |
✓ |
✓ |
✓ |
✓ |
✗ |
Différences de comportement gestuel :
|
Geste |
VOD |
LIVE |
VIDEO_LIST |
RESTRICTED |
MINIMAL |
|
Clic |
Afficher/masquer la barre de contrôle |
Afficher/masquer la barre de contrôle |
Afficher/masquer la barre de contrôle |
Afficher/masquer la barre de contrôle |
Désactivé |
|
Double-clic |
Basculer lecture/pause |
Basculer lecture/pause |
Basculer lecture/pause |
Basculer lecture/pause |
Désactivé |
|
Appui long |
Lecture à vitesse 2× |
Désactivé |
Lecture à vitesse 2× |
Désactivé |
Désactivé |
|
Glissement horizontal |
Navigation dans la progression |
Désactivé |
Navigation dans la progression |
Désactivé |
Désactivé |
|
Glissement vertical gauche |
Ajuster la luminosité |
Ajuster la luminosité |
Désactivé |
Ajuster la luminosité |
Désactivé |
|
Glissement vertical droit |
Ajuster le volume |
Ajuster le volume |
Désactivé |
Ajuster le volume |
Désactivé |
Différences de la barre de contrôle inférieure :
|
Contrôle |
VOD |
LIVE |
VIDEO_LIST |
RESTRICTED |
MINIMAL |
|
Bouton lecture/pause |
✓ |
✓ |
✓ |
✓ |
✗ |
|
Barre de progression |
Glissable |
Non glissable |
Glissable |
Non glissable |
✗ |
|
Affichage du temps |
✓ |
✓ |
✓ |
✓ |
✗ |
|
Bouton d'actualisation |
✗ |
✓ |
✗ |
✗ |
✗ |
|
Bouton plein écran |
✓ |
✓ |
✓ |
✓ |
✗ |
Utilisation de base
Le système de slots propose trois stratégies d'utilisation. Les développeurs peuvent sélectionner la méthode appropriée en fonction de leurs besoins :
|
Stratégie |
Description |
Scénarios applicables |
|
Utiliser l'interface par défaut |
Méthode la plus simple ; le composant du lecteur utilise l'interface par défaut. |
Intégration rapide, scénarios de lecture standard. |
|
Personnaliser certains slots |
Personnalisez uniquement des slots spécifiques ; les autres utilisent l'interface par défaut. |
Personnalisation partielle, conservation des interactions par défaut. |
|
Personnaliser entièrement l'interface |
Personnalisez tous les slots pour créer une interface de lecteur entièrement personnalisée. |
Personnalisation approfondie, interface utilisateur spécifique à la marque. |
Stratégie un : utiliser les configurations par défaut
Approche la plus simple. Utilisez directement les configurations de slots intégrées :
// 1. Get the player view
AliPlayerView playerView = findViewById(R.id.player_view);
// 2. Create the controller and playback data
AliPlayerController controller = new AliPlayerController(this);
AliPlayerModel model = new AliPlayerModel.Builder()
.videoSource(videoSource)
.build();
// 3. Bind to the view (automatically uses default slots)
playerView.attach(controller, model);
Stratégie deux : personnaliser certains slots
Personnalisez uniquement des slots spécifiques ; les autres conservent l'interface par défaut. Par exemple, personnalisez uniquement la barre supérieure :
// 1. Create a registry
SlotRegistry registry = new SlotRegistry();
// 2. Register only the slots to customize
registry.register(SlotType.TOP_BAR, parent -> new MyTopBarSlot(parent.getContext()));
// 3. Pass the registry during binding (unregistered slots use default implementation)
playerView.attach(controller, model, registry);
Stratégie trois : personnaliser entièrement l'interface
Personnalisez tous les slots pour créer une interface de lecteur entièrement personnalisée :
// 1. Create a registry
SlotRegistry registry = new SlotRegistry();
// 2. Register all slots
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()));
// ... Register other slots
// 3. Pass the registry during binding
playerView.attach(controller, model, registry);
Utilisation avancée
Comment implémenter le basculement dynamique des slots ?
Basculez dynamiquement les implémentations de slots au moment de l'exécution :
// Get the manager
ISlotManager slotManager = playerView.getSlotManager();
// Switch Surface type
registry.register(SlotType.PLAYER_SURFACE,
parent -> new TextureViewSlot(parent.getContext()));
slotManager.rebuildSlots();
Comment implémenter des slots personnalisés ?
AliPlayerKit propose deux méthodes pour implémenter des slots personnalisés. Les développeurs peuvent choisir l'approche la mieux adaptée à leurs exigences.
Méthode un : hériter de BaseSlot (recommandée)
L'héritage de BaseSlot constitue la méthode la plus simple. Le framework encapsule la logique courante telle que la gestion du cycle de vie et l'abonnement aux événements.
Scénarios applicables : la plupart des slots d'interface utilisateur, tels que les couvertures, les barres de contrôle et les affichages d'état.
-
Créez le fichier de disposition.
Créez le fichier de disposition dans le répertoire
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> -
Créez la classe Slot.
Héritez de
BaseSlotet remplacez les méthodes nécessaires :public class MyCoverSlot extends BaseSlot { public MyCoverSlot(@NonNull Context context) { super(context); } @Override protected int getLayoutId() { return R.layout.my_cover_layout; // Return the layout ID } @Override public void onBindData(@NonNull AliPlayerModel model) { // Bind data ImageView coverImage = findViewByIdCompat(R.id.cover_image); Glide.with(getContext()).load(model.getCoverUrl()).into(coverImage); } @Override public void onUnbindData() { // Clean up resources ImageView coverImage = findViewByIdCompat(R.id.cover_image); Glide.with(getContext()).clear(coverImage); } } -
Enregistrez et utilisez.
SlotRegistry registry = new SlotRegistry(); registry.register(SlotType.COVER, parent -> new MyCoverSlot(parent.getContext())); playerView.attach(controller, model, registry);
Méthode deux : implémenter l'interface ISlot
L'implémentation directe de l'interface ISlot offre une plus grande flexibilité. Toutefois, vous devez gérer manuellement le cycle de vie.
Scénarios applicables : nécessite un contrôle total sur la hiérarchie des vues ou l'héritage de types de vues spécifiques (tels que SurfaceView ou TextureView).
-
Créez le fichier de disposition.
Créez le fichier de disposition dans le répertoire
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> -
Créez la classe Slot.
Implémentez l'interface
ISlotet obtenez les capacités de slot en composantSlotBehavior:public class MySurfaceSlot extends FrameLayout implements ISlot, ISurfaceProvider { // Gain core slot capabilities through composition 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. Delegate lifecycle handling to SlotBehavior slotBehavior.attach(host); // 2. Save host reference this.host = host; // 3. Set up Surface setupSurfaceProvider(host); } @Override public void onDetach() { if (host != null) { onSurfaceDestroyed(host); } slotBehavior.detach(); host = null; } @Override public void onBindData(@NonNull AliPlayerModel model) { // Data binding logic } @Override public void onUnbindData() { // Data cleanup logic } @Override public void setupSurfaceProvider(@Nullable SlotHost host) { // Surface setup logic } } -
Enregistrez et utilisez.
SlotRegistry registry = new SlotRegistry(); registry.register(SlotType.COVER, parent -> new MyCoverSlot(parent.getContext())); playerView.attach(controller, model, registry);
Comparaison des méthodes
|
Fonctionnalité |
Hériter de BaseSlot |
Implémenter l'interface ISlot |
|
Volume de code |
Moins important, concentrez-vous uniquement sur la logique métier |
Plus important, gestion manuelle du cycle de vie requise |
|
Flexibilité |
Moyenne, hérite de FrameLayout |
Élevée, peut hériter de n'importe quel type de vue |
|
Cycle de vie |
Géré automatiquement par le framework |
Doit être délégué manuellement à SlotBehavior |
|
Scénarios recommandés |
La plupart des slots d'interface utilisateur |
Nécessite des types de vues spéciaux |
Comment restaurer l'interface utilisateur ?
Lorsque vous restaurez l'interface utilisateur du lecteur dans un projet, traitez les différents scénarios comme suit :
Scénario 1 : Slots officiels non fournis
Si le composant officiel du lecteur ne propose pas le type de slot dont vous avez besoin, implémentez-le vous-même :
-
Créez des slots personnalisés.
Placez les slots personnalisés dans le répertoire
ui/slots/customafin de les distinguer des implémentations officielles :your-module/src/main/java/com/yourpackage/ └── ui/ └── slots/ └── custom/ ├── MyDanmakuSlot.java // Live comment slot ├── MySubtitleSlot.java // Caption slot └── MyWatermarkSlot.java // Watermark slot
Scénario 2 : Slots officiels fournis mais non nécessaires
Si le composant officiel du lecteur fournit des slots que votre scénario n'exige pas, gérez cette situation de l'une des deux manières suivantes :
-
Méthode 1 : Désactivation via la configuration (recommandée).
Supprimez l'élément de configuration correspondant dans la méthode
createDefaultConfigs()deSlotConstants:// Before modification: SlotRegistry injects default components configs.add(new SlotConfig.Builder() .type(SlotType.LOG_PANEL) .excludeScenes(createSet(SceneType.MINIMAL)) .condition(AliPlayerKit::isLogPanelEnabled) .build()); // After modification: Comment out or delete this configuration; SlotRegistry will no longer inject default components // configs.add(new SlotConfig.Builder() // .type(SlotType.LOG_PANEL) // ... // .build()); -
Méthode 2 : Suppression physique (non recommandée).
Supprimez directement le fichier de classe Slot correspondant ainsi que ses ressources associées. Cette approche est déconseillée.
Scénario 3 : Le style de l'interface utilisateur du slot officiel ne répond pas aux exigences
Si le style de l'interface utilisateur du slot officiel ne correspond pas à vos attentes, modifiez directement le fichier de mise en page XML pour restaurer l'interface. Grâce à la séparation entre le style et la logique, les modifications de mise en page n'affectent pas la logique métier.
-
Localisez le fichier de mise en page correspondant.
Les fichiers de mise en page se trouvent dans le répertoire
playerkit/src/main/res/layout/, avec une convention de nommagelayout_{slot_type}.xml:Type de slot
Fichier de mise en page
TOP_BAR
layout_top_bar_slot.xmlBOTTOM_BAR
layout_bottom_bar_slot.xmlCOVER
layout_cover_slot.xmlPLAY_STATE
layout_play_state_slot.xml…
…
-
Modifiez le fichier de mise en page.
Modifiez directement les attributs de style dans le fichier XML. Veillez à ce que les ID des contrôles restent inchangés :
<!-- Before modification: Official default style --> <LinearLayout android:background="#80000000" android:padding="8dp"> <ImageView android:id="@+id/iv_back" android:layout_width="40dp" android:layout_height="40dp" /> </LinearLayout> <!-- After modification: Custom style (keep ID unchanged) --> <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> -
Contraintes architecturales.
Les contraintes suivantes garantissent que le code personnalisé n'entre pas en conflit avec le code officiel :
Les ID des contrôles doivent être fixes. La classe Slot utilise ces ID pour localiser les contrôles et lier les événements.
La structure de mise en page est ajustable, mais les contrôles principaux doivent être conservés.
Aucune modification de la logique métier n'est requise ; celle-ci s'adapte automatiquement à la nouvelle mise en page.
-
Recommandations de compatibilité pour les mises à niveau.
Pour effectuer des modifications importantes de l'interface utilisateur, nous vous recommandons d'utiliser la stratégie de slot personnalisé afin d'éviter les conflits lors des futures mises à niveau d'AliPlayerKit :
Ne modifiez pas directement les fichiers de mise en page officiels ni les classes Slot.
Créez des versions V2 (par exemple
TopBarSlotV2.javaetlayout_top_bar_slot_v2.xml).Enregistrez-les via
SlotRegistryou modifiezSlotConstantspour remplacer l'implémentation par défaut.
public class TopBarSlotV2 extends BaseSlot { @Override protected int getLayoutId() { return R.layout.layout_top_bar_slot_v2; } }
Comment implémenter des slots sans interface (headless) ?
Le système de slots prend en charge à la fois la modularisation des composants d'interface utilisateur et celle de la logique métier. Outre les slots d'interface utilisateur, vous pouvez étendre les fonctionnalités via des slots headless, qui implémentent la logique métier sans afficher d'interface graphique.
Exemple : Slot de gestion du plein écran
Le FullscreenSlot officiel est un slot headless typique. Il gère uniquement la logique de basculement en plein écran et ne possède aucune capacité de rendu d'interface utilisateur.
public class FullscreenSlot extends BaseSlot {
@Override
protected int getLayoutId() {
return 0; // Return 0 for no layout, no UI rendering
}
@Override
public void onAttach(@NonNull SlotHost host) {
super.onAttach(host);
// Listen for fullscreen toggle events
}
@Override
protected void onEvent(@NonNull PlayerEvent event) {
if (event instanceof FullscreenEvents.Toggle) {
// Handle fullscreen toggle logic
toggleFullscreen();
}
}
private void toggleFullscreen() {
// Pure logic handling: modify Activity orientation, adjust system UI, etc.
}
}
Méthodes d'implémentation :
|
Méthode |
Description |
Scénarios applicables |
|
Héritez de |
Simplicité : hérite de FrameLayout mais n'effectue aucun rendu. |
La plupart des slots headless. |
|
Implémentez l'interface |
Flexibilité : n'hérite pas du tout de View. |
Composants purement logiques. |
Valeur de conception :
Les slots headless offrent à la logique métier les mêmes avantages que le système de slots :
Gestion unifiée du cycle de vie.
Synchronisation automatique avec l'état du lecteur.
Remplacement modulaire.
Collaboration équivalente avec les slots d'interface utilisateur.
Bonnes pratiques
Gestion du cycle de vie
public class MySlot extends BaseSlot {
@Override
public void onAttach(@NonNull SlotHost host) {
super.onAttach(host);
// Initialize view
}
@Override
public void onDetach() {
// Clean up resources
super.onDetach();
}
}
Abonnement aux événements
@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) {
// Handle state changes
}
}
Sélection de Surface
|
Scénario |
Type recommandé |
Raison |
|
Lecture normale |
SurfaceView |
Meilleures performances |
|
Nécessite des animations |
TextureView |
Prend en charge les transformations |
|
Lecture en arrière-plan |
Aucune Surface |
Économie de ressources |
Exemple de référence
Le projet fournit un exemple complet situé dans playerkit-examples/example-slot-system.
Fonctionnalités de l'exemple
|
Fonctionnalité |
Description |
|
Commutation SurfaceView |
Adapté aux scénarios de lecture normale |
|
Commutation TextureView |
Adapté aux scénarios nécessitant des animations |
|
Commutation vers un slot vide |
Adapté à la lecture audio uniquement |
Exécuter l'exemple
Dans l'application de démonstration, sélectionnez l'exemple Slot System pour visualiser le résultat.
Référence API
Interfaces principales
|
Interface/Classe |
Description |
|
|
Interface Slot, définit le cycle de vie |
|
|
Classe de base Slot, encapsule la logique commune |
|
|
Registre de service, gère les constructeurs |
|
|
Gestionnaire, fournit des fonctions de reconstruction et de requête |
Méthodes de BaseSlot
|
Méthode |
Description |
|
|
Renvoie l'ID de ressource de mise en page |
|
|
Obtient l'hôte du slot |
|
|
Contrôle la visibilité |
|
|
Publie des événements |
Principes techniques
Cycle de vie
Les slots utilisent un système de double cycle de vie :
|
Cycle de vie |
Description |
|
Cycle de vie de la vue |
|
|
Cycle de vie des données |
|
Relation avec le cycle de vie Android :
Le cycle de vie du slot est indépendant du cycle de vie de l'Activity ou du Fragment Android. Pour effectuer des opérations spécifiques (comme mettre en pause les animations ou arrêter le sondage) lorsque l'application passe entre le premier plan et l'arrière-plan, procédez comme suit :
Méthode 1 : Écouter les événements d'état de lecture
Écoutez les changements d'état de lecture en utilisant observedEvents(). Arrêtez les animations lorsque le lecteur est en pause :
@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(); // Pause animation
} else if (state == PlayerState.PLAYING) {
startAnimation(); // Resume animation
}
}
}
Méthode 2 : Contrôle dans Activity/Fragment
Dans les méthodes onPause() ou onResume() de l'Activity, récupérez le slot via le gestionnaire de slots et appelez ses méthodes :
@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();
}
}
Flux de données unidirectionnel
Le système de slots utilise une architecture de flux de données unidirectionnel. Les slots ne conservent pas de références directes aux contrôleurs. Ils interagissent de manière découplée via les interfaces fournies par l'hôte :
Controller → State / Event → Slot (State flows down, read-only)
Slot → Command → Controller (Commands flow up, send-only)
-
L'état descend : Deux façons d'obtenir l'état.
Requête active : Interrogez l'état actuel en utilisant
getPlayerStateStore().Écoute passive : Recevez les notifications de changement d'état en vous abonnant aux événements.
Les commandes montent : Les slots envoient des commandes en utilisant
postEvent(). Le contrôleur les exécute, et le slot ne dépend pas des détails d'exécution.
Interroger activement l'état : Obtenez un accès en lecture seule à l'état du lecteur en utilisant host.getPlayerStateStore() :
@Override
public void onAttach(@NonNull SlotHost host) {
super.onAttach(host);
// Query playback state
PlayerState state = host.getPlayerStateStore().getPlayState();
// Query current playback position and total duration
long position = host.getPlayerStateStore().getCurrentPosition();
long duration = host.getPlayerStateStore().getDuration();
}
Écouter passivement l'état : Recevez les changements d'état en vous abonnant aux événements :
@Override
protected List<Class<? extends PlayerEvent>> observedEvents() {
return Arrays.asList(
PlayerEvents.StateChanged.class, // Playback state changes
PlayerEvents.Info.class // Playback progress updates
);
}
@Override
protected void onEvent(@NonNull PlayerEvent event) {
if (event instanceof PlayerEvents.StateChanged) {
// Handle playback state changes
updatePlayPauseIcon(((PlayerEvents.StateChanged) event).newState);
} else if (event instanceof PlayerEvents.Info) {
// Handle playback progress updates
PlayerEvents.Info info = (PlayerEvents.Info) event;
updateProgress(info.currentPosition, info.bufferedPosition, info.duration);
}
}
Envoyer des commandes : Envoyez des événements de commande en utilisant postEvent() pour déclencher le comportement du lecteur :
// Toggle play/pause
postEvent(new PlayerCommand.Toggle(mPlayerId));
// Seek to a specific position
postEvent(new PlayerCommand.Seek(mPlayerId, 30000));
// Set playback speed
postEvent(new PlayerCommand.SetSpeed(mPlayerId, 1.5f));
// Take a screenshot
postEvent(new PlayerCommand.Snapshot(mPlayerId));
L'architecture de flux de données unidirectionnel découple complètement les slots des contrôleurs. Les slots n'ont pas besoin de conserver de références au contrôleur. Ils se concentrent uniquement sur l'interrogation de l'état et l'envoi de commandes, réalisant ainsi une séparation claire des responsabilités.
Isolation horizontale et norme d'événements entre les slots
Pour garantir la stabilité architecturale, les slots ne doivent pas obtenir directement des instances d'autres slots ni engager de communication point à point.
Lorsque le slot A (tel qu'un slot de superposition de paramètres) déclenche un changement d'état qui affecte le slot B (tel qu'un slot de barre de contrôle inférieure) :
Bouillonnement d'événements : Le slot A utilise
postEvent()pour transmettre l'événement auController.Descente d'état : Le
Controllerou leStateStoregère centralement les changements d'état et diffuse le nouvel état à tous les slots en écoute.
Ce lien en forme de U (bouillonnement d'événements, descente d'état) évite le couplage en maille et les risques d'interblocage.
Conseil d'interception d'événements : Les conteneurs de slots couvrant la couche supérieure de la page doivent gérer la distribution des événements gestuels pour éviter de bloquer la détection des gestes par le GestureControlSlot sous-jacent.
FAQ
Quand onAttach est-il appelé ?
Il se déclenche lorsque vous appelez playerView.attach() ou slotManager.rebuildSlots().
Considérations pour les slots personnalisés ?
Appelez
super.onAttach(host)dansonAttach.Nettoyez les ressources avant
onDetach.
Comment déboguer ?
Utilisez LogHub pour consulter les journaux. Le format du TAG est ClassName.BaseSlot.
Exemples courants de plantage
Voici les problèmes de plantage les plus fréquemment signalés par les clients. Évitez-les strictement :
Erreur 1 : Ne pas appeler super.onAttach() entraîne une perturbation du cycle de vie
Code incorrect :
public class MySlot extends BaseSlot {
@Override
public void onAttach(@NonNull SlotHost host) {
// Forgot to call super.onAttach(host)
// Directly initialize the view
ImageView iv = findViewByIdCompat(R.id.iv_icon);
iv.setOnClickListener(v -> postEvent(...)); // NullPointerException!
}
}
Code correct :
@Override
public void onAttach(@NonNull SlotHost host) {
super.onAttach(host); // Must be called first
// Then initialize the view
ImageView iv = findViewByIdCompat(R.id.iv_icon);
iv.setOnClickListener(v -> postEvent(...));
}
Cause du plantage : Si super.onAttach(host) n'est pas appelé, slotBehavior ne peut pas s'initialiser, les événements ne peuvent pas être abonnés et la Surface ne peut pas être définie. Cela entraîne le retour de null par getHost(), l'échec de postEvent() et l'invalidation des abonnements aux événements.
Erreur 2 : L'écrasement d'ID lors de la personnalisation XML entraîne une NullPointerException
Code incorrect :
<!-- When customizing the layout, the ID conflicts with the official default ID -->
<LinearLayout ...>
<!-- The official uses @+id/iv_back, and you overwrote it -->
<ImageView
android:id="@+id/iv_back"
android:src="@drawable/my_icon" /> <!-- The official expectation is a back button -->
</LinearLayout>
Bonne pratique :
Conservez les ID des contrôles principaux inchangés : Les ID des contrôles utilisés par la classe Slot doivent être conservés dans la mise en page.
Correspondance du type de contrôle : Le type de contrôle correspondant à l'ID doit correspondre au type dans le code.
<!-- Keep core ID unchanged -->
<LinearLayout ...>
<ImageView
android:id="@+id/iv_back" <!-- Keep the ID, but you can change the style -->
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/custom_back" /> <!-- You can change the icon -->
</LinearLayout>
Cause du plantage : Si la mise en page manque de l'ID de contrôle requis par la classe Slot (R.id.iv_back) ou si le type de contrôle est incorrect, findViewByIdCompat() provoque un échec de cast ou une liaison incorrecte de l'événement de clic.
Erreur 3 : Appel de getHost() dans le constructeur
Code incorrect :
public class MySlot extends BaseSlot {
public MySlot(@NonNull Context context) {
super(context);
// getHost() returns null in the constructor
SlotHost host = getHost(); // null!
}
}
Code correct :
@Override
public void onAttach(@NonNull SlotHost host) {
super.onAttach(host);
// Host can only be accessed after onAttach
SlotHost host = getHost(); // Obtained normally
}
Cause du plantage : L'instance du slot n'est pas encore attachée à l'hôte lors de la construction. Cela entraîne le retour de null par getHost().
Erreur 4 : Ne pas nettoyer les ressources dans onDetach entraîne une fuite de mémoire
Code incorrect :
public class MySlot extends BaseSlot {
private Handler handler = new Handler();
@Override
public void onAttach(@NonNull SlotHost host) {
super.onAttach(host);
handler.postDelayed(() -> updateUI(), 1000); // Delayed task
}
@Override
public void onDetach() {
// Forgot to remove the delayed task
super.onDetach();
}
}
Code correct :
@Override
public void onDetach() {
handler.removeCallbacksAndMessages(null); // Clean up all delayed tasks
super.onDetach();
}
Cause du plantage : Si une tâche différée conserve une référence au Slot, la mémoire ne peut pas être récupérée (fuite de mémoire). De plus, le Slot pourrait déjà être dans un état détaché lorsque la tâche s'exécute.
Erreur 5 : Abonnement aux événements non déclaré dans observedEvents()
Code incorrect :
public class MySlot extends BaseSlot {
@Override
public void onAttach(@NonNull SlotHost host) {
super.onAttach(host);
// Expected to receive events, but not declared in observedEvents()
}
@Override
protected void onEvent(@NonNull PlayerEvent event) {
// Never called!
}
}
Code correct :
@Override
protected List<Class<? extends PlayerEvent>> observedEvents() {
return Arrays.asList(
PlayerEvents.StateChanged.class, // Declare events to subscribe to
PlayerEvents.Info.class
);
}
@Override
protected void onEvent(@NonNull PlayerEvent event) {
// Now events can be received normally
}
Cause du plantage : BaseSlot déclare les événements à abonner via observedEvents(). S'ils ne sont pas déclarés, l'abonnement n'aura pas lieu.