Accueil > Programmation Cocoa > Films et Menus

Films et Menus

Par Mike Beam, le 25/01/2002

Traduit par Christophe, le 28/10/2002.

De nombreux lecteurs m’ont demandé un article décrivant comment utiliser ensemble Cocoa et QuickTime. J’espère que celui-ci répondra à leurs demandes. (Ah! si seulement j’en savais un peu plus sur les réseaux…).

Aujourd’hui, je vais donc présenter comment incorporer une séquence QuickTime dans les applications Cocoa. Je dirai aussi quelques mots sur les menus, qui ne font pas, à proprement parler, partie de QuickTime. Mais la qualité d’un lecteur vidéo, comme de toute application, passe par de bons menus. En particulier, je vous montrerai comment créer des menus contextuels et un menu dans le Dock.

Avant toute chose, laissez-moi vous prévenir. Je ne suis pas un expert de l’API QuickTime. Pour être honnête, je ne l’ai même jamais utilisée. Ce papier a été pour moi l’occasion d’une première plongée dans la documentation de l’API QuickTime. Mais là n’est pas le problème, voyons comment on peut mettre des vidéos dans nos applications. En route.

NSMovie et NSMovieView

Les deux classes que nous allons utiliser sont NSMovie et NSMovieView. NSMovie est une classe générale (wrapper class) qui fournit une interface orientée objet aux données QuickTime. Pour être précis, NSMovie permet de charger de manière simple un film en mémoire et d’y accéder par la suite.

Pour ceux que la programmation plus en profondeur de QuickTime intéressent, sachez que l’on peut récupérer un pointeur sur les données QuickTime avec la méthode -movie de NSMovie. Ceci permet d’utiliser le framework QuickTime pour manipuler les données. Pour ce qui nous concerne aujourd’hui, nous nous contenterons d’utiliser NSMovieView. NSMovie permettant surtout de charger les données plutôt que de les manipuler.

NSMovieView (ou plus simplement movie view) est une sous-classe de NSView. Elle permet d’afficher les objets de NSMovie. Par défaut, elle contient le jeu complet des contrôles qui permettent de jouer et monter un film QuickTime. Ces contrôles peuvent être remplacés par une interface personnalisée ou ajoutés à celle-ci. NSMovieView fournit toutes les méthodes nécessaires pour contrôler la façon de jouer une vidéo. Un peu plus loin, nous mettrons tout cela en oeuvre dans une petite interface simple, gérée par des menus.

Après ces présentations, commençons par construire l’interface, ce qui va se résumer à placer un conteneur de “movie view” dans la fenêtre et à construire un petit objet contrôleur lié au conteneur par une outlet.

Construction de l’interface

Commençons par créer un nouveau projet que nous appelerons SimpleMoviePlayer en choisissant File -> New Project. Ouvrez le fichier MainMenu.nib en le double-cliquant dans Project Builder, Interface Builder s’ouvre. L’interface que nous allons créée est toute simple. Dans la palette Cocoa-GraphicsViews repérez le logo QuickTime ; c’est un conteneur NSMovieView, qui ressemble à un conteneur NSView décrit dans de précédents articles. Placez-le dans votre fenêtre et redimensionnez-le pour qu’il remplisse toute la fenêtre.

Une remarque importante concernant NSMovieView : les films joués seront automatiquement redimensionnés pour s’adapter aux dimensions de votre fenêtre, c’est donc plutôt une bonne idée de la dimensionner dès le départ à une taille standard comme 320×240 ou 480×256. Si cependant, vous voulez modifier la dimension du film plutôt que celle de la fenêtre, il vous faut acheter QuickTime Pro. Pour redimensionner votre conteneur NSMovieView, allez dans File -> Export. Vérifiez que vous avez bien sélectionné “Movie to QuickTime Movie” dans la liste déroulante et cliquez sur le bouton Options. Dans les options, cliquez sur Size pour saisir vos nouvelles dimensions. Pour être sûr d’avoir un bon rendu, gardez les mêmes proportions hauteur/largeur quand vous redimensionnez.

