Kivy : gestion de la fin d’une lecture audio

Introduction

Kivy ne propose pas d’interface élaborée pour surveiller le bon déroulement du fichier audio qui est en cours de lecture (durée, temps passé). Mais je vous propose de voir en détail ce qui est exploitable en utilisant le « player » associé à l’objet « son joué ».

 

Structure des données

Pour écouter un fichier MP3 (WAV ou OGG), il faut réaliser ceci :

filename = "youi.mp3"
self.currentSound = SoundLoader.load(filename)
self.currentSound.play()

L’objet « SoundLoader » ne propose pas beaucoup d’interaction. Il est possible de le lire (méthode play), de le forcer à s’arrêter (méthode stop) et de savoir s’il est arrêté ou s’il en lecture.

La méthode « on_stop » est appelée à la fin du son.

 

Description

Le fichier audio doit être joué par le scheduler et non directement :

filename = "youi.mp3"
self.currentSound = SoundLoader.load(filename)
Clock.schedule_once(self.play_current_sound, 0)

De plus, d’autres méthodes sont définies.

1. Méthode pour lancer la lecture du fichier audio :

    def play_current_sound(self, arg):
        self.currentSound.play()
        self.currentSound.bind(on_stop = self.stop_sound)

Attention à respecter cet ordre sinon à la fin du son, la méthode « on_stop » n’est pas appelée.

 

2. Méthode qui arrête la lecture du fichier audio (forcée par un bouton) ou qui est appelée en fin de lecture :

    def stop_sound(self,  arg = None):
        self.currentSound.unbind(on_stop = self.stop_sound)
        if self.currentSound is not None:
            self.currentSound.stop()

 

Conclusion

Avec ce principe, la méthode « stop_sound » est appelée lorsque le fichier est terminé ou lorsque l’utilisateur a volontairement arrêté la lecture. Il est possible de présenter un bouton [Stop] uniquement lorsqu’il y a un son qui est joué.

Cette fonctionnalité de lecture d’un son est implémentée de manière différente en le PC (sous Linux) et un SmartPhone Android. Il faudra faire les ajustements au dernier moment ou implémenter une interface…

 

Sound Chooser en Kivy

Introduction

Dans quelques applications ayant une possibilité de personnalisé des sons, on trouve un écran de configuration où on peut cliquer que un titre et un son est joué. Voici comment je l’ai fait avec Kivy, en utilisant le fameux ListAdapter (qui n’est pas si simple d’utilisation).

Un autre  moyen est de créer les widgets dans le Python, comme expliqué dans cette démo : https://github.com/kivy/kivy/tree/master/examples/audio

 

Le fichier KV

Comme à mon habitude, la vue principale est un widget qui hérite de Screen, nommé « SoundChooserLayout ». Je me suis rendu compte que le rendu est celui que j’attends (contrairement à un BoxLayout).

 

<SoundChooserLayout>:
    containerListView: list_id
    BoxLayout:
        orientation: 'vertical'

        Label:
            text: "Choose a sound"
            size_hint_y: None
            height: sp(25)
            font_size: '16sp'

        ListView:
            id: list_id

        BoxLayout:
            size_hint_y: None
            height: sp(50)
            Button:
                text: "Done"
                font_size: sp(20)
                height: sp(40)

Le widget « ListView » fera tout le travail. Dans le fichier KV, il est juste défini et ce sera dans le Python qu’il sera complété.

Le bouton « Done » ne fait rien pour le moment. Lorsque cet écran sera dans une application, il définira le son choisi par l’utilisateur.

 

Le code Python – extraits

Définition de la classe et constructeur :

class SoundChooserLayout(Screen):
    def __init__(self):
        super(SoundChooserLayout, self).__init__()
        self.audioFiles = {}
        self.currentSound = None
        self.readFiles()
        self.updateDisplay()

Le constructeur défini le dictionnaire des fichiers audio, le son qui est en cours de lecture.

La méthode « readFiles() » récupère les fichiers audio et mets à jour « audioFiles » :

    def readFiles(self):
        os.chdir("sounds")
        for filename in glob.glob("*.mp3"):
            self.audioFiles[filename] = SoundLoader.load(filename)

 

La méthode « updateDisplay() » modifie le widget ListView (c’est le plus compliqué dans cette mini-appli) :

    def updateDisplay(self):
        list_item_args_converter = \
            lambda row_index, obj: {'text': "%s (%.2f sec)" % (obj, self.audioFiles[obj].length),
                                    'index': row_index,
                                    'is_selected': False }

        my_adapter = ListAdapter(data = self.audioFiles,
                                    args_converter=list_item_args_converter,
                                    selection_mode='single',
                                    allow_empty_selection=False,
                                    template='CustomListItem')

        my_adapter.bind(on_selection_change=self.selection_changed)
        self.containerListView.adapter = my_adapter

Ici, le dictionnaire « self.audioFiles » est passé dans le « data », mais dans la fonction utilisée en « args_converter », ce sont les clés du dictionnaire qui sont présentes en paramètre (nommé « obj » dans la fonction lambda).

Lorsque l’utilisateur clique sur un item de la liste, la méthode « self.selection_changed » est appelée :

    def selection_changed(self, adapter, *args):
        if self.currentSound is not None:
            self.currentSound.stop()
        if len(adapter.selection) == 0:
            return

        ## unselect item
        if isinstance(adapter.selection[0].parent, kivy.uix.gridlayout.GridLayout) :
            self.currentSound = None
        else:
            selectedItem = adapter.data[adapter.selection[0].parent.index]
            self.currentSound = self.audioFiles[selectedItem]

        if self.currentSound:
            self.currentSound.play()

Cette méthode se découpe en plusieurs parties :

  • arrêter le son en cours de lecture
  • si rien n’est sélectionné (au cas où), terminer la méthode
  • quand on de-sélectionne un item (la sélection porte sur le GridLayout), le son en cours est vide
  • quand on sélectionne un nouvel item, récupérer le son sélectionné (dans SelectedItem, qui une clé du dictionnaire) et le définir comme son en cours
  • Si le son en cours n’est pas vide, le jouer

 

Bug constaté (et connu)

Kivy retrouve la durée d’un MP3 sur mon PC, mais pas sur mon SmartPhone, qui me retourne systématiquement « 1 seconde ». Je ne sais pas il sera corrigé, ni si un palliatif est possible…

 

[ Télécharger l'APK ]