/**
 *  Copyright © 2025, Luis Andrés Lange <https://javacomm.net>
 *
 *  This Source Code Form is subject to the terms of the Mozilla Public
 *  License, v. 2.0. If a copy of the MPL was not distributed with this
 *  file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 *  ----------------------------------------------------------------------------
 *
 *  Exhibit B - "Incompatible With Secondary Licenses" Notice
 *
 *  This Source Code Form is "Incompatible With Secondary Licenses",
 *  as defined by the Mozilla Public License, v. 2.0.
 *
 *  In short:
 *  - This file may be used, modified, and distributed under MPL 2.0 only.
 *  - It may NOT be relicensed under GPL, LGPL, AGPL, or any other Secondary License.
 *
 *  Rationale:
 *  - Ensures that the code remains MPL-2.0.
 *  - Avoids legal conflicts with GPL-licensed libraries (e.g., VideoLAN).
 *  - Maximizes usability for commercial and security-critical applications.
 *
 */
package net.javacomm.client.mediaplayer;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyVetoException;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Hashtable;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;
import javax.swing.event.MouseInputAdapter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.javacomm.client.config.schema.ISO6391;
import net.javacomm.client.config.schema.Sprache;
import net.javacomm.client.environment.Environment;
import net.javacomm.client.gui.JSliderVolumePanel;
import net.javacomm.client.gui.Videoslider;
import net.javacomm.client.resource.Resource;
import net.javacomm.multilingual.MultilingualButton;
import net.javacomm.multilingual.MultilingualCheckBoxMenuItem;
import net.javacomm.multilingual.MultilingualMenu;
import net.javacomm.multilingual.MultilingualMenuItem;
import net.javacomm.multilingual.schema.ISO639;
import net.javacomm.multilingual.schema.KEY;
import net.javacomm.window.manager.Control;
import net.javacomm.window.manager.Frames;
import net.javacomm.window.manager.WM;
import net.javacomm.window.manager.WindowAnrufenEvent;
import net.javacomm.window.manager.WindowAnrufenListener;
import net.javacomm.window.manager.WindowManagerInternalFrame;
import uk.co.caprica.vlcj.factory.MediaPlayerFactory;
import uk.co.caprica.vlcj.media.Media;
import uk.co.caprica.vlcj.media.MediaEventAdapter;
import uk.co.caprica.vlcj.media.MediaParsedStatus;
import uk.co.caprica.vlcj.player.base.AudioApi;
import uk.co.caprica.vlcj.player.base.ControlsApi;
import uk.co.caprica.vlcj.player.base.EventApi;
import uk.co.caprica.vlcj.player.base.LogoApi;
import uk.co.caprica.vlcj.player.base.LogoPosition;
import uk.co.caprica.vlcj.player.base.MediaApi;
import uk.co.caprica.vlcj.player.base.MediaPlayer;
import uk.co.caprica.vlcj.player.base.SnapshotApi;
import uk.co.caprica.vlcj.player.base.StatusApi;
import uk.co.caprica.vlcj.player.base.TitleApi;
import uk.co.caprica.vlcj.player.base.TitleDescription;
import uk.co.caprica.vlcj.player.base.TrackDescription;
import uk.co.caprica.vlcj.player.base.VideoApi;
import uk.co.caprica.vlcj.player.component.AudioPlayerComponent;
import uk.co.caprica.vlcj.player.component.CallbackMediaPlayerComponent;
import uk.co.caprica.vlcj.player.component.callback.FilledCallbackImagePainter;
import uk.co.caprica.vlcj.player.embedded.EmbeddedMediaPlayer;



/**
 * 
 * Das MP3-Model informiert den InternalFrame.
 * 
 * Player-Model-Internalframe
 * 
 * (InternalFrame registriert sich beim Model) Der Player informiert das Model
 * 
 * InternalFrame startet den Player
 * 
 * @author llange
 * @param <CenterPane>
 *
 */
public class JPlayerInternalFrame extends WindowManagerInternalFrame implements WindowAnrufenListener {

  public final static int SCREEN_WIDTH = 951;
  public final static int SCREEN_HEIGHT = 600;
  public final static int TOP_BAR = 231;

  private static final long serialVersionUID = -3144140963869727036L;
  private final static int max = 300;
  private Logger log = LogManager.getLogger(JPlayerInternalFrame.class);
  private InternalFrameAction internalFrameAction = new InternalFrameAction();
  private JPanel mainPanel = new JPanel();
  private GridBagLayout gridbag = new GridBagLayout();
  private GridBagConstraints con = new GridBagConstraints();
  private ImageIcon image;
  private PropertyChangeSupport changes = new PropertyChangeSupport(this);
  private String record;
  private JLabel labeltime = new JLabel();
  private JLabel labelmaxtime;
  private JLabel labelhalftime;
  private JLabel labelmin;
  private Knopfleiste knopfleiste = new Knopfleiste();
  private JSliderVolumePanel volumePanel = new JSliderVolumePanel(JSliderVolumePanel.NORMAL);
  private JButtonMute buttonMute = new JButtonMute();
  private Videoslider videoslider = new Videoslider(0, max, 0);
  private Hashtable<Integer, JLabel> sliderlabels = new Hashtable<>();
  private float duration;
  private Path mediafile;
  private boolean mouseDragged = false;
  private AudioPlayerComponent audioplayerComponent;
  private AudioAdapter audioAdapter;
  private VideoAdapter videoAdapter;
  private ControlsApi controlsApi;
  private StatusApi statusApi;
  private JComponent videosurface;
  private JSplitPane splitpaneHorizontal = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
  private JSplitPane splitpaneVertical = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
  private JScrollPane scrollvideopane = new JScrollPane();
  private MultilingualButton buttonAuswerfen = new MultilingualButton(KEY.BUTTON_AUSWERFEN);
  private FilledCallbackImagePainter imagePainter;
  private JScrollPane scrollmainpanel = new JScrollPane();
  private PLAYERMODE playertype;
  private Shufflemode shufflemode = Shufflemode.WINDOW_CLOSE;
  private CountDownLatch durationLatch;
  private EmbeddedMediaPlayer embeddedVideoplayer;
  private MediaPlayerFactory thumbFactory;
  private MediaPlayer thumbPlayer;
  private MediaPlayer mediaplayer;
  private ThumbAdapter thumbAdapter;
  private ControlsApi thumbControlapi;
  private LogoApi logoApi;
  private MouseAdapter videosurfaceListener;
  private JPopupMenu popup;
  private MultilingualMenuItem itemLauter = new MultilingualMenuItem(KEY.STRING_LAUTER);
  private MultilingualMenuItem itemLeiser = new MultilingualMenuItem(KEY.STRING_LEISER);
  private MultilingualMenuItem itemPopupTitel = new MultilingualMenuItem(KEY.TITEL);
  private MultilingualMenuItem itemPopupMediapause = new MultilingualMenuItem(KEY.STRING_PAUSE_FORTSETZEN);
  private MultilingualMenuItem itemPopupMediastart = new MultilingualMenuItem(KEY.STRING_AN_DEN_ANFANG);
  private MultilingualMenuItem itemPopupVorspulen = new MultilingualMenuItem(KEY.STRING_FORWARD);
  private MultilingualMenuItem itemPopupRueckspulen = new MultilingualMenuItem(KEY.STRING_BACKWARD);
  private MultilingualMenu menuSchrittweite = new MultilingualMenu(KEY.STRING_ZEITSPANNE);

  private MultilingualCheckBoxMenuItem itemVollbildmodus = new MultilingualCheckBoxMenuItem(
      KEY.VOLLBILDMODUS
  );
  private MultilingualMenuItem itemNextTitel = new MultilingualMenuItem(KEY.STRING_NAECHSTER_TITEL);