Comme je l’ai déjà évoqué, je ne connais pas trop l’API QuickTime, mais je suppose qu’il y a un moyen de récupérer la taille d’un film pour ajuster la taille de la vue au chargement du film. Si quelqu’un sait comment faire, qu’il me le fasse savoir.

Prenez quelques instants pour étudier les attributs possibles du conteneur NSMovieView. Sélectionnez le film et tapez Commande-1 pour faire apparaître la fenêtre des attributs. Pour cet exemple, nous ne ferons pas de modifications mais n’hésitez pas à faire de petits tests pour voir comment tout cela fonctionne.

Nous allons maintenant créer la classe Contrôleur de l’application. Dans l’onglet Classes de la fenêtre principale nib, sélectionnez NSObject (dans la colonne la plus à gauche) et tapez “Entrée” pour créer une sous-classe. Comme d’habitude renommez la sous-classe créée en Controller. Ouvrez le panneau d’infos en tapant Commande-1. Ajoutez une outlet nommée movieView, et une action nommée openMovie:. Ensuite, instanciez le contrôleur en cliquant Classes > Instantiate dans le menu principal.

Connectez l’outlet movieView au conteneur NSMovieView dans la fenêtre. L’action openMovie: permet, bien évidemment, d’ouvrir et de charger un film dans la vue. Si elle n’est pas déjà visible, faites apparaître la fenêtre MainMenu de MainMenu.nib. Connectez le menu Open à l’action openMovie: du contrôleur en “Contrôle-Tirant” (faire glisser en maintenant la touche Contrôle enfoncée) du menu vers l’objet Controller et en validant la connexion.

Ceci fait, il ne nous reste plus qu’à créer les fichiers qui seront ajoutés au projet et à retourner dans Project Builder pour coder la méthode créée. Cliquez sur l’onglet Classes et choisissez la classe Controller. Dans le menu principal, sélectionnez Classes > Create Files For Controller et retournez dans Project Builder pour saisir votre code.

Chargement du film

Le paragraphe suivant va vous présenter le code nécessaire au chargement du film. Pour cela, deux étapes essentielles. Tout d’abord, créer une nouvelle instance de NSMovie et l’initialiser pour monter les données du disque en mémoire. On crée la nouvelle instance avec la méthode +alloc, et on l’initialise avec la méthode -initWithURL:byReference: de NSMovie.

La seconde étape va consister à faire afficher par la NSMovieView l’instance de NSMovie que nous venons de créer. On fait cela en envoyant le message -setMovie: à la vue avec l’objet NSMovie comme argument.

A ce stade, il nous manque tout de même une petite chose car il nous faut fournir à la méthode initWithURL:byReference: l’URL du film et non le chemin sur le disque.

C’est un peu différent de ce à quoi nous sommes habitués. Ordinairement, nous accédons à un fichier par son chemin sur le disque et pas par une URL. C’est pourtant obligatoire ici. Pas de problème, Cocoa gère les URL au moyen de la classe NSURL. grâce à cette classe, vous pouvez créer des URL à partir de chaînes “classiques”, et plutôt que de coder en dur une URL dans l’application, nous allons permettre à l’utilisateur de choisir le fichier à ouvrir grâce à la classe NSOpenPanel.

NSOpenPanel

Comme son nom l’indique, la classe NSOpenPanel nous fournit le panneau d’ouverture standard pour les applications Cocoa. Son utilisation est très simple, ce qui n’est pas pour déplaire. Nous l’utiliserons pour permettre à l’utilisateur de chercher un film. Quand l’utilisateur cliquera sur “Ouvrir” (Open) nous récupérerons l’URL de l’objet NSOpenPanel quel que soit le film sélectionné.

Pour utiliser un panneau d’ouverture, nous devons tout d’abord créer une instance de la classe NSOpenPanel avec la méthode +openPanel. On utilise ensuite la méthode -runModalForDirectory:file:types: qui affiche le panneau à l’écran.

