Windows 8 : développement d’un lecteur audio

L’objectif de cet article est de découvrir les API Windows 8 qui vont nous permettre :

  • d’accéder aux librairies de l’utilisateur (dans notre cas la librairie de musique),
  • lire un flux audio en mode arrière-plan
  • s’interfacer aux évènements du contrôle audio

Vous pouvez télécharger les sources complètes au format Visual Studio 2012 RC en suivant MusicLibrary.zip.

Voici un aperçu de l’application :
image

Accès aux librairies

Les librairies auxquelles nous pouvons accéder depuis les applications Metro sont les librairies Windows de documents, d’images, de vidéos et de musiques de l’utilisateur, comme nous les retrouvons dans Windows Explorer :
image

Les capacités de l’application

Pour accéder au contenu de ces librairies dans une application Windows 8 il faut en déclarer l’accès via les capacités, depuis le manifeste de l’application :
image

Dans le cas contraire, une exception de type UnauthorizedAccessException sera levée avec le message suivant : “Access to the specified location (MusicLibrary) requires a capability to be declared in the manifest”.

Les classes KnownFolders, StorageFolder et StorageFile

L’accès aux librairies se fait via la classe Windows.Storage.KnownFolders qui expose des propriétés de type StorageFolder :

  1. StorageFolder documentsFolder = KnownFolders.DocumentsLibrary;
  2. StorageFolder musicFolder = KnownFolders.MusicLibrary;
  3. StorageFolder picturesFolder = KnownFolders.PicturesLibrary;
  4. StorageFolder videosFolder = KnownFolders.VideosLibrary;

 

Depuis un objet StorageFolder, nous pouvons interroger le système de fichiers pour
récupérer la liste des genres musicaux, des artistes ou des albums via la méthode
GetFoldersAsync et également des fichiers via la méthode GetFilesAsync. Dans le code
ci-dessous nous récupérons la liste des genres musicaux. Par genre, nous parcourons
la liste des albums et enfin par album, la liste des fichiers :

  1. foreach (var genreFolder in genreFolders)
  2. {
  3.     IReadOnlyList<StorageFolder> albumFolders = await genreFolder.GetFoldersAsync(CommonFolderQuery.GroupByAlbum);
  4.     foreach  (var albumFolder in albumFolders)
  5.     {
  6.         IReadOnlyList<StorageFile> trackFiles = await albumFolder.GetFilesAsync();
  7.         foreach (var trackFile in trackFiles)
  8.         {
  9.         }
  10.     }
  11. }

 

L’interface IStorageFolderQueryOperations

Pour aller un peu plus loin dans la recherche de répertoires ou de fichiers, nous
pouvons utiliser les membres de l’interface IStorageFolderQueryOperations, implémentée
par StorageFolder et StorageFile.

Cette interface nous permet par exemple de spécifier des options de filtres supplémentaires
sur les fichiers : la méthode CreateFileQueryWithOptions nous retourne un objet StorageFileQueryResult.
De cette manière nous pouvons filtrer les fichiers d’extension mp3 et wma.

Elle permet également d’être notifier en cas de changement dans un répertoire, par
exemple l’ajout d’un album dans un genre musical. Dans ce cas nous passerons par la
méthode CreateFolderQuery qui renvoie un objet StorageFolderQueryResult et qui possède
un évènement ContentsChanged.

Le code ci-dessous illustre ces 2 exemples :

  1. foreach (var genreFolder in genreFolders)
  2. {
  3.     StorageFolderQueryResult albumsQuery = genreFolder.CreateFolderQuery(CommonFolderQuery.GroupByAlbum);
  4.     albumsQuery.ContentsChanged += albumsQuery_ContentsChanged;
  5.     IReadOnlyList<StorageFolder> albumFolders = await albumsQuery.GetFoldersAsync();
  6.     foreach (var albumFolder in albumFolders)
  7.     {
  8.         StorageFileQueryResult tracksQuery = albumFolder.CreateFileQueryWithOptions(new QueryOptions(CommonFileQuery.OrderByMusicProperties, new string[] {« .mp3 »« .wma »}));
  9.         IReadOnlyList<StorageFile> trackFiles = await tracksQuery.GetFilesAsync();
  10.         foreach (var trackFile in trackFiles)
  11.         {
  12.         }
  13.     }
  14. }

 

L’interface IStorageItemProperties

Les classes StorageFolder et StorageFile implémentent l’interface IStorageItemProperties qui nous donne accès aux propriétés étendues de ces éléments.

Par exemple pour un album, nous aimerions retrouver l’année de sortie ainsi que l’artiste, et pour une piste, le numéro de la piste et sa durée. Dans ce cas nous devons récupérer un objet MusicProperties via la méthodes GetMusicProperties de l’objet StorageItemContentProperties.