  private JCheckBoxMenuItem dieciocho = new JCheckBoxMenuItem("18 Sekunden");
  private JCheckBoxMenuItem treinta = new JCheckBoxMenuItem("30 Sekunden");
  private JCheckBoxMenuItem seiscenta = new JCheckBoxMenuItem("60 Sekunden");
  private JCheckBoxMenuItem cientoochenta = new JCheckBoxMenuItem("180 Sekunden");
  private JCheckBoxMenuItem diez = new JCheckBoxMenuItem("10 Minuten");
  private JCheckBoxMenuItem veinte = new JCheckBoxMenuItem("20 Minuten");
  private ButtonGroup group = new ButtonGroup();
  private Hashtable<JCheckBoxMenuItem, Long> zeit = new Hashtable<>();
  private long skip;
  private boolean isFullsize;
  private Class<? extends JPlayerInternalFrame> resource;
  private Hashtable<String, Integer> audiolanguage = new Hashtable<>();
  private TitleApi titleApi;
  private MouseAction mouseAction;
  private int masterVolume;
  private EmbeddedMediaPlayer embedded;
  private MediaPlayerFactory mediaPlayerFactory;
  private JLabel corner;
  private EventApi thumbEvents;
  private ComponentAdapter componentAdapter = new ComponentAdapter() {

    @Override
    public void componentResized(ComponentEvent e) {
      setHorizontalDivider(1d);
    }

  };
  private JLabel imagelabel;
  private ImageIcon remoteImage;
  private Cursor dukeCursor;
  private MouseAdapter imagelabelAdpater;

  /**
   * Nur für Testzwecke
   */
  JPlayerInternalFrame() {}



  /**
   * Eine Mediadatei wird gelesen. Die physische Mediadatei ist immer eine Hülle,
   * welche dem vorgegebenen Dateityp nicht entsprechen muss. Der Mediadateityp
   * ist MP3, MPEG, MPG oder MP4.
   * 
   * @param file
   *             ein vollständiger Dateipfad auf eine Mediadatei
   *
   * @throws MediaException
   *                        ist keine normale Datei
   */
  JPlayerInternalFrame(Path file) throws MediaException {
    this(file, JSliderVolumePanel.NORMAL);
  }



  JPlayerInternalFrame(Path file, int volume) throws MediaException {
    this(file, volume, PLAYERMODE.SINGLE, false);
  }



  /**
   * 
   * Eine Mediadatei wird gelesen. Die physische Mediadatei ist immer eine Hülle,
   * welche dem vorgegebenen Dateityp nicht entsprechen muss. Der Mediadateityp
   * ist MP3, MPEG, MPG oder MP4.
   * 
   * 
   * @param file
   *                     ein vollständiger Dateipfad auf eine Mediadatei
   * @param volume
   *                     die Lautstärke im Bereich 0-100
   * @param type
   *                     ein einzelnes Lied, Shuffle oder DVD
   * @param fullsizemode
   *                     {@code true}, der Player wird im Vorhang auf Modus
   *                     geöffnet
   * 
   * @throws MediaException
   *                        ist keine normale Datei
   */
  public JPlayerInternalFrame(Path file, int volume, PLAYERMODE type, boolean fullsizemode)
      throws MediaException {
    resource = getClass();
    isFullsize = fullsizemode;

    if (isFullsize) {
      itemVollbildmodus.setSelected(true);
    }
    else {
      itemVollbildmodus.setSelected(false);
    }

    playertype = type;
    mediafile = file;
    if (mediafile == null) throw new IllegalArgumentException("/path/to/file/is/null"); // ändern auf
                                                                                        // IllegalArgumentException
                                                                                        // error
    if (type != PLAYERMODE.DVD) {
      record = mediafile.getFileName().toString();
      if (!Files.isRegularFile(mediafile))
        throw new MediaException(mediafile.toAbsolutePath().toString() + " ist keine normale Datei.");
    }
    else {
      record = PlayerFilter.getDvdName(mediafile);
    }
    masterVolume = Math.max(Math.min(JSliderVolumePanel.max, volume), 0);
    volumePanel.setLoudness(masterVolume);

    // in eine eigene Methode packen
    if (PlayerFilter.isSupportedAudioformat(mediafile)) {
      audioplayerComponent = new AudioPlayerComponent();
      controlsApi = audioplayerComponent.mediaPlayer().controls();
      statusApi = audioplayerComponent.mediaPlayer().status();
      setDuration(audioDuration(audioplayerComponent, mediafile));
      popup(type);
    }
    else if (PlayerFilter.isSupportedVideoformat(mediafile)) {
      popup(type);

      // nur für Windows implizit von der API
    }
    else {
      throw new MediaException(
          mediafile.toAbsolutePath().toString() + " - das Medienformat wird nicht unterstützt"
      );
    }
    setTitle(record);
    addComponentListener(componentAdapter);
    remoteImage = new ImageIcon(resource.getResource(Resource.REMOTECURSOR));
    Toolkit toolkit = Toolkit.getDefaultToolkit();
    dukeCursor = toolkit.createCustomCursor(remoteImage.getImage(), new Point(0, 0), "fernbedienung");
    init();
  }



  private void popup(PLAYERMODE type) {

    popup = new JPopupMenu();
    popup.setCursor(dukeCursor);

    // pressbutton
    itemPopupMediapause.setIcon(new ImageIcon(resource.getResource(Resource.POPUP_MEDIAPAUSE)));
    itemPopupMediapause.addActionListener((event) -> {

      if (!knopfleiste.isPressedPaused()) {
        knopfleiste.pressedStartbutton();
      }
      else {
        knopfleiste.pressedPausebutton();
      }

    });
    itemPopupTitel.setIcon(new ImageIcon(resource.getResource(Resource.PACKAGE_MULTIMEDIA)));
    itemPopupTitel.addActionListener(event -> {
      changes.firePropertyChange(getName(), Control.NULL, Control.TITEL_ABSPIELEN);
    });
    popup.add(itemPopupTitel);
    popup.addSeparator();
    popup.add(itemPopupMediapause);
    popup.addSeparator();
    itemPopupMediastart.setIcon(new ImageIcon(resource.getResource(Resource.POPUP_MEDIASTART)));
    itemPopupMediastart.addActionListener(event -> {
      rewind();
    });
    popup.add(itemPopupMediastart);

    dieciocho.addActionListener((event) -> {
      skip = zeit.get(dieciocho);
    });
    treinta.addActionListener((event) -> {
      skip = zeit.get(treinta);
    });
    seiscenta.addActionListener((event) -> {
      skip = zeit.get(seiscenta);
    });
    cientoochenta.addActionListener((event) -> {
      skip = zeit.get(cientoochenta);
    });
    diez.addActionListener((event) -> {
      skip = zeit.get(diez);
    });
    veinte.addActionListener((event) -> {
      skip = zeit.get(veinte);
    });
    dieciocho.addActionListener((event) -> {
      skip = zeit.get(dieciocho);
    });

    group.add(dieciocho);
    group.add(treinta);
    group.add(seiscenta);
    group.add(cientoochenta);
    group.add(diez);
    group.add(veinte);

    zeit.put(dieciocho, 18000L);
    zeit.put(treinta, 30000L);
    zeit.put(cientoochenta, 180000L);
    zeit.put(seiscenta, 60000L);
    zeit.put(diez, 600000L);
    zeit.put(veinte, 1200000L);

    treinta.doClick(); // muss nach zeit stehen

    // Vorspulen über das POPUP-Menu
    itemPopupVorspulen.setIcon(new ImageIcon(resource.getResource(Resource.POPUP_FORWARD)));
    itemPopupVorspulen.addActionListener(event -> {
      controlsApi.skipTime(skip);
      long time = statusApi.time();
      if (time == -1) return;
      dragging(time);
    });
    popup.add(itemPopupVorspulen);

    itemPopupRueckspulen.setIcon(new ImageIcon(resource.getResource(Resource.POPUP_BACKWARD)));
    itemPopupRueckspulen.addActionListener(event -> {
      // TODO Rückspulen
      controlsApi.skipTime(-skip);
      long time = statusApi.time();
      if (time == -1) {
        knopfleiste.pressedStartbutton();
        time = (long) getDuration() * 1000 - skip;
        controlsApi.setTime(time);
      }
      dragging(time);
    });
    popup.add(itemPopupRueckspulen);

    menuSchrittweite.add(dieciocho);
    menuSchrittweite.add(treinta);
    menuSchrittweite.add(seiscenta);
    menuSchrittweite.add(cientoochenta);
    menuSchrittweite.add(diez);
    menuSchrittweite.add(veinte);
    menuSchrittweite.setIcon(new ImageIcon(resource.getResource(Resource.POPUP_SCHRITTWEITE)));
    popup.add(menuSchrittweite);

    popup.addSeparator();

    popup.add(itemVollbildmodus);
    itemVollbildmodus.setIcon(new ImageIcon(resource.getResource(Resource.VORHANG)));
    itemVollbildmodus.addActionListener((event) -> {
      changes.firePropertyChange(JPlayerInternalFrame.class.getName(), Control.NULL, Control.VOLLBILDMODUS);
    });

    popup.addSeparator();

    popup.add(itemLauter);
    itemLauter.setIcon(new ImageIcon(resource.getResource(Resource.LAUTER_32x26)));
    itemLauter.addActionListener((event) -> {
      volumePanel.setLoudness(volumePanel.getLoudness() + 15);
      setAudioVolume(volumePanel.getLoudness());
    });

    popup.add(itemLeiser);
    itemLeiser.addActionListener((event) -> {
      volumePanel.setLoudness(volumePanel.getLoudness() - 15);
      setAudioVolume(volumePanel.getLoudness());
    });
    itemLeiser.setIcon(new ImageIcon(resource.getResource(Resource.LEISER_32x26)));

    if (type == PLAYERMODE.SHUFFLE) {
      popup.addSeparator();
      popup.add(itemNextTitel);
      itemNextTitel.setIcon(new ImageIcon(resource.getResource(Resource.NEXT_TITLE)));
      itemNextTitel.addActionListener((event) -> {
        buttonAuswerfen.doClick();
      });
    }

  }