Cette méthode a besoin de trois arguments. Le premier est le répertoire par défaut que nous voulons voir apparaître dans le panneau d’ouverture. Nous choisirons le répertoire “Départ” de l’utilisateur qui s’obtient par la fonction NSHomeDirectory(). Le deuxième argument est le fichier sélectionné par défaut à l’ouverture du panneau; Dans notre cas, nous n’en choisirons aucun. Le troisième argument est un tableau de types de fichiers contenant les types que le panneau d’ouverture sera capable de sélectionner. En l’occurence, notre tableau contiendra les chaînes représentant les types “mov”, “mpg”, “mp3″ et “jpg”. Dans une NSMovieView, vous pouvez néanmoins ouvrir n’importe quel format de fichier supporté par QuickTime.

Enfin, il est bon de noter que -runModalForDirectory:file:types: renvoie un entier. Cet entier permet de transmettre quel bouton a été cliqué. Nous retrouvons ici ce que nous avons appris au sujet des panneaux d’alerte et autres choses de ce type dans les articles précédents.

Jetons un coup d’oeil au code de la méthode openMovie: pour voir comment tout cela fonctionne:

- (IBAction)openMovie:(id)sender
{
  NSArray *fileTypes = [NSArray arrayWithObjects:@"mov", @"mpg", @"mp3",
  @"jpg", nil];

  NSOpenPanel *oPanel = [NSOpenPanel openPanel];

  int result = [oPanel runModalForDirectory:NSHomeDirectory() file:nil types:fileTypes];

  if (result == NSOKButton) {
	NSArray *movieToOpen = [oPanel URLs];
	NSURL *movieURL = [movieToOpen objectAtIndex:0];

	NSMovie *movie = [[NSMovie alloc] initWithURL:movieURL byReference:NO];
	[movieView setMovie:movie];
  }
}

Nous pouvons voir qu’à la première ligne nous créons un tableau contenant les types de fichiers autorisés sous forme de chaînes. Notez que lorsque nous utilisons le constructeur +arrayWithObjects: du tableau NSArray, le dernier élément du tableau doit être nul (nil).

La seconde ligne de code ne sert qu’à instancier un objet NSOpenPanel que nous nommons oPanel. A la troisième ligne nous demandons au panneau d’ouverture de s’afficher. Comme évoqué précédemment, le panneau s’ouvre par défaut sur le répertoire Départ de l’utilisateur grâce à la fonction NSHomeDirectory() passée en premier argument. Le deuxième argument étant nul, aucun fichier n’est sélectionné à l’ouverture, et nous avons passé notre tableau fileTypes en dernier argument.

Dans la partie suivante de la méthode, nous avons une instruction if qui prend la valeur “Vrai” si l’utilisateur clique sur le bouton “Ouvrir” du panneau d’ouverture. Dans cette instruction, nous récupérons le fichier sélectionné dans l’obet oPanel. Ce que nous obtenons en envoyant un message URLs à oPanel, qui renvoit un tableau d’URL avec les fichiers sélectionnés (c’est bien un tableau qui est retourné pour permettre à l’utilisateur une sélection multiple dans le panneau d’ouverture). A la ligne suivante, nous prenons le premier objet du tableau et l’affectons à la variable movieURL de l’objet NSURL. C’est cette variable que nous passerons pour initialiser l’objet NSMovie.

Ensuite, initialisez l’objet Film avec la méthode initWithURL:byReference: , en lui passant movieURL comme premier argument. Le second argument, byReference, sert à indiquer si nous voulons ou pas encoder certaines informations d’entête lorsque nous enregistrons notre objet NSMovie. Dans cet exemple, nous ne nous intéresserons pas à ce problème et transmettons simplement la valeur NO. Pour finir, nous demandons au lecteur d’afficher le film et voilà (sic) ! Compilez, lancez et regardez des films ! Nous allons voir au prochain paragraphe comment créer une interface personnalisée pour notre lecteur.

Menus