Cette interface expose également la méthode GetThumbnailAsync qui permet de retrouver la miniature d’un élément en spécifiant le type de miniature que nous voulons récupérer ainsi que sa taille.

Le code ci-dessous illustre l’utilisation de ces membres :

  1. // Album Properties
  2. MusicProperties albumProp = await albumFolder.Properties.GetMusicPropertiesAsync();
  3. string album = albumProp.Album;
  4. string artist = albumProp.Artist;
  5. uint year = albumProp.Year;
  6. // Music Thumbnail
  7. StorageItemThumbnail thumbnail = await albumFolder.GetThumbnailAsync(ThumbnailMode.MusicView, 250);
  8. // Track Properties
  9. MusicProperties trackProp = await trackFile.Properties.GetMusicPropertiesAsync();
  10. TimeSpan trackDuration = trackProp.Duration;
  11. uint trackNumber = trackProp.TrackNumber;

 

Lecture audio en mode arrière plan

La lecture audio en arrière-plan consiste à continuer la lecture d’un flux audio lorsque l’on navigue de page en page à l’intérieur de notre application, mais également lorsque l’on navigue vers une autre application. Les actions pour arriver à ce résultat sont décrites ci-dessous.

Déclaration d’une tâche de fond

La 1ère étape pour activer la lecture audio en arrière-plan consiste à le déclarer dans le manifeste de l’application. Il faut ajouter une déclaration Background Tasks de type Audio en spécifiant le point d’entrée applicatif :


image

Gestion de la navigation et du composant MediaElement

La 2ème étape est de déclarer la capacité de lecture en tache de fond au niveau de l’objet MediaElement. Le MediaElement est un composant déjà connu par les développeurs XAML qui permet d’effectuer la lecture de contenu multimédia. Il possède une propriété AudioCategory qui permet d’indiquer au système comment traiter ce flux audio :

  1. <MediaElement x:Name= »MediaPlayer » AudioCategory= »BackgroundCapableMedia » AudioDeviceType= »Multimedia » />

 

Une autre chose à savoir si l’on veut lire un flux audio en tache de fond lorsque l’on navigue entre les pages d’une application, est que le composant MediaElement doit être présent dans l’arbre de contrôles de toutes les pages.

L’une des solutions en Windows 8, est d’utiliser le système de Frame pour la navigation. Une même Frame est utilisée pour afficher les pages de notre application comme le montre le code ci-dessous :

  1. // Init frame
  2. var rootFrame = new Frame();
  3. rootFrame.Navigate(typeof(MainPage));
  4. Window.Current.Content = rootFrame;
  5. Window.Current.Activate();
  6. //Navigate to another page
  7. var frame = Window.Current.Content as Frame;
  8. frame.Navigate(typeof(PlayTracksPage));

 

Avec ce système il suffit d’attacher un composant MediaElement à l’objet Frame de façon à ce qu’il se retrouve dans l’arbre des contrôles quel que soit la page affichée. Nous pouvons dans ce cas déclarer un style contenant un objet MediaElement au niveau des ressources de l’application.

Voici le code de la déclaration du style :

  1. <Style x:Key= »MediaPlayerStyle » TargetType= »Frame »>
  2.     <Setter Property= »Template »>
  3.         <Setter.Value>
  4.             <ControlTemplate TargetType= »Frame »>
  5.                 <Grid>
  6.                     <MediaElement x:Name= »MediaPlayer » AudioCategory= »BackgroundCapableMedia » AudioDeviceType= »Multimedia » />
  7.                     <ContentPresenter />
  8.                 </Grid>
  9.             </ControlTemplate>
  10.         </Setter.Value>
  11.     </Setter>
  12. </Style>

 