  /**
   * Der vertikale Fensterteiler wird nach ganz oben geschoben und der horizontale
   * Fensterteiler nach ganz rechts.
   */
  public void fensterteilerMaximieren() {
    setHorizontalDivider(1.0);
    setVerticalDivider(0.0);
  }



  /**
   * Die Trennleiste wird in horizontaler Richtung positioniert. Die Angabe
   * erfolgt in Prozent. 0-Prozent ist ganz links und 100-Prozent ist ganz rechts.
   * 
   * @param prozent
   *                liegt im Intervall [0..1]
   */
  public void setHorizontalDivider(double prozent) {
    if (0 > prozent || prozent > 1) {
      log.warn(prozent + " - die Prozentangabe liegt nicht im Intervall [0..1]");
    }
    splitpaneHorizontal.setDividerLocation(prozent);
  }



  /**
   * Die Trennleiste wird in vertikaler Richtung positioniert. Die Angabe erfolgt
   * in Prozent. 0-Prozent ist ganz oben und 100-Prozent ist ganz unten.
   * 
   * @param prozent
   *                liegt im Intervall [0..1]
   */
  public void setVerticalDivider(double prozent) {
    if (0 > prozent || prozent > 1) {
      log.warn(prozent + " - die Prozentangabe liegt nicht im Intervall [0..1]");
    }
    splitpaneVertical.setDividerLocation(prozent);
  }



  /**
   * Die Trennleiste wird in horizontaler Richtung positioniert. Die Angabe
   * erfolgt in Pixel. 0 ist ganz links und {@code SCREEN_WIDTH} ist ganz rechts.
   * 
   * 
   * @param pixel
   *              die X-Position
   */
  public void setHorizontalDivider(int pixel) {
    splitpaneHorizontal.setDividerLocation(pixel);
  }



  /**
   * Die Trennleiste wird in vertikaler Richtung positioniert. Die Angabe erfolgt
   * in Pixel. 0 ist ganz oben.
   * 
   * @param pixel
   */
  public void setVerticalDivider(int pixel) {
    splitpaneVertical.setDividerLocation(pixel);
  }



  @Override
  public void activated() {
    try {
      setSelected(true);
      windowManager.setActivNode(record);
    }
    catch (PropertyVetoException e) {}
  }

  //
  // static String[] EMBEDDED_MEDIA_PLAYER_ARGS = {
  // "--video-title=vlcj video output",
  // "--no-snapshot-preview",
  // "--quiet",
  // "--intf=dummy",
  // "--no-osd", "--audio-language=de", "--menu-language=de",
  // "--menu-language=de",
  // "--dvd=D:/#1:6"



  /**
   * 
   * PlayerListener Internalframe
   * 
   * @param l
   */
  public synchronized void addPlayerListener(PropertyChangeListener l) {
    changes.addPropertyChangeListener(l);
  }



  /**
   * Die Länge eines Liedes in Sekunden.
   * 
   * 
   * @param path
   *             ein Lied im ADTS-Format
   * @return die Dauer des Liedes in Sekunden
   */
  private float audioDuration(AudioPlayerComponent audioplayerComponent, Path path) {
    if (path == null) throw new IllegalArgumentException("path is null");
    if (audioplayerComponent == null) throw new IllegalArgumentException("audioplayer is null");

    final float durationInSeconds;
    durationLatch = new CountDownLatch(1);

    mediaplayer = audioplayerComponent.mediaPlayer();
    MediaApi media = mediaplayer.media();
    media.prepare(path.toAbsolutePath().toString());

    DurationAdapter durationAdapter = new DurationAdapter();
    mediaplayer.events().addMediaEventListener(durationAdapter);

    media.parsing().parse(); // asynchron

    try {
      // Warte, bis er mit dem Parsen fertig ist
      durationLatch.await();
    }
    catch (InterruptedException e) {
      log.error(e.getMessage(), e.getCause());
    }
    durationInSeconds = durationAdapter.getDurationInSeconds();
    mediaplayer.events().removeMediaEventListener(durationAdapter);
    if (durationAdapter.getStatus() == MediaParsedStatus.FAILED) {
      JOptionPane.showMessageDialog(
          JPlayerInternalFrame.this,
          "<html>" + "<head/>"
              + "<body>"
              + "Die Datei ist möglicherweise korrupt und kann nicht abgespielt werden.<br><br>"
              + "<b>"
              + "<span style=\"color:blue\">"
              + path.toAbsolutePath().toString()
              + "</b>"
              + "</body>"
              + "</html>",
          "Video abspielen", JOptionPane.ERROR_MESSAGE
      );
    }
    return durationInSeconds;
  }



  /**
   * Alle Audioplayer Resourcen vom VLC Player werden freigegeben.
   * 
   */
  public void audioplayerRelease() {
    if (audioplayerComponent == null) return;
    AudioApi audioAPI = audioplayerComponent.mediaPlayer().audio();
    // Invalid Memory Access java.lang.Error bekommen, setMute
    audioAPI.setMute(false);
    audioplayerComponent.mediaPlayer().events().removeMediaPlayerEventListener(audioAdapter);
    try {
      audioplayerComponent.release();
    }
    catch (Error e) {
      log.info(e.getMessage());
      log.info("error nicht beachten, audioplayer.release() wurde schon ausgef�hrt");
    }
  }



