/*
 * File:     QTMediaPlayer.java
 * Project:  MPI Linguistic Application
 * Date:     03 April 2006
 *
 * Copyright (C) 2001-2006  Max Planck Institute for Psycholinguistics
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package mpi.eudico.client.annotator.player;

import mpi.eudico.client.annotator.Constants;
import mpi.eudico.client.annotator.ElanLayoutManager;
import mpi.eudico.client.annotator.ElanLocale;

import mpi.eudico.client.annotator.export.ImageExporter;

import mpi.eudico.client.annotator.gui.FormattedMessageDlg;

import mpi.eudico.client.mediacontrol.ControllerEvent;
import mpi.eudico.client.mediacontrol.ControllerListener;
import mpi.eudico.client.mediacontrol.ControllerManager;
import mpi.eudico.client.mediacontrol.PeriodicUpdateController;

import mpi.eudico.client.util.TimeFormatter;

import mpi.eudico.server.corpora.clomimpl.abstr.MediaDescriptor;

import quicktime.QTException;
import quicktime.QTSession;

import quicktime.app.display.QTCanvas;

import quicktime.app.time.TaskAllMovies;

import quicktime.app.view.QTComponent;
import quicktime.app.view.QTFactory;

import quicktime.io.OpenMovieFile;
import quicktime.io.QTFile;

import quicktime.qd.Pict;
import quicktime.qd.PixMap;
import quicktime.qd.QDDimension;
import quicktime.qd.QDGraphics;
import quicktime.qd.QDRect;

import quicktime.std.StdQTConstants;

import quicktime.std.clocks.TimeBase;
import quicktime.std.clocks.TimeRecord;

import quicktime.std.movies.Movie;
import quicktime.std.movies.MovieController;
import quicktime.std.movies.Track;

import quicktime.std.movies.media.DataRef;
import quicktime.std.movies.media.MPEGMedia;
import quicktime.std.movies.media.Media;

import quicktime.util.QTUtils;
import quicktime.util.RawEncodedImage;

import java.awt.*;
import java.awt.event.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.HierarchyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.DirectColorModel;
import java.awt.image.MemoryImageSource;

import java.io.File;

import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;


/**
 * The QuickTime implementation of an elan media player
 */