Puis l’affectation du style à l’objet Frame lors de sa création :

  1. var rootFrame = new Frame();
  2. rootFrame.Style = Resources[« MediaPlayerStyle »as Style;

 

Lorsque nous sommes dans une page et que nous voulons récupérer l’instance du composant MediaElement, l’utilisation de la classe VisualTreeHelper s’impose :

  1. DependencyObject rootGrid = VisualTreeHelper.GetChild(Window.Current.Content, 0);
  2. MediaElement mediaElement = (MediaElement)VisualTreeHelper.GetChild(rootGrid, 0);

 

Lecture en arrière-plan en dehors de l’application

Les 2 étapes décrites ci-dessus permettent de mettre en place la lecture d’un flux multimédia en arrière-plan dans l’application. Si nous voulons que la lecture du flux audio continue hors des frontières de l’application (c’est à dire lorsque l’utilisateur navigue vers une autre application) il faut s’interfacer à la classe MediaControl. Le chapitre ci-dessous y est consacré.

Gestion du contrôle audio

Avec Windows 8, comme c’est déjà le cas avec Windows Phone 7, l’utilisateur peut contrôler un flux audio depuis n’importe quel endroit grâce à ce qu’on appelle le MediaControl. Le MediaControl est un panel permettant de visualiser et de contrôler la lecture en cours. Qu’il soit dans une application Windows 8, une application Desktop ou sur le Start Screen, l’utilisateur peut faire apparaitre le MediaControl en haut à gauche de son écran en appuyant par exemple sur les touches de volume de son clavier :


image

Avec les APIs Windows 8, nous pouvons recevoir les notifications de ce contrôle (Previous, Play, Pause, Stop, Next…) dans notre application et également lui envoyer des informations, telles que le nom de l’artiste, de la piste en cours ou encore la miniature de l’album.

Pour s’interfacer au MediaControl et activer la lecture en arrière-plan depuis une application, il faut au minimum s’abonner aux 4 évènements suivants : StopPressed, PlayPressed, PausePressed, PlayPauseTogglePressed :

  1. MediaControl.StopPressed += MediaControl_StopPressed;
  2. MediaControl.PlayPressed += MediaControl_PlayPressed;
  3. MediaControl.PlayPauseTogglePressed += MediaControl_PlayPauseTogglePressed;
  4. MediaControl.PausePressed += MediaControl_PausePressed;

 

Attention il vous faudra dispatcher vers le thread UI la modification de votre objet MediaElement :

  1. void MediaControl_PausePressed(object sender, object e)
  2. {
  3.     App.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,() =>
  4.                                    {
  5.                                        mediaPlayer.Pause();
  6.                                    });
  7. }
  8. void MediaControl_PlayPauseTogglePressed(object sender, object e)
  9. {
  10.     if (MediaControl.IsPlaying)
  11.         App.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
  12.                                    {
  13.                                        mediaPlayer.Pause();
  14.                                    });
  15.     else
  16.         App.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
  17.                                    {
  18.                                        mediaPlayer.Play();
  19.                                    });
  20. }
  21. void MediaControl_PlayPressed(object sender, object e)
  22. {
  23.     App.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
  24.                                    {
  25.                                        mediaPlayer.Play();
  26.                                    });
  27. }
  28. void MediaControl_StopPressed(object sender, object e)
  29. {
  30.     App.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
  31.                                    {
  32.                                        mediaPlayer.Stop();
  33.                                    });
  34. }

 

Pour activer/désactiver les boutons Previous et Next du MediaControl il suffit de s’abonner/se désabonner aux évènements PreviousTrackPressed et NetxTrackPressed. A vous de gérer la position courante et le fait de s’abonner et se désabonner. Par exemple, après avoir cliquer sur Next, on vérifie la position actuelle :

  1. if  (!CanGoNext)
  2.     MediaControl.NextTrackPressed -= MediaControl_NextTrackPressed;
  3. if (currentPosition == 1)
  4.     MediaControl.PreviousTrackPressed += MediaControl_PreviousTrackPressed;

 

L’état du bouton Play/Pause quant à lui se gère via la propriété IsPlaying :

  1. MediaControl.IsPlaying = true;

 

Et enfin, les informations concernant la lecture en cours se paramètrent via les propriétés
TrackName, ArtistName et AlbumArt :

  1. MediaControl.TrackName = album.CurrentTrack.Name;
  2. MediaControl.ArtistName = album.CurrentTrack.ArtistName;
  3. MediaControl.AlbumArt = new Uri(album.ThumbnailLocalStorageUri);

 

Petite remarque sur l’Uri du contenu de l’AlbumArt. Cette propriété accepte 2 types d’Uri, soit une URI vers une donnée locale de l’application (ms-appdata://), soit une ressource dans le package (ms-appx://).

Conclusion

Nous avons vu que le parcours et la recherche dans les librairies est très simple à prendre en main et à mettre en place dans son application Windows 8. L’utilisation des membres de l’interface IStorageItemProperties permet de simplifier la récupération d’informations spécifiques en fonction du type de contenu de la librairie. Pour plus d’informations à ce sujet je vous invite à consulter la documentation sur le site MSDN

La mise en place d’une lecture en arrière-plan est un peu plus complexe. Il faut d’une part s’assurer que le composant MediaElement soit présent dans l’arbre de contrôle de toutes les pages XAML pour qu’il continue sa lecture lors de la navigation InApp. Et d’autre part si la lecture doit continuer en dehors de l’application, il faut s’interfacer au MediaControl. Cependant ce scénario permet une très bonne intégration au système d’exploitation et aux évènements clavier. Pour plus d’infos sur ce dernier voici un article de la MSDN (en Html / Javascript).

Laisser un commentaire