Mit dem Videoplayer, der in diesem Abschnitt vorgestellt wird, kann man keine Videoformate
darstellen (z. B. mpeg). Das Abspielen von mpeg- oder mov-Dateien kann man jedoch mit dem Java Media Framework bewerkstelligen. Das Java Media Framework
ist eine Erweiterung zum JDK, die ebenfalls von Sun spezifiziert wurde.
Bei Applets besteht eine andere Möglichkeit zum Abspielen von Videos über die MIME-Types des Browsers mit dem Aufruf von externen Programmen.
Diese Vorgehensweise hat jedoch den Nachteil, dass man den Player nicht aus der
Java-Anwendung steuern kann (falls der nicht mit einem Plug-in verknüpft wurde).
Dieses
Applet zeichnet einfach mehrere Einzelbilder nacheinander in der richtigen
Reihenfolge an dieselbe Stelle. Dadurch entsteht beim Betrachter
der Eindruck, dass sich das Bild bewegt.
Was bei dieser Methode Probleme bereitet,
ist das Laden der Bilder.
Um die Animation gleich von Beginn an flüssig zu gestalten, muss
gewährleistet sein, dass bei Beginn der Animation alle Bilder
geladen sind. Ist dies nicht der Fall, wird das
entsprechende Bild beim ersten Aufruf von drawImage()
geladen und verzögert so den Zeichenvorgang.
Dadurch kann man regelrecht mitverfolgen, wie das
Bild gezeichnet wird.
Also sollte das Programm so lange mit dem Abspielen der
Animation warten, bis die Bilder vollständig geladen sind.
Das kann man z. B. über die Methoden prepareImage() und checkImage()
bewerkstelligen, wie es bereits in Abschnitt beschrieben wurde.
Eine einfachere Möglichkeit besteht allerdings in der Verwendung
der Klasse MediaTracker.
MediaTracker wurde bereits bei den Grafik-Buttons im
Abschnitt über Ereignisbehandlung und beim
Einbinden von Bildern benutzt.
Dort sollten die Buttons
genau die gleiche Größe wie die Bilder besitzen. getHeight() und
getWidth() liefern aber erst die richtige Größe, wenn die Bilder
geladen sind. Deshalb ist die Überwachung des Ladevorgangs mit MediaTracker erforderlich.
Die
Bilder, die der Videoplayer verwendet, müssen alle dieselbe
Größe haben. Der Name einer Bilddatei besteht aus einem Basisnamen, der
für alle Bilder gleich ist, einer Zahl, welche die Position des Bildes
innerhalb eines Animationsablaufs angibt, und der Endung, die das Bildformat
angibt. Die Bilder müssen durchgehend nummeriert sein, und die Zahl des ersten
Bildes muss 1 sein.
Ein Beispiel für eine solche Bildnamen wären:
bild1.jpg, bild2.jpg, bild3.jpg, ...
Der Basisname der Bilder, die Anzahl der Bilder und das Bildformat werden
dem Applet als Parameter übergeben:
String imagebase = getParameter("ImageBase");
String imagetype = getParameter("ImageType");
int number = Integer.parseInt(getParameter("Number"));
Mit dieser Information kann sich das Applet die Namen aller Bilddateien
zusammensetzen.
Der nächste Schritt,
der vorgenommen werden muss, ist das Laden der Bilder. Hierzu
wird die Klasse MediaTracker benutzt:
Image images[] = new Image[number];
MediaTracker tracker = new MediaTracker(this);
for (int i= 0; i<number;i++) {
images[i] = getImage(getCodeBase(),
imagebase+(i+1)+"."+imagetype);
tracker.addImage(images[i], i);
}
Der Konstruktor von MediaTracker besitzt einen Parameter vom Typ Component.
Mit dem so erzeugten Exemplar tracker kann man den
Ladevorgang von Bildern steuern und überwachen. Hierzu muss man aber zuerst
festlegen, welche Bilder vom MediaTracker überwacht werden sollen.
Dies macht man mit der Methode addImage().
addImage(Image, int) erhält
als Parameter das zu überwachende Bild und eine Zahl vom Typ int.
Die Zahl
legt die ID fest, der das Bild zugeordnet wird. Die ID ist nicht eindeutig, d. h. einer ID können mehrere Bilder zugeordnet werden.
Auf diese Weise kann man Bilder gruppieren.
Mit der Methode checkID(int ID) kann der
Status der Bilder, die einer ID zugeordnet sind,
abgefragt werden. Dies zeigt, dass die Klasse MediaTracker Bilder nur
nach ihren IDs verwaltet. Alle Bilder mit derselben ID werden gleich behandelt.
Im obigen Programmauszug wird jedem Bild eine eigene ID zugeordnet. Die Erklärung,
warum dies so gemacht wird, folgt später.
checkID() liefert einen booleschen Wert, der anzeigt, ob schon alle Bilder der
übergebenen ID geladen wurden. checkID() startet den Ladevorgang jedoch nicht.
checkID() gibt es allerdings noch in einer anderen Variante, die
es ermöglicht, den Ladevorgang auch zu starten:
public synchronized boolean checkID(int id, boolean load)
Erhält load den Wert true, wird der Ladevorgang
gestartet.
Beim
Videoplayer soll nun so lange gewartet werden, bis alle Bilder geladen wurden.
Hierfür definiert die Klasse MediaTracker die Methode waitForID(int id):
for (int i=0; i < number; i++) {
getAppletContext().showStatus(
"Loading Image "+(i+1)+" of "+number);
try {
tracker.waitForID(i);
}
catch (InterruptedException e) {
System.err.println("Error: "+e);
}
// Fehlerüberprüfung
if (tracker.isErrorID(i)) {
Object[] errors = tracker.getErrorsID(i);
for(int n=0;n<errors.length;n++)
System.err.println(errors[n]);
}
}
In diesem Beispiel wird in einer Schleife ein Bild nach dem anderen geladen.
Damit der Betrachter des Applets über den aktuellen Stand des Ladevorgangs der
Bilder informiert ist, wird in der Statuszeile des Browsers bzw. des Appletviewers
die Anzahl der bereits geladenen und die Gesamtzahl
der Bilder ausgegeben.
waitForID() lädt alle Bilder, die einer
ID zugeordet sind. Dies erklärt auch, warum oben jedem Bild eine eigene
ID zugewiesen wurde. Wenn alle Bilder die gleiche ID besitzen würden, dann
könnte man durch einen Aufruf von waitForID() zwar das Laden aller
Bilder veranlassen, nicht jedoch den Text in der Statuszeile
erneuern, da waitForID() den Programmablauf so lange blockiert, bis
auch wirklich alle Bilder geladen sind.
Wenn waitForID() von einem anderen Thread unterbrochen wird,
löst dies eine InterruptedException aus.
Will man die Bilder von allen IDs laden, kann man die Methode waitForAll()
der Klasse MediaTracker verwenden.
Fehler beim Laden
werden weder von checkID() noch von
waitForID() erfasst.
Tritt beim Laden ein Fehler auf, so wird der Ladevorgang abgebrochen und
das Bild als geladen angesehen, d. h. ein Aufruf von checkID() liefert true. Deshalb sollte man zusätzlich Fehler abfragen.
Hierzu kann man eine der Methoden
verwenden. Informationen über die aufgetretenen Fehler können mit
den Methoden
erfragt werden.
Konnte ein Bild nicht geladen werden, weil der Name falsch angegeben wurde, liefern
obige Methoden die Exemplare der Klasse Image, bei denen die Fehler aufgetreten sind. Hiermit kann
die Fehlerquelle eindeutig bestimmt werden.
Sind die Bilder einmal geladen, müssen sie noch in der richtigen Reihenfolge
angezeigt werden. Dies wird fast genauso gemacht, wie bei der Laufschrift im
letzten Abschnitt.
Der Videoplayer
dieses Beispiels stellt dem Benutzer zusätzlich Kontrollfunktionen
zur Verfügung, die es ihm ermöglichen, den Animationsablauf
- zu stoppen,
- zu starten,
- schrittweise vorwärts ablaufen zu lassen,
- schrittweise rückwärts ablaufen zu lassen,
- mit variabler Geschwindigkeit ablaufen zu lassen.
Hierzu besitzt das Applet ein Panel, das die Steuerelemente enthält, und
eine von Canvas abgeleitete Komponente ImageCanvas, in der die
Bilder angezeigt werden.
Die wichtigste Komponente für die Darstellung der Animation ist das ImageCanvas.
Durch Betätigung
der Steuerelemente werden die einzelnen Methoden des ImageCanvas aufgerufen.
Dem Konstruktor des ImageCanvas werden die Bilder, aus denen sich die
Animation zusammensetzt, übergeben:
public ImageCanvas(Image images[]) {
this.images = images; // Speichern der Bilder
}
ImageCanvas implementiert das Interface Runnable.
Der damit verbundene Thread kann durch zwei Methoden beeinflusst werden:
public void turnOn() {
if (t == null) { // Wenn kein Thread angelegt ist
t = new Thread(this); // Erzeugen...
t.start(); // und Starten des Threads
}
}
Diese Methoden sind analog zu den Methoden start() und stop(), die bei
der Laufschrift verwendet wurden. Sie werden bei
Betätigung der Steuerelemente »start« und »stop« aufgerufen und starten
bzw. beenden die Animation.
In der run()-Methode ist
der Ablauf der Animation festgelegt:
public void run() {
Thread me = Thread.currentThread();
while(t == me) { // Endlosschleife
repaint(); // Bild zeichnen
try {
t.sleep(delay); // Verzögerung
}
catch (InterruptedException e) {
System.err.println("Error: "+e);
}
currentimage = (currentimage + 1) % images.length;
}
}
run() enthält eine Endlosschleife, in der bei jedem Durchlauf die
Komponente neu gezeichnet und das
Feld currentimage hochgezählt wird.
Ist currentimage gleich der maximalen Bilderzahl minus eins,
wird currentimage wieder auf null gesetzt. Im Beispiel wird
hierfür der modulo-Operator (%) verwendet:
currentimage = (currentimage + 1) % images.length;
Dadurch enthält currentimage zu jedem Zeitpunkt den Index in dem Array mit
den Bildern. Bei jedem Aufruf von repaint() wird
das mit currentimage indexierte Bild in die Komponente gemalt:
public void update(Graphics g) {
// Zeichnen des aktuellen Bildes
g.drawImage(images[currentimage], 0, 0, this);
}
Bei diesem Applet erfolgt das Zeichnen des Bildes in der Methode
update(). Ihre Ausführung wird durch einen Aufruf von
repaint() ausgelöst.
Der Zusammenhang zwischen update(), paint() und
repaint() wird im folgenden Abschnitt erläutert.
Abbildung 12.3: Ein Videoplayer in Java
 |
Material zum Beispiel
Copyright © 2002 dpunkt.Verlag, Heidelberg. Alle Rechte vorbehalten.