Dans ce paragraphe, je vais vous présenter comment réaliser des menus, et en particulier comment définir un menu comme le menu contextuel du lecteur ainsi que le menu du “Dock” de l’application, qui sont deux choses toutes simples à faire avec Interface Builder. Le menu que nous allons ajouter à notre application est simple, il se compose de trois boutons “Lecture”, “Pause” et “Lecture en boucle” (Play, Pause et Loop). Pour faire cela, ajouter au fichier Controller.h les trois déclarations de méthodes suivantes :

  • - (IBAction)playMovie:(id)sender;
  • - (IBAction)pauseMovie:(id)sender;
  • - (IBAction)toggleLoopMode:(id)sender;

Enregistrez ces changements et allez dans Interface Builder pour construire le menu.

Dans Interface Builder, nous devons tout d’abord faire connaître les changements que nous venons de faire dans Controller.h. C’est à dire ajouter les trois actions à la classe Controller d’IB. Pour faire cela, sélectionnez la classe Controller dans l’onglet Classes, et choisissez Read Files dans le menu Classes.

Ceci devrait vous ouvrir une boîte de dialogue vous demandant quel fichier lire. Controller.h devrait être sélectionné par défaut. Si ce n’est pas le cas, sélectionnez-le et validez par Entrée (ou le bouton Parse). En supposant que les outlets et les actions existantes n’ont pas été modifiées (il n’y aucune raison à cela), la classe va automatiquement se mettre à jour pour intégrer les changements que nous avons fait sur Controller.h dans Project Builder. Maintenant, nous sommes prêts à mettre en place les menus.

Pour créer un nouveau menu, tirez l’icône Menu de la palette sur la fenêtre Application de l’onglet Instances. Vous obtenez ainsi le menu par défaut à deux lignes. Vous pouvez modifier les propriétés de ces lignes de menus en les sélectionnant et en tapant Commande-1 pour faire apparaître la fenêtre d’information. Changez les noms de ces options de “Item1″ et “Item2″ en “Lecture” et “Pause”.

Pour “Lecture en boucle”, il nous faut ajouter une troisième option à notre menu. Dans la palette des Menus Cocoa, il y a un objet appelé “Item”, tirez-le jusqu’à votre menu juste au dessous de l’option “Pause”. Maintenant, il nous reste à établir les connexions.

Comme vous pouvez vous en douter, “Lecture” va être associé à l’action playMovie:, “Pause” à pauseMovie:, et “Lecture en boucle” à toggleLoopMode:. Réalisez ces connexions en tirant une ligne de chaque menu vers l’instance Controller.

Maintenant, nous allons réaliser deux connexions complémentaires. La première, entre l’instance de NSMenu et l’objet File’s Owner. Tirez une ligne de File’s Owner vers l’icône nommée NSMenu et la liste des outlets de File’s Owner apparaîtra. Connectez à l’outlet dockMenu. Ceci nous permet d’accéder au menu du Dock personnalisé ! C’est aussi simple que ça !

De la même manière, tirez une ligne de NSMovieView à l’icône du NSMenu et la liste des outlets du lecteur apparaîtra. Faîtes la connexion avec l’outlet menu. En faisant cela, nous faisons de notre petit menu, le menu contextuel du lecteur. Félicitations ! Nous avons un menu contextuel ! A noter que nous établi la connexion entre le menu et le lecteur. Ce qui signifie que le menu est le menu contextuel du lecteur et de rien d’autre. Donc, si vous contrôle-cliquez hors de la fenêtre du lecteur, rien n’apparaîtra. En revanche, si vous contrôle-cliquez sur la fenêtre du lecteur, ce menu apparaîtra. En attachant les menus aux vues, Cocoa nous permet d’avoir des menus personnalisés pour chaque vue de notre application.

Implémentons ces méthodes

Maintenant que l’interface a été modifiée, retournons à Project Builder pour implémenter ces méthodes. Si vous avez parcouru la documentation de NSMovieView, vous avez sûrement une bonne idée de ce qui va se passer maintenant. La première méthode que nous allons coder est -playMovie:, tout tient en une seule ligne de code:

- (IBAction)playMovie:(id)sender  {        [movieViewstart:self];  }

