Tra le innumerevoli potenzialità offerte dalla tecnologia Java un ruolo significativo è, certamente, ricoperto dal Java Media Framework (JMF).
Il JMF è composto, fondamentalmente, da un insieme di API (Application Programming Interface) che consente, in modo versatile e relativamente semplice, di interfacciarsi con l'avvincente mondo dei media. Vedremo, in questo articolo, alcune funzionalità di base offerte dal JMF, esaminando i componenti più importanti ed il loro ruolo.
Reperire il JMF
Il sito delle Java Media Framework API rappresenta il punto di partenza nel quale è possibile trovare tutte le informazioni utili sul JMF, compreso il link per scaricare la versione più aggiornata.
Durante l'installazione si consiglia di scegliere l'opzione che consente di aggiornare automaticamente le variabili d'ambiente CLASSPATH e PATH.
Un primo esempio
Iniziamo subito a metterci all'opera con il JMF e costruiamo un semplice, ma perfettamente funzionante, riproduttore di file audio. Vediamo, prima di tutto, il codice:
Listato 1. Il riproduttore audio
import javax.media.*;
import java.io.File;
import java.io.IOException;
public class BasicAudioPlayer
{
private Player audioPlayer = null;
public BasicAudioPlayer (File file) throws IOException,
NoPlayerException, CannotRealizeException
{
// Converte il file in un oggetto di tipo URL e richiama
// il metodo statico createRealizedPlayer
audioPlayer = Manager.createRealizedPlayer(file.toURL());
}
public void playAudioFile()
{
audioPlayer.start();
}
public void stopAudioFile()
{
audioPlayer.stop();
audioPlayer.close();
}
public static void main(String[] args)
{
try
{
if (args.length == 1)
{
File audioFile = new File(args[0]);
BasicAudioPlayer player = new BasicAudioPlayer(audioFile);
System.out.println("Inizio riproduzione del file '" +
audioFile.getAbsolutePath() + "'");
System.out.println("Premere INVIO per interrompere la " +
"riproduzione ed uscire dal programma");
player.playAudioFile();
// Rimane in attesa della pressione del tasto INVIO
System.in.read();
System.out.println("Interruzione ed uscita dal programma");
player.stopAudioFile();
}
else
{
// Non è stato fornito il nome del file in input
System.out.println("È necessario fornire in input il nome "
+ "del file da riprodurre");
}
}
catch (Exception ex)
{
ex.printStackTrace();
}
System.exit(0);
}
}
Compiliamo la nostra applicazione ed eseguiamola, avendo cura di utilizzare la seguente sintassi:
java BasicAudioPlayer audiofile
dove audiofile
rappresenta il file audio che si vuole riprodurre.
Nota: i file audio supportati da JMF sono i più svariati: MP3, WAV, AU, solo per citarne alcuni.
Se tutto è stato fatto in modo corretto, dopo pochi istanti, sarà possibile ascoltare il file audio (non dimentichiamoci di accendere gli altoparlanti!) che abbiamo fornito in input alla nostra applicazione, proprio come se l'avessimo aperto attraverso un comune programma (sicuramente più completo e complesso !) come Windows Media Player.
Analizziamo, adesso, il sorgente del nostro programma. Come si può notare, il codice che abbiamo scritto è davvero molto semplice e sorprendentemente breve. Al suo interno, in modo analogo alla stragrande maggioranza delle applicazioni basate sul JMF, sono stati utilizzati due dei componenti principali di tale architettura:
- La classe Manager
- L'interfaccia Player
entrambi definiti nel package javax.media.
La classe Manager
La classe Manager interpreta il ruolo di una "fabbrica" che ha il compito di istanziare molti degli oggetti il cui tipo è identificato dalle interfacce definite nel Java Media Framework. Tra tali interfacce è compresa anche quella appena citata: javax.media.Player
.
Nelle righe di codice seguenti:
public BasicAudioPlayer(File file) throws IOException, NoPlayerException,
CannotRealizeException
{
audioPlayer = Manager.createRealizedPlayer(file.toURL());
}
si può notare come sia stato utilizzato un metodo statico della classe Manager, denominato createRealizedPlayer()
, proprio per ottenere un'istanza di Player pronta per essere utilizzata. Come è facile osservare, consultando la documentazione del JMF, la classe Manager mette a disposizione anche altri metodi per la creazione di oggetti di tipo Player.
L'interfaccia Player
Il termine "Player" è oramai molto diffuso ed è entrato a far parte del gergo comune. Si pensi, ad esempio, ad un CD Player, ad un MP3 Player o, ancora più semplicemente, ad un "vecchio" riproduttore di audio cassette. Anche un video registratore rappresenta un riproduttore audio-video e, pertanto, un Player.
In pratica, il JMF costruisce per ogni istanza di tipo Player un oggetto capace di svolgere le funzioni necessarie per interfacciarsi con un tipo particolare di file multimediale.
Tra le più classiche e note funzionalità offerte, un Player sarà in grado di eseguire operazioni di start e stop, proprio come faremmo noi utilizzando gli appositi tasti di un dispositivo multimediale. Nell'esempio precedente è possibile vedere come siano stati invocati proprio i metodi start()
e stop()
per avviare e interrompere la riproduzione del file audio.
Nel Java Media Framework sono definiti 6 possibili stati che descrivono la condizione in cui un Player si trova in un determinato istante. Vediamo quali sono e cosa indicano:
- Unrealized: Stato iniziale, immediatamente successivo alla costruzione dell'istanza di un Player
- Realizing: Vengono acquisite le risorse di sistema che verranno utilizzate durante la riproduzione e che non sono usate da altri Player.
- Realized: Il Player è "cosciente" del proprio compito in base all'input fornitogli. È ora possibile utilizzare i pannelli visuali associati al Player (ne discuteremo tra breve)
- Prefetching: Viene effettuata una sorta di preloading dei primi dati da riprodurre. Tali dati vanno a riempire un buffer creato ad hoc.
- Prefetched: Il Player è, finalmente, pronto a partire. Tutte le risorse e le informazioni necessarie sono state acquisite.
- Started: Inizia la riproduzione.
Vediamo uno state diagram che illustra il passaggio di un Player attraverso i vari stati sopra descritti:
È importante mettere in risalto il seguente concetto: «Ogni oggetto di tipo Player deve necessariamente passare attraverso gli stati Realized
e Prefetched
prima di poter arrivare allo stato Started
».
Tale passaggio, normalmente, implicherebbe un utilizzo importante delle risorse di sistema ma il JMF permette di lavorare in modo asincrono e registrare degli ascoltatori (listeners) che entrano in azione soltanto allo scatenarsi di un evento legato ad un determinato stato (ad esempio, quando il Player giunge allo stato Realized). Questo consente di continuare nell'esecuzione del programma eseguendo nel frattempo altre operazioni.
Per lavorare in modo asincrono, sarà sufficiente utilizzare uno dei seguenti metodi createPlayer()
messi a disposizione dal JMF nella classe Manager():
public static Player createPlayer(DataSource source)
public static Player createPlayer(MediaLocator sourceLocator)
public static Player createPlayer(java.net.URL sourceURL)
Nell' esempio precedente, invece, non avendo particolari esigenze di ottimizzazione, abbiamo utilizzato il metodo statico createRealizedPlayer()
della classe Manager che, lavorando in modo sincrono, blocca il flusso del programma fino a quando non sia stato creato un Player valido che si trovi già nello stato Realized (e, quindi, pronto per passare allo stato Prefetched).
Riproduzione di un file video
Abbiamo visto come scrivere una semplice applicazione che riproduca un file audio. La potenza di JMF è evidente quando ci si accorge che non è necessario, dal punto di vista del codice, impostare in nessun modo il Player in base al formato fornitogli in input. Il riconoscimento del file multimediale fornito in input avviene in maniera automatica.
Vediamo ora come, in modo molto simile a quanto visto per un file audio, sia possibile arricchire la funzionalità del nostro Player riproducendo anche i file di tipo video. A differenza di prima però, in questo caso, avremo bisogno di costruire una GUI che consenta di visualizzare il video in qualche modo. Anche in questa occasione il JMF ci viene in aiuto semplificandoci il compito. Infatti, il componente Player mette a disposizione alcuni metodi per la gestione dei componenti visuali. Nel nostro codice sorgente utilizzeremo i seguenti:
- getVisualComponent(): Restituisce un riferimento al componente grafico utilizzato per mostrare il video
- getControlPanelComponent(): Restituisce un riferimento al componente grafico che funge da pannello di controllo. In esso sono presenti i pulsanti play, stop, rewind
- getGainControl().getControlComponent(): Consente di accedere al componente grafico che può essere utilizzato per gestire il volume
Oltre a questi componenti, ogni istanza di tipo Player
può esporre una serie di controlli, utilizzabili per eseguire azioni ben precise, in relazione al tipo di media con cui si interfaccia il Player stesso. Il metodo getControls()
restituisce proprio tale lista di controlli che, facilmente, possono essere inseriti in un panel.
Nel nostro esempio, a tale scopo, utilizzeremo la seguente porzione di codice:
Control[] controls = player.getControls();
for (int i = 0; i < controls.length; i++)
{
if (controls[i].getControlComponent() != null)
{
tabPane.add(controls[i].getControlComponent());
}
}
Listato 2. Il codice completo
import javax.media.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
public class AudioVideoPlayer extends JFrame
{
private static final String FRAME_TITLE = "Un semplice riproduttore audio/video";
private static final String CONTROL_PANEL_TITLE = "Pannello di Controllo";
// Coordinate relative alla finestra dell'applicazione
private static final int LOC_X = 100;
private static final int LOC_Y = 100;
private static final int HEIGHT = 400;
private static final int WIDTH = 400;
private Player player = null;
// Il componente di tipo JTabbedPane utilizzato per la
// visualizzazione dei controlli associati al Player istanziato
private JTabbedPane tabPane = null;
public AudioVideoPlayer()
{
super(FRAME_TITLE);
setLocation(LOC_X, LOC_Y);
setSize(WIDTH, HEIGHT);
tabPane = new JTabbedPane();
this.getContentPane().add(tabPane);
// Creazione di una classe anonima per la gestione
// dell'evento windowCloing
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
closePlayer();
System.exit(0);
}
});
}
private JPanel createMediaPanel()
{
JPanel mainPnl = new JPanel();
GridBagLayout panelLayout = new GridBagLayout();
GridBagConstraints constr = new GridBagConstraints();
mainPnl.setLayout(panelLayout);
// Aggiunta del pannello ricavato dal metodo
// getVisualComponent()
if (player.getVisualComponent() != null)
{
constr.gridx = 0;
constr.gridy = 0;
constr.weightx = 1;
constr.weighty = 1;
constr.fill = GridBagConstraints.BOTH;
mainPnl.add(player.getVisualComponent(), constr);
}
// Aggiunta del pannello ricavato dal metodo
// getGainControl().getControlComponent() (se esistente)
if ((player.getGainControl() != null) &&
(player.getGainControl().getControlComponent() != null))
{
constr.gridx = 1;
constr.gridy = 0;
constr.weightx = 0;
constr.weighty = 1;
constr.gridheight = 2;
constr.fill = GridBagConstraints.VERTICAL;
mainPnl.add(player.getGainControl().getControlComponent(), constr);
}
// Aggiunta del pannello ricavato dal metodo
// getControlPanelComponent()
if (player.getControlPanelComponent() != null)
{
constr.gridx = 0;
constr.gridy = 1;
constr.weightx = 1;
constr.gridheight = 1;
if (player.getVisualComponent() != null)
{
constr.fill = GridBagConstraints.HORIZONTAL;
constr.weighty = 0;
}
else
{
constr.fill = GridBagConstraints.BOTH;
constr.weighty = 1;
}
mainPnl.add(player.getControlPanelComponent(), constr);
}
return mainPnl;
}
public void setMediaLocator(MediaLocator locator) throws IOException,
NoPlayerException, CannotRealizeException
{
setPlayer(Manager.createRealizedPlayer(locator));
}
public void setPlayer(Player newPlayer)
{
// Qualora sia presente un Player già attivo, viene chiuso
closePlayer();
player = newPlayer;
if (player == null) return;
tabPane.removeAll();
tabPane.add(CONTROL_PANEL_TITLE, createMediaPanel());
Control[] controls = player.getControls();
for (int i = 0; i < controls.length; i++)
{
if (controls[i].getControlComponent() != null)
{
tabPane.add(controls[i].getControlComponent());
}
}
}
private void closePlayer()
{
if (player != null)
{
player.stop();
player.close();
}
}
public static void main(String[] args)
{
try
{
if (args.length == 1)
{
AudioVideoPlayer mpf = new AudioVideoPlayer();
mpf.setMediaLocator(new MediaLocator(new File(args[0]).toURL()));
mpf.setVisible(true);
}
else
{
System.out.println("E' necessario fornire in input il nome del " +
"file da riprodurre");
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
Eseguiamo, quindi, l'applicazione con il file .mpg in allegato digitando da riga di comando:
java AudioVideoPlayer montavit-the-hero_1.mpg
e visualizzeremo la finestra seguente:
Il codice, come si può notare non presenta particolari difficoltà. Il lettore attento avrà notato l'utilizzo di una nuova classe: MediaLocator
(anch'essa appartenente al package javax.media
). La funzione di tale classe è simile a quella fornita dalla classe java.net.URL
, nel senso che serve ad individuare la locazione di una sorgente di dati di tipo multimediale. Quando parleremo di trasmissione di media attraverso il protocollo RTP (in un prossimo articolo) vedremo che la classe MediaLocator presenta dei vantaggi rispetto alla URL.
Alla prossima.