  /**
   * Ein Audioformat wird mit VideoLAN abgespielt.
   * 
   */
  public void audiostart() {
    knopfleiste.pressStart();
    mouseAction = new MouseAction();
    videoslider.addMouseMotionListener(mouseAction);
    videoslider.addMouseListener(mouseAction);
    videoslider.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "none");
    videoslider.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "none");
    videoslider.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "none");
    videoslider.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "none");

    audioAdapter = new AudioAdapter();
    audioplayerComponent.mediaPlayer().events().addMediaPlayerEventListener(audioAdapter);
    audioplayerComponent.mediaPlayer().media().play(mediafile.toAbsolutePath().toString());

  }



  /**
   * Erzeuge einen Player.
   * 
   * @param language
   *                 kann {@code null} sein oder hat die Syntax
   *                 --audio-language=Landescode wie de, es, fr
   * @return ein Player
   */
  public EmbeddedMediaPlayer createEmbeddedPlayer(ISO6391 language) {
    CallbackMediaPlayerComponent mediaplayerComponent;
    mediaPlayerFactory = null;
    if (language != null) {
      mediaPlayerFactory = new MediaPlayerFactory(
          "--audio-language=" + language.value(), "--menu-language=" + language.value(),
          "--video-title=vlcj video output", "--no-snapshot-preview", "--quiet", "--intf=dummy", "--no-osd",
          "--dvdnav-menu"
      );
    }
    mediaplayerComponent = new CallbackMediaPlayerComponent(
        mediaPlayerFactory, null, null, true, null, null, null, null
    );
    embedded = mediaplayerComponent.mediaPlayer();
    videosurface = mediaplayerComponent.videoSurfaceComponent();
    videosurfaceListener = new MouseAdapter() {

      @Override
      public void mousePressed(MouseEvent event) {
        if (event.isPopupTrigger()) {
          popup.show(videosurface, event.getX(), event.getY());
        }
      }



      @Override
      public void mouseReleased(MouseEvent event) {
        if (event.isPopupTrigger()) {
          popup.show(videosurface, event.getX(), event.getY());
        }
      }
    };
    videosurface.addMouseListener(videosurfaceListener);
    videosurface.addMouseWheelListener(event -> {
      JScrollBar verticalScrollBar = scrollvideopane.getVerticalScrollBar();
      int units = event.getUnitsToScroll();
      verticalScrollBar.setValue(verticalScrollBar.getValue() + units);
    });

    scrollvideopane.getViewport().add(videosurface);
    knopfleiste.pressStart();
    imagePainter = new FilledCallbackImagePainter();
    mediaplayerComponent.setImagePainter(imagePainter);
    return embedded;
  }



  void dragging(long newTime) {
    EventQueue.invokeLater(() -> {
      if (mouseDragged) return;
      long currentSeconds = newTime / 1000;
      labeltime.setText(format(currentSeconds));
      if (getDuration() > 0) {
        float value = max * currentSeconds / getDuration();
        videoslider.setValue((int) value);
        labelhalftime.setText(format((long) getDuration() / 2));
      }
    });

  }



  public void embeddedVideoplayerShutdown() {
    if (embeddedVideoplayer == null) return;

    AudioApi audioAPI = embeddedVideoplayer.audio();
    // Invalid MemoryAccess kann reproduziert werden
    try {
      audioAPI.setMute(false);
    }
    catch (Error e) {}
    embeddedVideoplayer.events().removeMediaPlayerEventListener(videoAdapter);
    try {
      embeddedVideoplayer.release();
      // TOOD videoplayerSurface auch noch freigeben
    }
    catch (Error e) {
      log.info(e.getMessage());
      log.info("error nicht beachten, embeddedVideoplayer.release() wurde schon ausgeführt");
    }

    if (thumbPlayer != null) {
      thumbEvents.removeMediaPlayerEventListener(thumbAdapter);
      try {
        thumbPlayer.release();
      }
      catch (Error e) {}
      thumbFactory.release();
    }
  }



  /**
   * Alle Titel werden in das DVD-Menu eingetragen. Vorherige Titel werden
   * gelöscht.
   * 
   */
  public List<TitleDescription> findeAlleDvdTitel() {
    if (playertype != PLAYERMODE.DVD) {
      // ist keine DVD
      throw new DvdException("Der Player ist nicht im Abspielmodus");
    }
    return titleApi.titleDescriptions();
  }



  /**
   * Die Länge eines Liedes.
   * 
   * @return die Dauer in Sekunden
   */
  public float getDuration() {
    return duration;
  }



  @Override
  public String getFrameTitleId() {
    return record;
  }



  @Override
  public Dimension getMinimumSize() {
    return new Dimension(591 + 24 + 24 + 24 + 1, 434 + 24);
  }



  @Override
  public Frames getType() {
    return Frames.PLAYER;
  }



  /**
   * Die aktuelle Lautstärke.
   * 
   * @return die Lautstärke im Bereich 0-100; 0=stumm; 100=maximale Lautstärke
   */
  public int getVolume() {
    if (audioplayerComponent != null) {
      return masterVolume;
    }
    else if (embeddedVideoplayer != null) {
      return masterVolume;
    }
    else {
      masterVolume = JSliderVolumePanel.NORMAL;
    }
    return masterVolume;
  }



  private void init() {
    setMaximizable(true);
    setResizable(true);
    setIconifiable(false);
    setClosable(true);
    setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

    URL url = resource.getResource(Resource.MP3PLAYER);
    image = new ImageIcon(url);
    setFrameIcon(image);

    setContentPane(splitpaneHorizontal);

    splitpaneHorizontal.setLeftComponent(splitpaneVertical);
    splitpaneHorizontal.setRightComponent(windowManager);

    scrollmainpanel.setMinimumSize(new Dimension(0, 0));
    scrollmainpanel.setViewportView(mainPanel);

    scrollmainpanel.getHorizontalScrollBar().setUnitIncrement(12);
    scrollmainpanel.getVerticalScrollBar().setUnitIncrement(12);

    splitpaneVertical.setTopComponent(scrollmainpanel);

    scrollvideopane.setMinimumSize(new Dimension(0, 0));
    scrollvideopane.getHorizontalScrollBar().setUnitIncrement(12);
    scrollvideopane.getVerticalScrollBar().setUnitIncrement(12);
    scrollvideopane.setCursor(dukeCursor);

    ImageIcon icon = new ImageIcon(resource.getResource(Resource.MULTIMEDIA_CHIP));
    imagelabel = new JLabel(icon);
    imagelabelAdpater = new MouseAdapter() {
      @Override
      public void mousePressed(MouseEvent event) {
        if (event.isPopupTrigger()) {
          popup.show(imagelabel, event.getX(), event.getY());
        }
      }



      @Override
      public void mouseReleased(MouseEvent event) {
        if (event.isPopupTrigger()) {
          popup.show(imagelabel, event.getX(), event.getY());
        }
      }
    };
    imagelabel.addMouseListener(imagelabelAdpater);

    imagelabel.setOpaque(true);
    scrollvideopane.setViewportView(imagelabel);
    scrollvideopane.setBorder(null); // Verursacher
    corner = new JLabel();
    // die richtige Frabe einblenden
    corner.setBackground(UIManager.getColor("nimbusSelectionBackground"));
    corner.setOpaque(true);
    scrollvideopane.setCorner(ScrollPaneConstants.LOWER_RIGHT_CORNER, corner); // ok!

    splitpaneVertical.setBottomComponent(scrollvideopane);
    splitpaneVertical.setOneTouchExpandable(true);
    splitpaneVertical.setContinuousLayout(true);
    splitpaneVertical.setMinimumSize(new Dimension(0, 0));

    splitpaneHorizontal.setDividerLocation(SCREEN_WIDTH - WindowManagerInternalFrame.SPLITWIDTH);
    splitpaneHorizontal.setOneTouchExpandable(true);
    splitpaneHorizontal.setContinuousLayout(true);
    mainPanel.setMinimumSize(new Dimension(270, 0));
    windowManager.setMinimumSize(new Dimension(0, 0));

    mainPanel.setLayout(gridbag);

    videoslider.setSnapToTicks(false);

    con.anchor = GridBagConstraints.CENTER;
    con.weightx = 1.0;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.gridheight = 1;
    con.gridwidth = 4;
    con.insets = new Insets(12, 6, 12, 6);
    con.gridx = 0;
    con.gridy = 0;
    con.weighty = 0.0;
    gridbag.setConstraints(volumePanel, con);

    mainPanel.add(volumePanel);
    volumePanel.addVolumeListener(new PropertyChangeListener() {
      @Override
      public void propertyChange(PropertyChangeEvent event) {
        if (buttonMute.isSpeakerOn()) return;
        Integer value = (Integer) event.getNewValue();
        setAudioVolume(value);
      }
    });

    con.weighty = 0.0;
    con.weightx = 1.0;
    con.insets = new Insets(0, 0, 12, 0);
    con.gridheight = 1;
    con.gridwidth = 1;
    con.gridx = 1;
    con.gridy = 1;
    con.anchor = GridBagConstraints.EAST;
    con.fill = GridBagConstraints.NONE;
    gridbag.setConstraints(knopfleiste, con);
    mainPanel.add(knopfleiste);

    con.fill = GridBagConstraints.NONE;
    con.anchor = GridBagConstraints.WEST;
    con.insets = new Insets(0, 24, 12, 0);
    con.gridwidth = 1;
    con.weighty = 0.0;
    con.weightx = 1.0;
    con.gridheight = 1;
    con.gridx = 2;
    con.gridy = 1;
    gridbag.setConstraints(buttonAuswerfen, con);
    mainPanel.add(buttonAuswerfen);

    // labeltime.setForeground(Resource.VW_BLUE_GREEN);
    labeltime.setPreferredSize(new Dimension(72, 16)); // eine Mindestgröße
                                                       // angeben wegen der
                                                       // Zeitausdehnung
                                                       // 10:04:64
    labeltime.setMinimumSize(labeltime.getPreferredSize());
    labeltime.setHorizontalAlignment(SwingConstants.RIGHT);

    con.insets = new Insets(0, 0, 0, 0);
    con.gridx = 0;
    con.gridy = 2;
    con.gridheight = 1;
    con.gridwidth = 1;
    con.weightx = 0.0;
    con.fill = GridBagConstraints.NONE;
    con.anchor = GridBagConstraints.CENTER;
    gridbag.setConstraints(labeltime, con);
    mainPanel.add(labeltime);

    labelmaxtime = new JLabel();
    labelmaxtime.setText(format((long) getDuration()));
    labelmaxtime.setPreferredSize(new Dimension(72, 16)); // eine Mindestgröße
                                                          // angeben wegen der
                                                          // Zeitausdehnung
                                                          // 10:04:64
    labelmaxtime.setMinimumSize(labelmaxtime.getPreferredSize());
    labelmaxtime.setHorizontalAlignment(SwingConstants.CENTER);

    labelhalftime = new JLabel();
    labelhalftime.setText(format((long) getDuration() / 2));
    labelhalftime.setPreferredSize(new Dimension(72, 16)); // eine Mindestgröße
                                                           // angeben wegen der
                                                           // Zeitausdehnung
                                                           // 10:04:64
    labelhalftime.setMinimumSize(labelhalftime.getPreferredSize());
    labelhalftime.setHorizontalAlignment(SwingConstants.CENTER);

    labelmin = new JLabel();
    labelmin.setText(format(0));
    labelmin.setPreferredSize(new Dimension(72, 16)); // eine Mindestgröße
                                                      // angeben wegen der
                                                      // Zeitausdehnung 10:04:64
    labelmin.setMinimumSize(labelmin.getPreferredSize());
    labelmin.setHorizontalAlignment(SwingConstants.CENTER);

    sliderlabels.put(0, labelmin);
    sliderlabels.put(max / 2, labelhalftime);
    sliderlabels.put(max, labelmaxtime);

    videoslider.setLabelTable(sliderlabels);
    videoslider.setPaintLabels(true);
    videoslider.setPaintTicks(true);
    videoslider.setMajorTickSpacing(max / 2);

    con.insets = new Insets(12, 0, 0, 0);
    con.anchor = GridBagConstraints.CENTER;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.gridheight = 1;
    con.gridx++;
    con.gridy = 2;
    con.gridwidth = 2;
    con.weightx = 1.0;
    gridbag.setConstraints(videoslider, con);
    mainPanel.add(videoslider);

    con.fill = GridBagConstraints.NONE;
    con.anchor = GridBagConstraints.CENTER;
    con.insets = new Insets(0, 0, 0, 6);
    con.gridwidth = 1;
    con.weighty = 0.0;
    con.weightx = 0.0;
    con.gridheight = 1;
    con.gridx = 3;
    con.gridy = 2;
    gridbag.setConstraints(buttonMute, con);
    mainPanel.add(buttonMute);

    buttonMute.addActionListener((event) -> {
      buttonMute.changeSpeakerOn();
      if (buttonMute.isSpeakerOn()) {
        // Stummschalten
        if (PlayerFilter.isSupportedAudioformat(mediafile)) {
          AudioApi audioAPI = audioplayerComponent.mediaPlayer().audio();
          audioAPI.setMute(true);
          return;
        }
        if (PlayerFilter.isSupportedVideoformat(mediafile)) {
          AudioApi audioAPI = embeddedVideoplayer.audio();
          audioAPI.setMute(true);
          return;
        }
      }
      else {
        if (PlayerFilter.isSupportedAudioformat(mediafile)) {
          AudioApi audioAPI = audioplayerComponent.mediaPlayer().audio();
          audioAPI.setMute(false);
          return;
        }
        if (PlayerFilter.isSupportedVideoformat(mediafile)) {
          AudioApi audioAPI = embeddedVideoplayer.audio();
          audioAPI.setMute(false);
          return;
        }
        // alter lautst�rkewert
        if (audioplayerComponent != null) {
          AudioApi audioAPI = audioplayerComponent.mediaPlayer().audio();
          log.info("volumePanel=" + volumePanel.getLoudness());
          audioAPI.setVolume(volumePanel.getLoudness());
        }
      }
    });

    switch(playertype) {
      case SINGLE:
        buttonAuswerfen.setIcon(new ImageIcon(resource.getResource(Resource.EJECT_32x32)));
        break;
      case SHUFFLE:
        buttonAuswerfen.setIcon(new ImageIcon(resource.getResource(Resource.NEXT_TITLE)));
//        buttonAuswerfen.setText("Nächster Titel");
//        buttonAuswerfen.setHorizontalTextPosition(SwingConstants.LEFT);
//        buttonAuswerfen.setIconTextGap(8);
//        buttonAuswerfen.setMnemonic(KeyEvent.VK_N);

        break;
      case DVD:
        buttonAuswerfen.setIcon(new ImageIcon(resource.getResource(Resource.EJECT_32x32)));
        break;
    }

    buttonAuswerfen.setOpaque(false);
    buttonAuswerfen.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent event) {
        if (playertype == PLAYERMODE.SHUFFLE) {
          shufflemode = Shufflemode.NEXT;
        }
        try {
          setClosed(true);
        }
        catch (PropertyVetoException e) {
          log.error(e.getMessage(), e.getCause());
        }
      }
    });

    knopfleiste.addButtonListener(new PropertyChangeListener() {

      @Override
      public void propertyChange(PropertyChangeEvent event) {

        Control control = (Control) event.getNewValue();
        switch(control) {
          case MEDIAPLAYER_START:
            controlsApi.play();
            break;
          case MEDIAPLAYER_PAUSE:
            controlsApi.setPause(true);
            break;
          case MEDIAPLAYER_REWIND:
            rewind();
            break;
          default:
            break;
        }
      }
    });

    windowManager.addWMListener(new PropertyChangeListener() {
      @Override
      public void propertyChange(PropertyChangeEvent event) {
        changes.firePropertyChange(event);
      }
    });
    addInternalFrameListener(internalFrameAction);
    setSize(SCREEN_WIDTH, SCREEN_HEIGHT + 60);
  }



  /**
   * An den Dateianfang zurückspulen.
   */
  private void rewind() {
    controlsApi.setPosition(0);
    dragging(0);
  }



  /**
   * Die Lautstärke wird am realen Lautsprecher eingestellt. Sie kann über den
   * Schieberegler oder das Popupmenu gesteuert werden.
   * 
   */
  private void setAudioVolume(int value) {
    if (audioplayerComponent != null) {
      AudioApi audioapi = audioplayerComponent.mediaPlayer().audio();
      masterVolume = value;
      audioapi.setVolume(masterVolume);
    }
    if (embeddedVideoplayer != null) {
      AudioApi audioapi = embeddedVideoplayer.audio();
      masterVolume = value;
      audioapi.setVolume(masterVolume);
    }
  }



  /**
   * Eine Hilfsmehode, welche die rechte untere Ecke in der Videoansicht einfärbt.
   * Nach einem L&F-Wechsel wurde die Farbe nicht geändert.
   * 
   * @param color
   *              eine Farbe
   */
  public void corner(Color color) {
    corner.setBackground(color);
  }



  @Override
  public void insertWM(WM value) {
    windowManager.addWindow(value);
  }



  private void logoDisable() {
    if (logoApi == null) return;
    try {
      logoApi.enable(false);
    }
    catch (Error e) {
      log.warn(e);
    }
  }



  /**
   * Der Close-Button wurde programmatisch geklickt.
   * 
   */
  public void mp3CloseButton() {
    try {
      // shuffle n�chste Lied
      setClosed(true);
    }
    catch (PropertyVetoException e) {
      log.warn(e.getMessage(), e.getCause());
    }
  }



  public synchronized void removeAllListener() {

    for (MouseListener ml : videoslider.getMouseListeners()) {
      videoslider.removeMouseListener(ml);
    }

    for (MouseMotionListener ml : videoslider.getMouseMotionListeners()) {
      videoslider.removeMouseMotionListener(ml);
    }

    for (ActionListener listener : itemLeiser.getActionListeners()) {
      itemLeiser.removeActionListener(listener);
    }
    for (ActionListener listener : itemLauter.getActionListeners()) {
      itemLauter.removeActionListener(listener);
    }
    for (ActionListener listener : itemVollbildmodus.getActionListeners()) {
      itemVollbildmodus.removeActionListener(listener);
    }

    imagelabel.removeMouseListener(imagelabelAdpater);
    removeComponentListener(componentAdapter);
    removeAllPlayerListener();
    if (videosurface != null) videosurface.removeMouseListener(videosurfaceListener);
    windowManager.removeAllListener();
  }



  public synchronized void removeAllPlayerListener() {
    for (PropertyChangeListener listener : changes.getPropertyChangeListeners()) {
      removePlayerListener(listener);
    }
  }



  public synchronized void removePlayerListener(PropertyChangeListener l) {
    changes.removePropertyChangeListener(l);
  }



  @Override
  public void removeWM(WM frame) {
    windowManager.removeWM(frame);
  }



  /**
   * Die Sprache wird während des Abspielens geändert.
   * 
   * @param language
   *                 die Filmsprache
   */
  public void setAudioLanguage(Sprache language) {
    // Wird überhaupt eine DVD abgespielt?
    if (!PlayerFilter.isSupportedDvd(mediafile)) return;

    audiolanguage.clear();
    embeddedVideoplayer.audio().trackDescriptions().forEach((TrackDescription trackdescription) -> {
      audiolanguage.put(trackdescription.description(), trackdescription.id());
    });
    int englishID = Integer.MAX_VALUE;
    int germanID = Integer.MAX_VALUE;
    for (String key : audiolanguage.keySet()) {
      if (key.contains(language.value())) {
        if (language.value().equals("English")) {
          englishID = Math.min(englishID, audiolanguage.get(key));
          log.info(englishID);
        }
        else if (language.value().equals("German")) {
          germanID = Math.min(germanID, audiolanguage.get(key));
        }
        else {
          embeddedVideoplayer.audio().setTrack(audiolanguage.get(key));
          break;
        }
      }
    }
    if (englishID < Integer.MAX_VALUE) {
      log.info(englishID);
      embeddedVideoplayer.audio().setTrack(englishID);
    }
    if (germanID < Integer.MAX_VALUE) {
      embeddedVideoplayer.audio().setTrack(germanID);
    }
  }



  /**
   * Die Länge eines Liedes.
   * 
   * @param value
   *              die Dauer in Sekunden
   */
  void setDuration(float value) {
    duration = value;
  }



  @Override
  public void setFrameTitleId(String id) {
//    wm.setID(id);
  }



  @Override
  public void setType(Frames value) {

  }



  public void shuffleNext() {
    buttonAuswerfen.doClick();
  }



  /**
   * Der DVD-Titel wird abgespielt.
   * 
   * @param title
   *              ein DVD-Titel
   */
  public void startDvdTitle(int title) {
    titleApi.setTitle(title);
  }



  /**
   * Die Knöpfe An den Anfang, Vorspulen und Rückspulen werden aktiviert oder
   * deaktiviert.
   * 
   * 
   * @param on
   *           {@code true}, die Knöpfe werden aktiviert
   */
  @Deprecated
  public void switchOnPlayermenu(boolean on) {
    itemPopupMediastart.setEnabled(on);
    itemPopupVorspulen.setEnabled(on);
    itemPopupRueckspulen.setEnabled(on);
  }



  /**
   * 
   * Die Länge eines Films in Sekunden.
   * 
   * @param embeddedPlayer
   *                       der Player, der den Film abspielt
   * @param path
   *                       ein Pfad mit Dateiname
   * @return die Dauer des Films in Sekunden
   * 
   * @throws MediaException
   *                        das Medium ist möglicherweise korrupt und kann nicht
   *                        abgespielt werden
   */
  float videoDuration(EmbeddedMediaPlayer embeddedPlayer, Path path) throws MediaException {
    if (path == null) throw new IllegalArgumentException("path is null");
    if (embeddedPlayer == null) throw new IllegalArgumentException("embeddedPlayer is null");
    final float durationInSeconds;
    durationLatch = new CountDownLatch(1);

    MediaApi media = embeddedPlayer.media();
    media.prepare(path.toAbsolutePath().toString());

    // Nur für die Dauer des Parsens wird ein Listener eingerichtet
    DurationAdapter durationAdapter = new DurationAdapter();
    embeddedPlayer.events().addMediaEventListener(durationAdapter);
    media.parsing().parse(); // asynchron
    try {
      // Warte, bis er mit dem Parsen fertig ist
      durationLatch.await();
    }
    catch (InterruptedException e) {
      log.error(e.getMessage(), e.getCause());
    }
    embeddedPlayer.events().removeMediaEventListener(durationAdapter);
    durationInSeconds = durationAdapter.getDurationInSeconds();
    if (durationAdapter.getStatus() == MediaParsedStatus.FAILED) {
      throw new MediaException(path.toAbsolutePath().toString());
    }
    return durationInSeconds;
  }



  /**
   * Die Filme werden in den Threads mediaEvents und mediaAdapter von libvcl4
   * abgespielt. Aus diesem Grund wird kein eigener Thread über das
   * Threadframework gestartet. libvcl unterscheidet sich in diesem Punkt von
   * xuggler und javazoom.
   * 
   * 
   * @param mode
   *                 SINGLE, DVD SHUFFLE
   * @param language
   *                 language kann {@code null} sein, wenn das Video keine DVD ist
   * @throws MediaException
   *                        die Videodatei ist korrupt
   */
  public void videostart(PLAYERMODE mode, ISO6391 language) throws MediaException {
    if (mode == PLAYERMODE.DVD) {
      embeddedVideoplayer = createEmbeddedPlayer(language);
      controlsApi = embeddedVideoplayer.controls();
      statusApi = embeddedVideoplayer.status();
      // DVD Titel
      setDuration(videoDuration(embeddedVideoplayer, mediafile));
      // embeddedPalyer.play darf immer erst nach setDuration aufgeruden werden
      embeddedVideoplayer.media().play("dvd:///" + mediafile.toAbsolutePath().toString() + "#1"
      // + "#3"
      // embeddedVideoplayer.media().play("dvd:///d:/#1:6");
      );
    }
    else {
      embeddedVideoplayer = createEmbeddedPlayer(null);
      controlsApi = embeddedVideoplayer.controls();
      statusApi = embeddedVideoplayer.status();
      setDuration(videoDuration(embeddedVideoplayer, mediafile));
      embeddedVideoplayer.media().play(mediafile.toAbsolutePath().toString());
    }
    // MouseAction wird erst registriert, wenn es zuvor keine MediaException
    // gab.
    // Bei einer korrupten Datei käme es in der Spielliste anschließend zu einer
    // NullPointerException
    // in mouseMoved, weil thumbPlayer null ist
    mouseAction = new MouseAction();
    videoslider.addMouseMotionListener(mouseAction);
    videoslider.addMouseListener(mouseAction);
    videoslider.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "none");
    videoslider.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "none");
    videoslider.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "none");
    videoslider.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "none");

    try {
      videoAdapter = new VideoAdapter();
      embeddedVideoplayer.events().addMediaPlayerEventListener(videoAdapter);
      // bei einer DVD kein thumbPlayer!!!!
      if (playertype == PLAYERMODE.DVD) {
        //
        // offener Punkt bei einer DVD Vorspulen und Rückspulen
      }
      else {
        thumbFactory = new MediaPlayerFactory(
            "--intf=dummy", "--no-audio", "--vout", "dummy", "--no-snapshot-preview", "--quiet"
        );
        thumbPlayer = thumbFactory.mediaPlayers().newMediaPlayer();
        thumbAdapter = new ThumbAdapter();
        thumbEvents = thumbPlayer.events();
        thumbControlapi = thumbPlayer.controls();
        thumbPlayer.media().play(mediafile.toAbsolutePath().toString());
        thumbControlapi.stop();
      }
    }
    catch (IllegalStateException e) {
      log.error(e.getMessage());
    }

  }

  /******************************************************************/
  /*                                                                */
  /* Class AudioAdapter */
  /*                                                                */
  /******************************************************************/

  private class AudioAdapter extends VLCMediaPlayerAdapter {

    @Override
    public void finished(MediaPlayer mediaPlayer) {
      EventQueue.invokeLater(() -> {
        videoslider.setValue(max);
        labeltime.setText(format((long) getDuration()));
        knopfleiste.pressPause();
        changes.firePropertyChange(getName(), Control.NULL, Control.SONG_FINISHED);
      });
    }



    @Override
    public void lengthChanged(MediaPlayer mediaPlayer, long newLength) {
      setDuration(newLength / 1000);
      EventQueue.invokeLater(() -> {
        labelmaxtime.setText(format((long) getDuration()));
      });
    }



    @Override
    public void opening(MediaPlayer mediaPlayer) {
      AudioApi audioapi = mediaPlayer.audio();
      EventQueue.invokeLater(() -> {
        try {
          masterVolume = volumePanel.getLoudness();
          audioapi.setVolume(masterVolume);
        }
        catch (Error error) {
          log.error(error.getMessage());
        }
      });
    }



    @Override
    public void timeChanged(MediaPlayer mediaPlayer, long newTime) {
      dragging(newTime);
    }



    @Override
    public void volumeChanged(MediaPlayer mediaPlayer, float volume) {
      // Der AudioAdapter läuft nicht im AWT-Thread, sondern im Mediaplayerthread
//      log.info(volume);
      EventQueue.invokeLater(() -> {
//        masterVolume = (int) (volume * 100);
//        log.info(masterVolume);
        changes.firePropertyChange(JPlayerInternalFrame.class.getName(), Control.NULL, Control.VOLUME);
      });
    }



    @Override
    public void mediaPlayerReady(MediaPlayer mediaPlayer) {
      // log.info("READY");

    }

  }

  /******************************************************************/
  /*                                                                */
  /* Class VideoDurationAdapter */
  /*                                                                */
  /******************************************************************/
  /**
   * Nur für die Ermittlung der Videodauer wird der Adapter benötigt.
   * 
   * @author lromero
   *
   */
  class DurationAdapter extends MediaEventAdapter {

    private long durationInSeconds = 0;
    private MediaParsedStatus status;

    long getDurationInSeconds() {
      return durationInSeconds;
    }



    MediaParsedStatus getStatus() {
      return status;
    }



    @Override
    public void mediaDurationChanged(Media media, long newDuration) {
      durationInSeconds = newDuration / 1000;
    }



    @Override
    public void mediaParsedChanged(Media media, MediaParsedStatus newStatus) {
      status = newStatus;
      durationLatch.countDown();
    }

  }

  /******************************************************************/
  /*                                                                */
  /* Class WindowAction */
  /*                                                                */
  /******************************************************************/

  class InternalFrameAction extends InternalFrameAdapter {

    @Override
    public void internalFrameActivated(InternalFrameEvent event) {
      changes.firePropertyChange(getName(), Control.NULL, Control.MEDIAPLAYER_FRAME_ACTIVATED);
    }



    @Override
    public void internalFrameClosing(InternalFrameEvent event) {
      embeddedVideoplayerShutdown();
      audioplayerRelease();
      if (playertype == PLAYERMODE.SINGLE) {
        changes.firePropertyChange(getName(), Control.NULL, Control.SINGLE_QUIT);
      }
      else if (playertype == PLAYERMODE.DVD) {
        changes.firePropertyChange(getName(), Control.NULL, Control.DVD_QUIT);
      }
      else {
        switch(shufflemode) {
          case NEXT:
            changes.firePropertyChange(getName(), Control.NULL, Control.SHUFFLE_CONTINUE);
            break;
          case WINDOW_CLOSE:
            changes.firePropertyChange(getName(), Control.NULL, Control.SHUFFLE_WINDOW_CLOSE);
            break;
        }
      }
    }

  }

  /**
   * Der Schieberegler für die Abspielposition wurde betätigt. Spiele das Lied an
   * einer anderen Stelle weiter ab.
   * 
   * 
   * @author llange
   *
   */
  private class MouseAction extends MouseInputAdapter {

    private int currentValue;

    @Override
    public void mouseDragged(MouseEvent event) {
      mouseDragged = true;
      long time = statusApi.time();
      if (time == -1) {
        // wird von der neuen Position angespielt, wenn das Lied bereits
        // abgespielt wurde
        knopfleiste.pressedStartbutton();
      }
      labeltime.setText(JPlayerInternalFrame.format((long) (videoslider.getValue() * getDuration() / max)));
    }



    @Override
    public void mouseEntered(MouseEvent event) {
      if (thumbEvents == null) return; // DVD
      thumbEvents.addMediaPlayerEventListener(thumbAdapter);
    }



    @Override
    public void mouseExited(MouseEvent event) {
      if (thumbEvents == null) return; // DVD
      thumbEvents.removeMediaPlayerEventListener(thumbAdapter);
      logoDisable();
      return;
    }



    @Override
    public void mouseMoved(MouseEvent event) {
      int position = videoslider.getTimerposition();
      if (!PlayerFilter.isSupportedVideoformat(mediafile)) return;
      if (position == Videoslider.OUTSIDE_TRACK) {
        logoDisable();
        return;
      }
      float result = position / (float) videoslider.getMaxTimevalue();
      try {
        if (playertype != PLAYERMODE.DVD) {
          if (thumbPlayer == null) {
            log.warn("thumbPlayer is null");
            return;
          }
          thumbPlayer.media().play(mediafile.toAbsolutePath().toString());
          thumbPlayer.controls().setPosition(result);
        }
      }
      catch (Error e) {
        log.warn(e);
      }
    }



    @Override
    public void mousePressed(MouseEvent event) {
      if (!mouseDragged) {
        // 1-Klick
        // ich nehme hier die Position entgegen, wegen einer Pause

        long time = statusApi.time();
        if (time == -1) {
          // wird von der neuen Position angespielt, wenn das Lied bereits
          // abgespielt wurde
          knopfleiste.pressedStartbutton();
        }
        currentValue = videoslider.getTimerposition();
        int result = videoslider.getMaximum() * currentValue / videoslider.getMaxTimevalue();
        // umrechnen von Timer nach value
        videoslider.setValue(result);
        labeltime.setText(JPlayerInternalFrame.format((long) (videoslider.getValue() * getDuration() / max)));
        int position = currentValue;
        float result2 = position / (float) videoslider.getMaxTimevalue();
        controlsApi.setPosition(result2);
      }
    }



    @Override
    public void mouseReleased(MouseEvent event) {
      if (mouseDragged) {
        // bei dragging muss videoslider.getValue verwendet werden

        int position = videoslider.getValue();
        float result = position / (float) videoslider.getMaximum();
        controlsApi.setPosition(result);
        mouseDragged = false;
      }
    }

  }

  /******************************************************************/
  /*                                                                */
  /* Class ThumbAdapater */
  /*                                                                */
  /******************************************************************/
  class ThumbAdapter extends VLCMediaPlayerAdapter {

    final static String PICTURE = "snapshot.png";

    @Override
    public void positionChanged(MediaPlayer mediaPlayer, float newPosition) {

      Runnable task = () -> {
        Path file = Paths
            .get(Environment.getInstance().getSnapshotdir().toAbsolutePath().toString(), PICTURE);
        try {
          Files.deleteIfExists(file);
          SnapshotApi snapshotapi = mediaPlayer.snapshots();
          snapshotapi.save(file.toFile(), 180, 100);
        }
        catch (Error e) {
          // Bug, wenn die Maus unmittelbar vor den Positionsanzeiger geschoben
          // wird
          log.warn(e.getMessage(), "snapshot");
        }
        catch (IOException e) {
          log.error(e.getMessage(), e.getCause());
        }
      };
      // mediaPlayer.submit(task); //Bug
      EventQueue.invokeLater(task);
    }



    @Override
    public void snapshotTaken(MediaPlayer mediaPlayer, String filename) {
      thumbControlapi.stop();
      EventQueue.invokeLater(() -> {
        // Bug, wenn die Maus unmittelbar vor den Positionsanzeiger geschoben
        // wird
        if (logoApi == null) return; // Bug ist tatsächlich aufgetreten
        try {
          logoApi.setOpacity(200);
          logoApi.setPosition(LogoPosition.TOP_LEFT);
          logoApi.setDuration(500);
          logoApi.setFile(filename);
          logoApi.setRepeat(-1);
          logoApi.enable(true);
        }
        catch (Error error) {
          log.warn(error.getMessage());
        }
      });
    }

  }

  /******************************************************************/
  /*                                                                */
  /* Class VideoAdapater */
  /*                                                                */
  /******************************************************************/

  private class VideoAdapter extends VLCMediaPlayerAdapter {

    // MediaPlayerEventListener

    @Override
    public void finished(MediaPlayer mediaPlayer) {
      EventQueue.invokeLater(() -> {
        videoslider.setValue(max);
        labeltime.setText(format((long) getDuration()));
        knopfleiste.pressPause();
        changes.firePropertyChange(getName(), Control.NULL, Control.SONG_FINISHED);
      });
    }



    @Override
    public void lengthChanged(MediaPlayer mediaPlayer, long newLength) {
      setDuration(newLength / 1000);
      EventQueue.invokeLater(() -> {
        labelmaxtime.setText(format((long) getDuration()));
      });
    }



    @Override
    public void mediaPlayerReady(MediaPlayer mediaPlayer) {
      // log.info("READY");
      titleApi = mediaPlayer.titles();
      logoApi = mediaPlayer.logo();
      videosurface.requestFocusInWindow();
      VideoApi videoApi = mediaPlayer.video();
      // videoApi könnte null sein
      try {
        videosurface.setSize(videoApi.videoDimension());
      }
      catch (NullPointerException e) {
        // Videoformat, das nur Audiodaten enthält
      }
      EventQueue.invokeLater(() -> {
        changes.firePropertyChange(JPlayerInternalFrame.class.getName(), playertype, Control.PLAYERMODE);
      });
    }



    @Override
    public void opening(MediaPlayer mediaPlayer) {
      AudioApi audioapi = mediaPlayer.audio();
      EventQueue.invokeLater(() -> {
        try {
          log.debug(volumePanel.getLoudness());
          audioapi.setVolume(volumePanel.getLoudness());
        }
        catch (Error error) {
          log.error(error.getMessage());
        }
      });
    }



    @Override
    public void timeChanged(MediaPlayer mediaPlayer, long newTime) {
      dragging(newTime);
    }



    @Override
    public void volumeChanged(MediaPlayer mediaPlayer, float volume) {
      EventQueue.invokeLater(() -> {
//        masterVolume = (int) (volume * 100);
//        log.info(masterVolume);
        changes.firePropertyChange(JPlayerInternalFrame.class.getName(), Control.NULL, Control.VOLUME);
      });
    }

  }

  /**
   * 
   * Ein Sekundenzeitwert wird formatiert.
   * 
   * @param value
   *              die Zeit in Sekunden
   * @return das Zeitformat ist H:MM:ss
   */
  public static String format(long value) {
    StringBuilder builder = new StringBuilder();

    if (value >= 36000) {
      throw new RuntimeException("Time out of range");
    }
    if (value >= 3600) {
      long stunden = value / 3600;
      builder.append(stunden);
      builder.append(":");
      long sekunden1 = value % 3600;
      long minuten = sekunden1 / 60;
      if (minuten >= 10) {
        // >=10 Minuten
        builder.append(minuten);
        long sekunden2 = sekunden1 % 60;

        if (sekunden2 >= 10) {
          // >=10 Sekunden
          builder.append(":");
          builder.append(sekunden2);
        }
        else {
          // >=0 Sekunden
          builder.append(":0");
          builder.append(sekunden2);
        }
      }
      else {
        builder.append("0");
        builder.append(minuten);
        long sekunden2 = sekunden1 % 60;

        if (sekunden2 >= 10) {
          // >=10 Sekunden
          builder.append(":");
          builder.append(sekunden2);
        }
        else {
          // >=0 Sekunden
          builder.append(":0");
          builder.append(sekunden2);
        }
      }
    }
    else if (value >= 600) {
      // >=10 Minuten
      long zehner = value / 60;
      builder.append(zehner);
      long sekunden = value % 60;
      if (sekunden >= 10) {
        // >=10 Sekunden
        builder.append(":");
        builder.append(sekunden);
      }
      else {
        // >=0 Sekunden
        builder.append(":0");
        builder.append(sekunden);
      }
    }
    else if (value >= 60) {
      // >=1 Minuten

      long einer = value / 60;
      // builder.append("0");
      builder.append(einer);
      builder.append(":");
      long rest = value % 60;
      if (rest >= 10) {
        builder.append(rest);
      }
      else {
        builder.append("0");
        builder.append(rest);
      }
    }
    else if (value >= 10) {
      // >=10 Sekunden
      builder.append("0:");
      builder.append(value);
    }
    else if (value >= 1) {
      // >=1 Sekunden
      builder.append("0:0");
      builder.append(value);
    }
    else {
      builder.append("0:00");
    }
    return builder.toString();
  }



  /**
   * Setze den Haken im Popup-Menu. Es wird kein Event ausgelöst.
   * 
   * @param value
   *              {@code true} und der Haken wird gesetzt, sonst {@code false}
   */
  public void setPopupIconVollbildmodus(boolean value) {
    itemVollbildmodus.setSelected(value);
  }



  @Override
  public void setLanguage(ISO639 code) {
    itemVollbildmodus.setLanguage(code);
    if (playertype == PLAYERMODE.SHUFFLE) {
      buttonAuswerfen.setText("");
    }
    else {
      buttonAuswerfen.setLanguage(code);
    }
    itemNextTitel.setLanguage(code);
    volumePanel.setLanguage(code);
    itemLauter.setLanguage(code);
    itemLeiser.setLanguage(code);
    itemPopupVorspulen.setLanguage(code);
    itemPopupRueckspulen.setLanguage(code);
    itemPopupMediastart.setLanguage(code);
    itemPopupMediapause.setLanguage(code);
    itemPopupTitel.setLanguage(code);
    menuSchrittweite.setLanguage(code);
    treeView(code);
  }



  @Override
  public void ringRing(WindowAnrufenEvent event) {
    extText = event.getNickname();
    getMultiAbnehmen().setLanguage(KEY.FRAME_ABNEHMEN, event.getCode(), extText);
    getMultiAnrufen().setLanguage(KEY.TELEFON_ANRUFEN, event.getCode(), extText);
  }



  @Override
  public void setBackgroundGradient(Color top, Color bottom) {
    windowManager.setBackgroundGradient(top, bottom);
  }

}