Nous utilisons ici la méthode start: de NSMovieView pour lancer la lecture du film. L’argument trasmis à la méthode est sender, que nous fixons à self ici. La méthode -pauseMovie: est sensiblement la même :

- (IBAction)pauseMovie:(id)sender
{
    [movieView stop:self];
}

Comme vous pouvez le voir, nous nous contentons d’appeler la méthode stop: de NSMovieView; plutôt facile. La troisième méthode, -toggleLoopMode:, est un peu plus compliquée.

L’idée générale est de permettre à l’utilisateur de basculer du mode “Boucle” au mode “Simple”. Quand le film est en mode “Boucle”, l’option “Boucle” du menu est cochée. Une coche voulant dire qu’une option de menu est activée; l’absence de coche signifiant que l’option est éteinte. On fixe et lit l’état d’une option de menu avec les méthodes -setState et -state: de NSMenuItem, respectivement.

Un film QuickTime peut être joué selon trois modes de lecture différents. Le premier, la lecture simple sans boucle, le deuxième, la lecture en boucle, le troisième, la lecture avant-arrière. Nous ne nous occuperons ici que des deux premiers.

L’idée de -toggleLoopPlayback est de vérifier l’état de l’option de menu et de changer le mode de lecture en conséquence tout en modifiant l’état pour tenir compte du changement de mode de lecture. Jetons un coup d’oeil au code ci-dessous:

- (IBAction)toggleLoopMode:(id)sender
{
  if ( [sender state] == NSOnState ) {
	[sender setState:NSOffState];
	[movieView setLoopMode:NSQTMovieNormalPlayback];
  } else {
	[sender setState:NSOnState];
	[movieView setLoopMode:NSQTMovieLoopingPlayback];
  }
}

Nous examinons si le menu “Boucle” est dans son état actif ou pas. S’il l’est nous le désactivons et mettons le mode de lecture en mode simple. Si le menu, au contraire, est désactivé, nous l’activons et passons au mode lecture en boucle. NSQTMovieNormalPlayback et NSQTMovieLoopingPlayback sont des constantes qui servent à représenter ces deux modes.

Maintenant compilez et lancez votre application pour vous amusez avec. Vous devriez très vite vous rendre compte que l’option Boucle du menu du Dock ne fonctionne pas correctement. Si vous regardez dans les messages d’erreurs à l’exécution, vous remarquez que vous en avez une qui vous dit que NSApplication ne répond pas aux messages d’état.

L’origine de ces erreurs et du comportement anormal de ce menu tient dans sa nature même. Le Dock est une application distincte qui gère l’affichage des menus flottants. La communication a lieu entre le menu du Dock et votre application par l’envoi de messages envoyés suite aux clics effectués dans le menu personnalisé, comme un clic sur “Boucle” par exemple. Malheureusement, cela signifie pour nous que, lorsque nous nous attendons à ce que le paramètre sender de notre message soit affecté à l’option de menu sélectionnée (ce qui aurait ici une influence sur le mode de lecture), il est en fait affecté à l’instance de l’application Dock, puisqu’il représente l’objet qui communique avec le Dock et le menu du Dock.

La morale de l’histoire c’est que l’on ne peut pas utiliser simplement le paramètre sender pour représenter l’option choisie dans un menu du Dock. Une solution à ce problème est de créer une outlet dans la classe Controller qui est connectée à l’option du menu pour pouvoir communiquer directement avec l’option du menu plutôt que de se servir de l’argument sender. Je vous laisse faire ces modifications, que vous retrouverez dans mon projet que vous pouvez télécharger ici.

Nous voici à la fin de l’article d’aujourd’hui. J’espère que vous l’avez apprécié et avez appris des choses utiles grâce à lui. J’ai évoqué plus haut le problème du redimensionnement des films à la taille de la vue NSMovieView et je voudrais bien faire quelque chose pour remédier à ce problème. On verra ça une prochaine fois !

Textes originaux en anglais sur O’Reilly : Movies and Menus par Mike Beam

mactov Programmation Cocoa , , ,

  1. Pas encore de commentaire
  1. Pas encore de trackbacks
Vous devez être identifié pour poster un commentaire