public class QTMediaPlayer extends ControllerManager implements ElanMediaPlayer,
    ControllerListener, ActionListener {
    public static final int STOP_WITH_STOP_TIME = 0;
    public static final int STOP_WITH_PREVIEW = 1;
    static int scratchCount = 0;

    //  private QTJComponent canvas;
    private QTComponent canvas;
    private float aspectRatio;
    private long offset;
    private long milliSecondsPerSample;
    private long intervalStopTime;
    private boolean playingInterval;
    private PeriodicUpdateController periodicController;
    private TimeBaseExtremesCallBack endOfMediaCallback;
    private JPopupMenu popup;
    private boolean isWavPlayer;
    private MediaDescriptor mediaDescriptor;
    private ElanLayoutManager layoutManager;
    private boolean detached;
    private JMenuItem durationItem;
    private JMenuItem detachItem;
    private JMenuItem infoItem;
    private JMenuItem saveItem;

    // private VisualComponent visualComponent;    
    //private Panel visualComponent;
    private Component visualComponent;
    private int stopMode;
    private TimeBase timeBase;
    private long exactStopTime;
    private Movie movie;
    private MovieController controller;
    private QTFile scratchFile;
    private String scratchName = "tmpMedia";
    private boolean isEditSave = false;
    private int mpegImageWidth = 0;
    private int mpegImageHeight = 0;

    /**
     * Create a QTMediaPlayer for a media URL
     *
     * @param mediaDescriptor DOCUMENT ME!
     *
     * @throws NoPlayerException DOCUMENT ME!
     */
    public QTMediaPlayer(MediaDescriptor mediaDescriptor)
        throws NoPlayerException {
        this.mediaDescriptor = mediaDescriptor;

        // WebStart related initialization, see at the end of this file for details
        initQTJNI();

        try {
            QTSession.open();

            String URLString = mediaDescriptor.mediaURL;

            System.out.println("mediaURL = " + URLString);

            //URLString = "rtsp://nt06.mpi.nl:80/De_Eng.mp4";
            //DataRef dataRef = new DataRef(rtspURL);
            //Movie mov = Movie.fromDataRef(dataRef, StdQTConstants.newMovieActive | StdQTConstants4.newMovieAsyncOK);
            // remove the file: part of the URL
            String fileString = URLString;

            if (URLString.startsWith("file:")) {
                fileString = URLString.substring(5);
            } else { // rtsp stuff?
            }

            QTFile qtFile = new QTFile(fileString);
            movie = Movie.fromFile(OpenMovieFile.asRead(qtFile));
            movie.setTimeScale(1000);
            movie.update();

            Movie editMovie = createScratchMovie();

            if (editMovie != null) {
                movie = editMovie;
                isEditSave = true;
            }

            quicktime.app.view.MoviePlayer player = new quicktime.app.view.MoviePlayer(movie);
            controller = new MovieController(movie);
            controller.activate();
            controller.setAttached(true);

            if (URLString.endsWith("wav") ||
                    mediaDescriptor.mimeType.equals(
                        MediaDescriptor.GENERIC_AUDIO_TYPE)) {
                isWavPlayer = true; // ask api??
                canvas = null;
                visualComponent = null;
            } else {
                if (URLString.startsWith("file") &&
                        (URLString.endsWith("mpg") ||
                        URLString.endsWith("mpeg"))) {
                    MPEGVideoHeader mpegHeader = new MPEGVideoHeader(fileString);
                    mpegImageWidth = mpegHeader.getWidth();
                    mpegImageHeight = mpegHeader.getHeight();

                    // System.out.println("MPEG w: " + mpegImageWidth + " - h: " + mpegImageHeight);
                }

                //canvas = QTFactory.makeQTJComponent(player);
                //canvas.asJComponent().addMouseListener(new MouseHandler());
                canvas = QTFactory.makeQTComponent(movie);

                // canvas = QTFactory.makeQTComponent(controller);
                visualComponent = canvas.asComponent();
                visualComponent.addMouseListener(new MouseHandler());

                //		visualComponent = new VisualComponent(canvas.asComponent());
                //		visualComponent = new Panel();
                //		visualComponent.setLayout(null);
                popup = new JPopupMenu();
                durationItem = new JMenuItem(ElanLocale.getString(
                            "Player.duration") + ":  " +
                        TimeFormatter.toString(getMediaDuration()));
                durationItem.setEnabled(false);
                infoItem = new JMenuItem(ElanLocale.getString("Player.Info"));
                infoItem.addActionListener(this);
                saveItem = new JMenuItem(ElanLocale.getString(
                            "Player.SaveFrame"));
                saveItem.addActionListener(this);
                popup.addSeparator();
                popup.add(saveItem);
                popup.add(infoItem);
                popup.add(durationItem);
                JPopupMenu.setDefaultLightWeightPopupEnabled(false);
            }

            //		for (int i = 1; i <= movie.getTrackCount(); i++) {
            //			quicktime.std.movies.Track t = movie.getIndTrack(i);
            //			System.out.println("media " + t.getMedia());
            //			System.out.println("sample2 " + t.getMedia().sampleNumToMediaTime(1));
            //		}
            float boundsX = player.getOriginalSize().getWidth(); // 352     352
            float boundsY = player.getOriginalSize().getHeight(); // 288   240

            // System.out.println("x: " + boundsX + "     y: " + boundsY);
            aspectRatio = boundsX / boundsY;
            System.out.println("Aspect ratio: " + aspectRatio);

            offset = mediaDescriptor.timeOrigin;

            // ask api
            milliSecondsPerSample = 40;

            // callback for end of media
            timeBase = movie.getTimeBase();
            endOfMediaCallback = new TimeBaseExtremesCallBack(timeBase,
                    StdQTConstants.triggerAtStop);
            endOfMediaCallback.callMeWhen();

            stopMode = STOP_WITH_PREVIEW; //STOP_WITH_STOP_TIME;//

            movie.goToBeginning();

            if (isWavPlayer) {
                TaskAllMovies.addMovieAndStart();

                //stopMode = STOP_WITH_STOP_TIME;//??
            }
        } catch (QTException e) {
            System.out.println("QTException while creating QT player ");
            e.printStackTrace();
            QTSession.close();
            throw new NoPlayerException("QTException while creating QT player.");
        }
    }

    public MediaDescriptor getMediaDescriptor() {
        return mediaDescriptor;
    }

    public Movie getMovie() {
        if (isEditSave) {
            return movie;
        }

        return null;
    }

    /**
     * Returns the (mpeg) media width and height as read from the mpegfile.
     * @return the dimension according to fields in the stream headers
     */
    public Dimension getMediaFileDimension() {
        if ((mpegImageWidth > 0) && (mpegImageHeight > 0)) {
            return new Dimension(mpegImageWidth, mpegImageHeight);
        }

        return null;
    }

    /**
     * DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public String getFrameworkDescription() {
        return "Quicktime For Java Media Player";
    }

    /**
     * Elan controllerUpdate Used to stop at the stop time in cooperation with
     * the playInterval method
     *
     * @param event DOCUMENT ME!
     */
    public synchronized void controllerUpdate(ControllerEvent event) {
    }

    /**
     * play between two times. This method uses the contollerUpdate method to
     * detect if the stop time is passed. The setintervalStopTime method of
     * JMF can not be used because it gives unstable behaviour
     *
     * @param startTime DOCUMENT ME!
     * @param stopTime DOCUMENT ME!
     */
    public void playInterval(long startTime, long stopTime) {
        if ((movie == null) || playingInterval || (stopTime <= startTime)) {
            return;
        }

        try {
            // load small intervals in memory 
            // time scale??
            if ((stopTime - startTime) < 5000) {
                int loadBeginTime = ((startTime - 5000) > 0)
                    ? (int) (startTime - 5000) : 0;
                int loadDuration = (int) (stopTime - loadBeginTime + 1000);

                //System.out.println("Loading " + loadDuration + " milliseconds in ram");
                movie.loadIntoRam(loadBeginTime, loadDuration,
                    StdQTConstants.unkeepInRam);

                //		movie.loadIntoRam(loadBeginTime, loadDuration, 0);//StdQTConstants.unkeepInRam);
            }

            setMediaTime(startTime);
            Thread.sleep(100);

            //movie.prePreroll((int)startTime, movie.getPreferredRate());
            movie.preroll((int) startTime, movie.getPreferredRate());

            /*
            controller.setSelectionBegin(new TimeRecord (1000, startTime));
            controller.setSelectionDuration(new TimeRecord (1000, stopTime - startTime));
            controller.setLooping(true);
            controller.setPlaySelection(true);
            startControllers();
            controller.play(1f);
            */

            // correct stoptime for frame boundary?
            if (stopMode == STOP_WITH_STOP_TIME) {
                setStopTime(stopTime);
            } else {
                movie.setPreviewTime((int) startTime,
                    (int) (stopTime - startTime));
                movie.setPreviewMode(true);
            }

            exactStopTime = stopTime;
            start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Empty implementation for ElanMediaPlayer Interface
     * Only usefull for player that correctly supports setting stop time
     */
    public void setStopTime(long stopTime) {
        try {
            timeBase.setStopTime(new TimeRecord(1000, stopTime));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Gets the display Component for this Player.
     *
     * @return DOCUMENT ME!
     */
    public java.awt.Component getVisualComponent() {
        return visualComponent;

        /*
        if (isWavPlayer) {
            return null;
        } else {
            //return canvas.asJComponent();

        //       return canvas.asComponent();
            return visualComponent;
        }
        */
    }

    public java.awt.Component createNewVisualComponent() {
        if (isWavPlayer) {
            return null;
        } else {
            try {
                canvas = QTFactory.makeQTComponent(movie);
                visualComponent = canvas.asComponent();
                visualComponent.addMouseListener(new MouseHandler());
            } catch (QTException qte) {
                System.out.println("Could not create a new visual component. ");
                qte.printStackTrace();
            }

            return visualComponent;
        }
    }

    /**
     * Gets the ratio between width and height of the video image
     *
     * @return DOCUMENT ME!
     */
    public float getAspectRatio() {
        if (movie == null) {
            return 0;
        }

        //float aspectRatio = (float) (canvas.asJComponent().getPreferredSize()
        //                                   .getWidth() / canvas.asJComponent().getPreferredSize().getHeight());
        //float aspectRatio = (float) (canvas.asComponent().getPreferredSize().getWidth() /
        //									canvas.asComponent().getPreferredSize().getHeight());
        return aspectRatio;
    }

    /**
     * Starts the Player as soon as possible. is not synchronized in JMF
     */
    public synchronized void start() {
        if (movie == null) {
            return;
        }

        // do not try to start at the end of the media, the JMF player blocks
        // start playing at the beginning of the media data
        if ((getMediaDuration() - getMediaTime()) < 40) {
            setMediaTime(0);
        }

        try {
            // make sure all managed controllers are started
            startControllers();

            movie.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Stop the media player
     */
    public synchronized void stop() {
        if (movie == null) {
            return;
        }

        try {
            movie.stop();

            // make sure all managed controllers are stopped
            stopControllers();
            setControllersMediaTime(getMediaTime());
        } catch (QTException qt) {
            qt.printStackTrace();
        }
    }

    /**
     * Tell if this player is playing
     *
     * @return DOCUMENT ME!
     */
    public boolean isPlaying() {
        if (movie == null) {
            return false;
        }

        float rate = 0;

        try {
            rate = movie.getRate();
        } catch (Exception qt) {
            qt.printStackTrace();
        }

        return (rate != 0);
    }

    /**
     * Set the offset to be used in get and set media time for this player
     *
     * @param offset the offset in milli seconds
     */
    public void setOffset(long offset) {
        this.offset = offset;
        mediaDescriptor.timeOrigin = offset;
    }

    /**
     * DOCUMENT ME!
     *
     * @return the offset used by this player
     */
    public long getOffset() {
        return offset;
    }

    /**
     * Sets the Clock's media time in milli seconds. is not synchronized in JMF
     *
     * @param time DOCUMENT ME!
     */
    public synchronized void setMediaTime(long time) {
        if (movie == null) {
            return;
        }

        // do not set media time on a started player
        if (isPlaying()) {
            stop();
        }

        try {
            movie.setTime(new TimeRecord(1000, time + offset));

            // set the media time for the connected controllers
            setControllersMediaTime(time);
        } catch (Exception qt) {
            qt.printStackTrace();
        }
    }

    public void nextFrame() {
        setMediaTime(getMediaTime() + getMilliSecondsPerSample());
    }

    public void previousFrame() {
        setMediaTime(getMediaTime() - getMilliSecondsPerSample());
    }

    /**
     * Gets this Clock's current media time in milli seconds.
     *
     * @return DOCUMENT ME!
     */
    public long getMediaTime() {
        if (movie == null) {
            return 0;
        }

        long time = 0;

        try {
            time = movie.getTime();
        } catch (Exception qt) {
            qt.printStackTrace();
        }

        //System.out.println("get mt : " + (time - offset));
        return time - offset;
    }

    /**
     * DOCUMENT ME!
     *
     * @return the step size for one frame
     */
    public long getMilliSecondsPerSample() {
        return milliSecondsPerSample;
    }

    /**
     * DOCUMENT ME!
     *
     * @param milliSeconds the step size for one frame
     */
    public void setMilliSecondsPerSample(long milliSeconds) {
        milliSecondsPerSample = milliSeconds;
    }

    /**
     * Sets the temporal scale factor.
     *
     * @param rate DOCUMENT ME!
     */
    public synchronized void setRate(float rate) {
        if (movie == null) {
            return;
        }

        // do not set rate on a started player
        if (isPlaying()) {
            stop();
        }

        try {
            movie.setPreferredRate(rate);
            setControllersRate(rate);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Gets the current temporal scale factor.
     *
     * @return DOCUMENT ME!
     */
    public float getRate() {
        if (movie == null) {
            return 0;
        }

        float rate = 0;

        try {
            rate = movie.getPreferredRate();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return rate;
    }

    /**
     * Get the duration of the media represented by this object in milli
     * seconds.
     *
     * @return DOCUMENT ME!
     */
    public long getMediaDuration() {
        if (movie == null) {
            return 0;
        }

        long duration = 0;

        try {
            duration = movie.getDuration();
        } catch (Exception qt) {
            qt.printStackTrace();
        }

        //System.out.println("dur " + duration);
        return duration - offset;
    }

    /**
     * Sets the volume as a number between 0 and 1
     *
     * @param volume DOCUMENT ME!
     */
    public void setVolume(float volume) {
        if (movie == null) {
            return;
        }

        try {
            movie.setVolume(volume);
        } catch (Exception qt) {
            qt.printStackTrace();
        }
    }

    /**
     * Gets the volume as a number between 0 and 1
     *
     * @return DOCUMENT ME!
     */
    public float getVolume() {
        if (movie == null) {
            return 0;
        }

        float volume = 0;

        try {
            volume = movie.getVolume();
        } catch (Exception qt) {
            qt.printStackTrace();
        }

        return volume;
    }

    /**
     * Sets the way an interval is being played by QT.
     * @param mode the new stop mode, one of <code>STOP_WITH_PREVIEW</code>
     * or <code>STOP_WITH_STOP_TIME</code>
     */
    public void setStopMode(int mode) {
        if ((mode == STOP_WITH_PREVIEW) || (mode == STOP_WITH_STOP_TIME)) {
            stopMode = mode;
        }
    }

    /**
     * Attempts to create a scratch movie file for save editing.
     * This is to avoid modifications to an mpeg file when using 2d
     * annotations (i.e. adding tracks).
     *
     * @return the temp scratch movie, or null
     */
    private Movie createScratchMovie() {
        if (movie == null) {
            return null;
        }

        try {
            Media media = null;

            if (movie.getTrackCount() >= 1) {
                Track origTrack = movie.getTrack(1);
                media = origTrack.getMedia();

                if (media instanceof MPEGMedia) {
                    isWavPlayer = false;

                    // create a new editable movie object to prevent modification of 
                    // mpg files when using svg/graphic annotations	
                    File tempFile = new File(Constants.ELAN_DATA_DIR,
                            (scratchName + scratchCount++));

                    // if deletion did fail on last exit...
                    if (tempFile.exists()) {
                        // if it is more than 24 hours old
                        long modified = tempFile.lastModified();
                        long age = System.currentTimeMillis() - modified;

                        if (age >= (24 * 60 * 60 * 1000)) {
                            tempFile.delete();
                        }
                    }

                    scratchFile = new QTFile(tempFile);

                    scratchFile.createMovieFile(QTUtils.toOSType("TOVD"),
                        StdQTConstants.newMovieActive |
                        StdQTConstants.createMovieFileDeleteCurFile);

                    scratchFile.deleteOnExit();

                    File resourceFile = new File(scratchFile.getAbsolutePath() +
                            ".#res");

                    if (resourceFile.exists()) {
                        resourceFile.deleteOnExit();
                    }

                    //Movie editMovie = Movie.createMovieFile(scratchFile, QTUtils.toOSType("TOVD"),
                    //	StdQTConstants.newMovieActive | StdQTConstants.createMovieFileDeleteCurFile |
                    //	StdQTConstants.createMovieFileDontCreateResFile);
                    Movie editMovie = new Movie();
                    int dataRefCount = media.getDataRefCount();
                    DataRef mediaDataRef = media.getDataRef(dataRefCount);
                    QDDimension origSize = origTrack.getSize();
                    Track editTrack = editMovie.newTrack(origSize.getWidthF(),
                            origSize.getHeightF(), origTrack.getVolume());
                    int ts = movie.getTimeScale();

                    //Media editMedia = new MPEGMedia(editTrack, ts, mediaDataRef);

                    /*Media editMedia = */ Media.newFromType(StdQTConstants.MPEGMediaType,
                        editTrack, ts, mediaDataRef);

                    int duration = origTrack.getDuration();

                    origTrack.insertSegment(editTrack, 0, duration, 0);
                    origTrack.copySettings(editTrack);

                    OpenMovieFile omf = OpenMovieFile.asWrite(scratchFile);

                    editMovie.addResource(omf, 0, scratchFile.getName());
                    omf.close();

                    Movie nextMovie = Movie.fromFile(OpenMovieFile.asRead(
                                scratchFile));

                    if (nextMovie != null) {
                        nextMovie.setTimeScale(ts);
                        nextMovie.update();

                        /*
                        System.out.println("Edit Movie: duration: " + nextMovie.getDuration());
                        System.out.println("Edit Movie: num tracks: " + nextMovie.getTrackCount());
                        System.out.println("Edit Movie: num samples: " + nextMovie.getTrack(1).getMedia().getSampleCount());
                        */
                        QTUtils.reclaimMemory();

                        return nextMovie;
                    }
                }
            }
        } catch (QTException qte) {
            qte.printStackTrace();
        }

        return null;
    }

    /**
     * Grabs the current video frame and converts it to an Image object.
     *
     * @return the current video frame
     */
    public Image getCurrentFrameImage() {
        return getFrameImageForTime(getMediaTime());
    }

    /**
     * Grabs the frame for the given time and converts it to a BufferedImage.<br>
     * The size of the image is determined by the size read from the header of
     * the mediafile; QT often uses another size for MPEG-1 files.
     * QT on Windows uses another default pixel format than QT on the Mac; the current
     * implementation seems to work on both platforms.
     *
     * @param time the media time for the frame
     * @return the frame image or null
     */
    public Image getFrameImageForTime(long time) {
        try {
            // by default mpeg movies seem to be sized to 320 x 240 by qt
            // this is not right...
            int w = 352;
            int h = 288;

            if ((mpegImageWidth > 0) && (mpegImageHeight > 0)) {
                w = mpegImageWidth;
                h = mpegImageHeight;
            } else {
                //w = (int)(movie.getNaturalBoundsRect().getWidth() * movie.getMatrix().getSx());
                //h = (int)(movie.getNaturalBoundsRect().getHeight() * movie.getMatrix().getSy());
                w = movie.getNaturalBoundsRect().getWidth();
                h = movie.getNaturalBoundsRect().getHeight();
            }

            Pict pict = movie.getTrack(1).getPict((int) time);

            //System.out.println("Def pixel format: " + QDGraphics.kDefaultPixelFormat);
            // Windows: 1111970369 == k32BGRAPixelFormat
            // MACOS: 32 == k32ARGBPixelFormat
            QDGraphics offScr = new QDGraphics(QDGraphics.kDefaultPixelFormat,
                    new QDRect(0, 0, w, h));
            pict.draw(offScr, offScr.getBounds());

            PixMap pixmap = offScr.getPixMap();
            RawEncodedImage raw = RawEncodedImage.fromPixMap(pixmap);

            // copy bytes to an array
            int intsPerRow = pixmap.getRowBytes() / 4;
            int[] pixels = new int[intsPerRow * h];
            raw.copyToArray(0, pixels, 0, pixels.length);

            DirectColorModel model = new DirectColorModel(32, // bits/sample
                    0x00ff0000, // R
                    0x0000ff00, // G
                    0x000000ff, // B
                    0x00000000); // ignore alpha

            Image image = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(
                        w, h, model, pixels, 0, intsPerRow));

            //QTUtils.reclaimMemory();
            return image;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * If a scratch file has been created, delete it.
     */
    protected void finalize() {
        if (scratchFile.exists()) {
            File resourceFile = new File(scratchFile.getAbsolutePath() +
                    ".#res");

            if (resourceFile.exists()) {
                resourceFile.delete();
            }

            scratchFile.delete();
        }

        if (isWavPlayer) {
            TaskAllMovies.removeMovie();
        }

        QTSession.close();
    }

    // Greg's code
    // for WIN32 under JNLP load QT JNI librarys manualy. OS-X does not require this
    private void initQTJNI() {
        if (System.getProperty("os.name").regionMatches(false, 0, "Win", 0, 3)) {
            System.loadLibrary("QTJava");
            System.loadLibrary("QTJavaNative");
        }
    }

    public void setLayoutManager(ElanLayoutManager layoutManager) {
        if ((this.layoutManager == null) && !isWavPlayer) {
            detachItem = new JMenuItem(ElanLocale.getString("Detachable.detach"));
            detachItem.addActionListener(this);
            popup.insert(detachItem, 0);
        }

        this.layoutManager = layoutManager;
    }

    /*
     *
     */
    public void actionPerformed(ActionEvent e) {
        if (e.getSource().equals(detachItem) && (layoutManager != null)) {
            if (detached) {
                layoutManager.attach(QTMediaPlayer.this.getVisualComponent());
                detachItem.setText(ElanLocale.getString("Detachable.detach"));
                detached = false;
            } else {
                layoutManager.detach(QTMediaPlayer.this.getVisualComponent());
                detachItem.setText(ElanLocale.getString("Detachable.attach"));
                detached = true;
            }
        } else if (e.getSource() == infoItem) {
            new FormattedMessageDlg(this);
        } else if (e.getSource() == saveItem) {
            ImageExporter export = new ImageExporter();
            export.exportImage(getCurrentFrameImage());
        }
    }

    /**
     * DOCUMENT ME!
     */
    public void updateLocale() {
        if (infoItem != null) {
            infoItem.setText(ElanLocale.getString("Player.Info"));
        }

        if (durationItem != null) {
            durationItem.setText(ElanLocale.getString("Player.duration") +
                ":  " + TimeFormatter.toString(getMediaDuration()));
        }

        if (saveItem != null) {
            saveItem.setText(ElanLocale.getString("Player.SaveFrame"));
        }

        if (detachItem != null) {
            if (detached) {
                detachItem.setText(ElanLocale.getString("Detachable.attach"));
            } else {
                detachItem.setText(ElanLocale.getString("Detachable.detach"));
            }
        }
    }

    public void cleanUpOnClose() {
        // TODO Auto-generated method stub
    }

    private class VisualComponent extends Panel implements ComponentListener,
        HierarchyListener {
        boolean doNotify;

        public VisualComponent(Component component) {
            doNotify = true;
            add(component);
            addComponentListener(this);
            addHierarchyListener(this);
        }

        public void componentResized(ComponentEvent e) {
            System.out.println("resized");
        }

        public void hierarchyChanged(HierarchyEvent e) {
            System.out.println("hier");
        }

        public void addNotify() {
            System.out.println("addNotify");

            if (doNotify) {
                super.addNotify();
                doNotify = false;
            }

            /*            try {
                            super.addNotify();
                            add(canvas.asComponent());
                //            quicktime.qd.NativeGraphics.getContext(canvas).unlock();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }*/
        }

        public void removeNotify() {
            System.out.println("removeNotify");
            super.removeNotify();

            /*            try {
            //                quicktime.qd.NativeGraphics.getContext(canvas).unlock();
                            remove(canvas.asComponent());
                            super.removeNotify();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }*/
        }

        public void componentShown(ComponentEvent e) {
            System.out.println("show");
        }

        public void componentHidden(ComponentEvent e) {
            System.out.println("hide");
        }

        public void componentMoved(ComponentEvent e) {
            System.out.println("move");
        }

        protected void finalize() throws Throwable {
            System.out.println("finalize visual component");
            super.finalize();
        }
    }

    /**
     * Private class that extends a QTCanvas and keeps the mouse events away
     * from QT otherwise the media rendering will be stopped by a mouse click
     * in the visual component panel
     *
     * @param layoutManager DOCUMENT ME!
     */
    private class QTCanvasNoMouse extends QTCanvas {
        public void addMouseListener(java.awt.event.MouseListener ml) {
            if (ml instanceof MouseHandler) {
                super.addMouseListener(ml);
            }
        }
    }

    /**
     * DOCUMENT ME!
     * $Id: QTMediaPlayer.java,v 1.6 2006/03/30 14:59:42 hasloe Exp $
     * @author $Author: hasloe $
     * @version $Revision: 1.6 $
     */
    private class MouseHandler extends MouseAdapter {
        /**
         * DOCUMENT ME!
         *
         * @param e DOCUMENT ME!
         */
        public void mousePressed(MouseEvent e) {
            if (SwingUtilities.isRightMouseButton(e) || e.isPopupTrigger()) {
                JPopupMenu.setDefaultLightWeightPopupEnabled(false);
                popup.show(getVisualComponent(), e.getPoint().x, e.getPoint().y);
            }
        }

        public void mouseClicked(MouseEvent e) {
            if (e.getClickCount() >= 2) {
                if (layoutManager != null) {
                    layoutManager.setFirstPlayer(QTMediaPlayer.this);
                }

                return;
            }

            if (SwingUtilities.isRightMouseButton(e) || e.isPopupTrigger()) {
                JPopupMenu.setDefaultLightWeightPopupEnabled(false);
                popup.show(getVisualComponent(), e.getPoint().x, e.getPoint().y);
            }
        }
    }

    /**
     * This class implements a method that is called when the movie stops
     */
    private class TimeBaseExtremesCallBack
        extends quicktime.std.clocks.ExtremesCallBack {
        /**
         * Super class constructor
         *
         * @param tb DOCUMENT ME!
         * @param flag DOCUMENT ME!
         *
         * @throws QTException DOCUMENT ME!
         */
        public TimeBaseExtremesCallBack(TimeBase tb, int flag)
            throws QTException {
            super(tb, flag);
        }

        /**
         * Make sure all the connected controllers are stopped at end of media
         */
        public void execute() {
            //System.out.println("extremes callback");
            try {
                movie.stop();
                stopControllers();
                setMediaTime(exactStopTime); // de troep stopt vaak enkele milli seconden te vroeg.

                if (stopMode == STOP_WITH_STOP_TIME) {
                    setStopTime(movie.getDuration());
                } else {
                    movie.setPreviewMode(false);
                }

                exactStopTime = movie.getDuration();
                endOfMediaCallback.callMeWhen();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
