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…

 

Kivy : accès à l’appareil photo sous Android

Introduction

Depuis une application Kivy, il est possible d’appeler des objets Android et les faire interagir en utilisant Python. Pour ce faire, une interface est disponible sous le nom de « jnius ». En effet, c’est très pratique et génial…

Voici un exemple d’utilisation de l’appareil photo, le tout réutilisable dans vos applications Kivy, car dans un module à part (genre « Service »).

 

Appel de l’appareil photo dans l’application Kivy

Attention, avant de procéder à l’appel de caméra pour prendre une photo, il faut savoir une chose : l’application Kivy se mets en pause. Pour ce faire, il est nécessaire que l’instance de App définisse la méthode « pause » et renvoie « True ». C’est un peu comme si Kivy disant « je mets en pause l’appli, est-ce que j’en ait le droit ? ». Si « True », je passe à la suite. Si « False », je stoppe.

 

Le service d’appel de la photo est défini comme ceci (basé sur le tuto « takepicture ») :

from jnius import autoclass, cast
from android import activity
from functools import partial
from kivy.clock import Clock
import os

Intent = autoclass('android.content.Intent')
PythonActivity = autoclass('org.renpy.android.PythonActivity')
MediaStore = autoclass('android.provider.MediaStore')
Uri = autoclass('android.net.Uri')
Environment = autoclass('android.os.Environment') 

class CameraService(object):
    '''
    Service pour acceder a la camera du device.
    Attention, seules les images JPG sont autorisées.
    '''

    def __init__(self, savingPath = None, aFilename = None):
        '''
        Constructeur
        savingPath doit se terminer par le separateur de répertoire
        '''
        if savingPath is None:
            savingPath = Environment.getExternalStorageDirectory().getPath()
        self.path = savingPath
        self.filename = aFilename
        self.picture = None

    def __define_new_filename(self):
        index = 0
        while True:
            index += 1
            fn = os.path.join(self.path, 'takepicture{}.jpg'.format(index))
            if not os.path.exists(fn):
                return fn

    def take_picture(self, aCallbackFunction):
        self.callbackFunction = aCallbackFunction
        if self.filename is None:
            self.filename = self.__define_new_filename()
        uri = Uri.parse('file://' + os.path.join(self.path, self.filename) )
        uri = cast('android.os.Parcelable', uri)

        activity.bind(on_activity_result=self.__on_activity_result)

        intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        intent.putExtra(MediaStore.EXTRA_OUTPUT, uri)
        PythonActivity.mActivity.startActivityForResult(intent, 0x123)

    def __on_activity_result(self, requestCode, resultCode, intent):
        if requestCode == 0x123:
            Clock.schedule_once(partial(self.__add_picture, self.filename), 0)

    def __add_picture(self, fn, *args):
        self.picture = fn
        self.callbackFunction(self.picture)
        activity.unbind(on_activity_result=self.__on_activity_result)

 

Son appel se fait dans une instance de Screen, par exemple :

class myScreen(Screen):
    #...

    def btn_take_photo(self):
        camera = CameraService()
        camera.take_picture(self.set_picture)

    def set_picture(self, newPicture_name):
        self.logLabel.text = "Photo : %s" % str(newPicture_name)

Lorsqu’un bouton est cliqué, la méthode « btn_take_photo() » est appelée. Celle-ci fait appel au service (sans paramètre dans ce cas) et lance la prise de photo avec un paramètre : la méthode à appeler lorsque l’utilisateur aura terminé : « self.set_picture ».

Cette méthode prend un paramètre : le nom du fichier sauvegardé par l’appareil photo du smartphone. Attention, seul le format JPEG est autorisé…

 

Conclusion

Pour reprendre cette photo, je vous conseille de la réduire avec PIL pour éviter d’avoir une image en 4128 x 2322 pixels.

im = Image.open(filename)
width, height = im.size
im.thumbnail( (width/4,height/4) , Image.ANTIALIAS)
im.save(filename,quality=95)

Kivy : icones de l’ActionBar trop petites

Introduction

Avec la version 1.8 de Kivy, une nouvelle fonctionnalité a été ajoutée : ActionBar. API : http://kivy.org/docs/api-kivy.uix.actionbar.html

Vous verrez que ce Widget permet de définir une barre (en haut de l’écran) avec des boutons d’actions. Ceci a été fait dans l’environnement Android : http://developer.android.com/guide/topics/ui/actionbar.html

Et même un guide d’implémentation a été rédigé pour nous aider à rester ergonomique : http://developer.android.com/design/patterns/actionbar.html

 

Problématique et solution

La volonté de Kivy est plutôt bonne, mais avec les téléphones haute résolution, il y a des petits problèmes : les icones restent (très) petites.

l’API de Kivy nous donne une piste : il est possible de définir les images pour les icônes « Back » et l’icône de l’application. La solution tient en quelques lignes dans le fichier « .kv » (2 lignes en gras ci-dessous) :

        ActionBar:
            pos_hint: {'top':1}
            ActionView:
                use_separator: True
                ActionPrevious:
                    title: 'Client'
                    #with_previous: False
                    on_press: root.manager.go_back()
                    previous_image: "images/ic_action_previous_item.png"
                    app_icon: "images/ic_action_icon.png"

                ActionOverflow:

                ActionButton:
                    text: 'Refresh'
                    icon: "images/ic_action_refresh.png"
                    on_press: root.refresh()

L’image pour le retour à l’écran précédent :

previous_image: "images/ic_action_previous_item.png"

 

L’image de l’application :

app_icon: "images/ic_action_icon.png"

Bonus

Pour rester graphiquement proche d’une application Android, on pourra utiliser les images fournies par l’API : Android_Design_Icons_20131106.zip (cliquez pour télécharger le pack d’icones de http://developer.android.com)

 

Attention, ces 2 images sont redéfinies, mais je n’ai pas réussi à redéfinir les images pour le widget TreeView. Il faudra se contenter de petites icônes pour cette fois…

 

Kivi : surcharge du ScreenManager

Introduction

Le ScreenManager de Kivy permet de gérer les pages de l’application mobile comme des pages qui se chevauchent, scrollable horizontalement. L’interface Metro l’utilise dans Windows 8. Si on utilise le ScreenManager fourni par Kivy, il faut pas mal de code pour scroller d’un écran à un autre en faisant attention à l’écran précédent et à l’écran suivant. Avec une surcharge de cette classe, le code est réduit et l’utilisation est uniforme.

Code habituel

Habituellement, Kivy nous conseille de procéder comme ceci (1 écran « Welcome » et 3 écrans « Settings », « About », « Battery ») :

class Welcome(Screen):

    def do_enter(self):
        self.manager.transition = SlideTransition(direction="left")
        self.manager.current = SettingsApp.screenName
        dataforSettings = ....
        self.manager.current_screen.setItems( dataforSettings )

class WelcomeApp(App):
    def build(self):
        self.manager = ScreenManager()

        # ajout de l'instance de page d'accueil
        welcomeScreen = Welcome(name='Welcome')

        self.manager.add_widget(welcomeScreen)

        # ajout des vues pour les 3 écrans
        for app in [SettingsApp(), AboutApp(), BatteryApp()]:
            app.load_kv()
            self.manager.add_widget( app.build() )

        self.manager.transition = SlideTransition(direction="left")

        return self.manager

class Settings(Screen):
    def do_enter(self):
        ## passer a l'ecran suivant
        self.manager.transition = SlideTransition(direction="left")
        self.manager.current = screens.AboutApp.screenName
    def back(self):
        self.manager.transition = SlideTransition(direction="right")
        self.manager.current = 'Welcome'

class SettingsApp(App):
    screenName = "Settings"
    def build(self):
        return Settings(self.screenName)

Explication de Kivy : http://kivy.org/docs/api-kivy.uix.screenmanager.html

Il faut donc :

  • gérer les directions des transitions (« left » ou « right »)
  • donner les noms des écrans
  • utiliser ces noms d’écrans dans les autres écrans (genre plat de spaghetti)

 

Proposition d’amélioration

Puisque ces écrans sont séquentiels (1 -> 2 -> 3 et jamais 1 -> 3 -> 1 -> 2), on peut simplifier cette gestion dans le ScreenManager.

1. Surcharge du ScreenManager

Une nouvelle classe est définie et elle prendra en charge la gestion du bouton retour sous Android :

class CustomScreenManager(ScreenManager):
    def __init__(self, **kwargs):
        super(ScreenManager, self).__init__(**kwargs)
        self.allScreens = []
        self.screenIndex = 0
        Window.bind(on_keyboard=self.hook_keyboard)

    def hook_keyboard(self, window, key, *largs):
        if key == 27: # BACK
            return self.go_back()
        elif key in (282, 319): # SETTINGS
            print("SETTINGS")

    def add_screen(self, aScreen):
        self.allScreens.append(aScreen.name)
        self.add_widget(aScreen)

    def go_next(self):
        self.screenIndex = self.screenIndex + 1
        self.transition = SlideTransition(direction="left")
        self.current = self.allScreens[self.screenIndex]
        return self.current_screen

    def go_back(self):
        if self.screenIndex == 0:
            return False
        self.screenIndex = self.screenIndex - 1
        self.transition = SlideTransition(direction="right")
        self.current = self.allScreens[self.screenIndex]
        self.current_screen.postback()
        return True

Elle gère un tableau d’écrans et un index pour savoir quel écran est présenté. Pour le premier écran, le retour ne fait rien.

Méthodes :

  • go_back() : un écran l’appelle pour retourner en arrière. L’écran n’est pas obligé de savoir quel écran il doit appeler.
  • go_next() : un écran l’appelle pour faire apparaître l’écran suivant. L’écran n’est pas obligé de savoir quel écran il doit appeler.

 

2. Modifications dans l’utilisation

L’utilisation de cette fonctionnalité est alors un simplifiée et uniforme :

class Welcome(Screen):

    def do_enter(self):
        self.manager.go_next()
        reader = ClientDataReader()
        self.manager.current_screen.setItems( reader.getAllRecords() )

    def postback(self):
        pass

class WelcomeApp(App):

    def build(self):
        manager = CustomScreenManager()

        # ajout de l'instance de page d'accueil
        welcomeScreen = Welcome(name='Welcome')

        manager.add_screen(welcomeScreen)
        # ajout des vues pour les 3 écrans
        for app in [SettingsApp(), AboutApp(), BatteryApp()]:
            app.load_kv()
            self.manager.add_screen( app.build() )

        return self.manager

class Settings(Screen):
    def do_enter(self):
        ## passer a l'ecran suivant
        nextScreen = self.manager.go_next()

    def postback(self):
        pass

class SettingsApp(App):
    screenName = "Settings"
    def build(self):
        return Settings(self.screenName)

Dans le fichier « settings.kv », l’appel à l’écran précédent « root.back() » devient « root.manager.go_back() ».

3. Encore plus loin

Pour terminer complètement cette amélioration, il faut définir une classe CustomScreen (qui hérite de Screen) et qui défini les 4 méthodes :

  • pre_back() : avant de passer à l’écran précédent
  • post_back() : une fois que l’écran précédent est affiché
  • pre_next() : avant de passer à l’écran suivant
  • post_next() : une fois que l’écran suivant est affiché

Par défaut, ces 4 méthodes ne font rien et les classes des écrans (qui héritent de Screen) doivent hériter de CustomScreen.

On a alors un code bien plus simple :

class Settings(Screen):
    def do_enter(self):
        ## passer a l'ecran suivant
        nextScreen = self.manager.go_next()

class SettingsApp(App):
    screenName = "Settings"
    def build(self):
        return Settings(self.screenName)

Téléchargement : customscreen.py

Application Back-office & Mobile : CodeIgniter & Kivy

Introduction

Après quelques essais avec Kivy pour faire une application mobile qui indique des données d’infusion sur les thés, je me lance pour faire une application en back-office pour gérer les données. L’application mobile récupère les données en JSON et stocke dans une base SQLite.

Voici les outils utilisés de chaque coté :

Back-office Appli Mobile
Générateur de code Écriture du code « à la main »
PHP, CodeIgniter Python, Kivy
MySQL SQLite3

 

Principe général :

 

Application Back-office

Avec le générateur de code (plus d’infos sur ce site), l’application est créée, déployée et les données ajoutées, le tout en 1 heure sur « free.fr ». Le plus long a été de copier / coller les données entre ma source (le site du palais des thés) et ma nouvelle application.

Le controller fournissant le flux JSON est lui aussi généré :

Les écrans n’ont pas été retravaillés suite à la génération de code, mais c’est suffisant pour tout faire fonctionner sur l’environnement de « free.fr » : http://jc.specs.free.fr/TeaTime/ (un identifiant et mot de passe sont nécessaires…). Le nouveau thème Ubuntu (en gris et orange a été choisi).

Écrans du back-office

Catégorie de thés :

Thés :

Application mobile

Avec Kivy, je disposais déjà d’une application qui lit un JSON sous forme de fichier. Il m’a suffit de lui permettre de lire des fichiers JSON par Internet, de remplir une base de données SQLite et de la remplir.

Voici les étapes de réalisation :

  1. Lire dans un processus parallèle le JSON. Au lieu d’attendre que l’URL réponde, les widgets de l’écran de l’appli mobile sont disponibles.
  2. Stocker le JSON dans une base SQLite embarquée avec l’application. Lorsque c’est fait, un widget de l’application est modifié : un label devient « Fichier chargé ».
  3. Lorsque l’utilisateur entre dans les écrans de l’appli mobile, il faut lire la base de données SQLite et valoriser les objets qui seront utilisés par Kivy.

Kyvi n’est d’aucune utilité pour réaliser ces opérations de synchronisation ; Python prend le dessus : gestion des processus asynchrones, lecture d’une URL, transformation de JSON en objet, accès à une base de données SQLite.

 

Écrans de l’appli mobile

   

Le fichier APK de l’application mobile est téléchargeable sur http://jc.specs.free.fr/TeaTime/

 

Deploy amélioré de kivy sur Android, avec buildozer

Introduction

Cet article fait suite à celui qui montre comment déployer sur le mobile une application kivy avec une VM. Le résultat était concluant, avec pas mal de manipulations pour déployer le fichier APK généré.

La taille des textes était très petite car la résolution du S4 est de 1080 x 1920 au lieu du test sur mon PC, en 600 x 800.

 

Correction de la taille des textes

Toutes les dimensions sont passée en unité « sp » dans les fichier kv.

Au lieu de :

font_size: 32

il y a :

font_size: '32sp'

La taille « 32 » devient indépendante de la résolution du device.

 

Déploiement avec buildozer

La procédure avec buildozer parait très avancée : une ligne de commande et mon application kivy est déployée sur mon mobile ? Un peu de magie ?

Sur mon PC (amd64 avec ubuntu), la procédure d’installation décrite sur cette page est insuffisante. Il faut que j’installe des bibliothèques supplémentaires (lib32ncurses5, lib32z1 lib32bz2-1.0, lib32stdc++6, libc6-i386).

Le paramétrage des variables d’environnement est fait comme indiqué :

export ANDROIDSDK=/home/julien/.buildozer/android/platform/android-sdk-21
export ANDROIDNDK=/home/julien/.buildozer/android/platform/android-ndk-r9
export ANDROIDNDKVER=r9
export ANDROIDAPI=21
export PATH=$ANDROIDNDK:$ANDROIDSDK/platform-tools:$ANDROIDSDK/tools:$PATH

Le fichier « buildozer.spec » est édité pour définir les paramètres de l’application (le chemin des sources, le nom du package, l’orientation, etc…). De plus, la version est maintenant portée par le « main.py ».

La magie peut maintenant opérer en une ligne de commande :

/usr/share/kivy/buildozer$ buildozer android debug deploy run

L’application est compilée, packagée (fichier APK généré), déployée sur le mobile et même lancée.

Le résultat est là :

Bravo à l’équipe Kivy pour ces outils.

Test de kivy sur Android (Samsung Galaxy S4)

Kivy, deploy sur le mobile

Suite à l’article sur un premier test de kivy, je me lance dans l’installation de l’application de test sur un smartphone… Est-ce que l’application sur mon PC est la même que sur le smartphone ? Est-ce que Kivy tient ses promesses ?

 

Opérations sur le PC

Il est possible de déposer ses fichier .py et .kv dans un dossier sur le smartphone et de tester avec « Kivy Launcher », mais pour le moment, ça ne fonctionne pas (pourtant le processus est détaillé, chez moi : sans succès). Je me lance donc vers le processus de création du package APK pour Android pour voir ce que ça donne.

Voici les étapes.

1. Récupération de la VirtualBoX

L’équipe kivy propose une VM avec les outils disponibles à la création du package. Ceci évide de devoir installer tous les outils sur le PC. Suivre le lien http://kivy.org/#download, section « Virtual Machine ».

Après avoir lancé la VM, il faut réaliser les opérations suivantes:

2. Compilation des package

cd ~/android/python-for-android/
./distribute.sh -m "kivy"

3. Copie des sources dans la VM

Avec l’option de drag-n-drop, copier les fichiers de l’application (.py, .kv, images, polices, etc…)

4. Lancement du build du package de l’application (génération de l’APK)

cd ~/android/python-for-android/dist/default
./build.py --dir ~/Public/build/ --name "Login Sample" --package org.test.login --version 0.1 --orientation portait debug installd

Le fichier APK est dans bin/

Il faut alors récupérer l’APK sur le PC et l’envoyer sur le mobile (dans le répertoire Download). Pour mon cas, le fichier fait 6Mo (pour les 4 fichiers décrits dans le premier article).

 

Opérations sur le mobile

Avant tout, il faut se mettre en mode développeur (et en debug). Une opération cachée le permet sur le Samsung S4 (dans la partie paramétrage). Une description est faite dans une vidéo.

Installer « Apk Manager+ » depuis google play.

Lancer « Apk Manager+ » et installer l’APK de l’application qui se trouve dans le répertoire de dépôt.

Lancer l’application : ca fonctionne… enfin c’est presque bon…

Les textes sont très petits. Il faudra régler la taille des textes (selon l’unité proposée : « pixel », « inch », « dp », « sp », etc…).

En conclusion, cela fonctionne plutôt bien, reste quelques réglages à faire et des bonnes pratiques à connaître.

Le mode de test en live (avec « Kivy Launcher ») ne fonctionne pas chez moi. Il apporte une plus-value énorme pour ces réglages.