# javafx seltsames Verhalten mediaplayer



## Joob (31. Jan 2019)

Ich zeichne eine Audioaufnahme auf.
Dannach spiele ich eine gespeicherte Aufnahme ab und im Anschluss die aufgenommene Audiodatei.

Die aufgenommene Datei wird nur zum Teil abgespielt, auch wird setOnEndOfMedia nicht ausgelöst. Die erste wird immer komplett abgespielt. Ich habe auch schon die Reihenfolge gewechselt dann stock es bei der ersten Datei. Also liegt das Problem bei der aufgenommenen Datei.

Nach mehrmaligen Abspielen wird dann immer mehr der Datei abgespielt bis irgendwann mal das Ende erreicht wird und auch setOnEndOfMedia erreicht wird.

Ich habe bereits getestet ob der Thread der die Aufnahme speichert noch läuft, das ist nicht der Fall. Wenn ich die Datei mit einem anderen Player über das Betriebssystem abspiele läuft diese komplett ab.

Zeit spielt auch keine Rolle, man muss mehrfach abspielen um die Datei komplett zu hören.

Was mach ich falsch, oder kennt jemand den Effekt.

Hier mein Code:



```
public void handletrainyourvoicedone () throws InterruptedException {
    
           

        Media teachervoice = new Media(new File(workingDir + fs + vokidtest + ".mp3").toURI().toString());


        Media yourvoice = new Media(new File(workingDir + fs + "U" + userid + ".wav").toURI().toString());
       
        Media[] medialst = new Media[2];
        medialst[0] = teachervoice;
        medialst[1] = yourvoice;

       
        SequentialMedia sqmed = new SequentialMedia(medialst);
        sqmed.play();
       

           
           
    }

private class SequentialMedia {
   
    LocalFileToolClass  lftc                = new LocalFileToolClass();
    TransformClass      trfc                = new TransformClass();
   
    private final Media[] medias;
    private int next;
    private MediaPlayer player;
   
   
    public SequentialMedia(Media... medias) {
      this.medias = medias;
    }
   
   
    private void playNext() {
      dispose();
        System.out.println("next : " + next);
      if (next >= medias.length){
          System.out.println("mache schluss");
        allDone();
        return;
      }
         
      player = new MediaPlayer(medias[next++]);
      player.setOnEndOfMedia(this::playNext);
      player.play();
        System.out.println("Dauer : " + player.getError());
    }
   
   
    public void play() {
      next = 0;
      playNext();
    }
   
   
    public void dispose() {
      if (player != null)
        player.dispose();
    }
   
    public void allDone() {
       
        // set points
        points = points + 3;
        trainyourvoicepoints.setText("Your Points : " + trfc.get_Str_F_Int(points));


        // clean labels
        trainyourvoicevokbaselanguage.setText("");
        trainyourvoicelabelword.setText("");       


        // delete voicefiles in tyv
        lftc.DelFile(workingDir + fs + "U" + userid + ".wav");
        //lftc.DelFile(workingDir + fs + vokidtest + ".wav");           
        lftc.DelFile(workingDir + fs + vokidtest + ".mp3");           
       
    }
}
```

Hier die Aufnahmeklasse :

```
public class RecordingVoiceFile {
   
    final long          recordTime;
    final String        voicefilename;
    final String        pathvoicefile;
    final ProgressBar   progbar ;

    /**
     *  The sample rate - 44,100 Hz for CD quality audio.
     */
    public static final int     SAMPLE_RATE             = 44100;   
    private static final int    BYTES_PER_SAMPLE        = 2;                    // 16-bit audio
    private static final int    BITS_PER_SAMPLE         = 16;                   // 16-bit audio
    private static final double MAX_16_BIT              = Short.MAX_VALUE;      // 32,767
    private static final int    SAMPLE_BUFFER_SIZE      = 4096;   
   
    private static byte[]       buffer;                                         // our internal buffer
   
    public TargetDataLine targetLine = null;
  
    public RecordingVoiceFile(long lngrecordTime, String strpathvoicefile, String strvoicefilename, ProgressBar probar) throws LineUnavailableException
    {
        this.recordTime         = lngrecordTime;
        this.pathvoicefile      = strpathvoicefile;
        this.voicefilename      = strvoicefilename;
        this.progbar            = probar;
       
        System.out.println("AUDIO RECORDING START:");
        progbar.setProgress(-1);
       
        AudioFormat         format = new AudioFormat((float) SAMPLE_RATE, BITS_PER_SAMPLE, 1, true, false);
        //AudioFormat         format      = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 16, 2, 4, 44100, false);
        DataLine.Info       info        = new DataLine.Info(TargetDataLine.class, format);
       
        // Prüft ob das LocalSystem diese Audios erzeugen kann
        if(AudioSystem.isLineSupported(info)){
           
            System.err.println("Line supported");
       
            targetLine = (TargetDataLine)AudioSystem.getLine(info);

            // öffnet den kanal um zu lesen
            targetLine.open(format, SAMPLE_BUFFER_SIZE * BYTES_PER_SAMPLE);
            System.out.println("Start Recording: ");

            // the internal buffer is a fraction of the actual buffer size, this choice is arbitrary
            // it gets divided because we can't expect the buffered data to line up exactly with when
            // the sound card decides to push out its samples.
            buffer = new byte[SAMPLE_BUFFER_SIZE * BYTES_PER_SAMPLE/3];
           
            // ließt die audio infos schreibt sie aber noch nicht in eine datei
            targetLine.start();

            Thread thread = new Thread(){

                @Override public void run() {

                    AudioInputStream audioStream = new AudioInputStream(targetLine);
                    File audioFile = new File(pathvoicefile, voicefilename);

                    try {

                        AudioSystem.write(audioStream, AudioFileFormat.Type.WAVE, audioFile);
                       
                    } catch (IOException io) {
                        io.printStackTrace();
                    }
                }
            };


            thread.start();
            Timeline timeline = new Timeline(new KeyFrame(Duration.millis(recordTime),ae -> { stopRecording(); }));
            timeline.play();
       
        } else {
            System.err.println("Audio not supported from system");
            progbar.setProgress(0);
        }
    }

    private void stopRecording() {
       
        System.out.println("STOP");
        targetLine.stop();
        targetLine.drain();
        targetLine.close();
        progbar.setProgress(0);
   
    }
}
```


----------



## mihe7 (1. Feb 2019)

Erstell Dir mal ein http://sscce.org/ und schau, ob das Problem weiterhin besteht. Wenn ja, dann stell den Code mal hier inkl. einer nicht funktionierenden Audiodatei zur Verfügung, dann können andere mal schauen, ob bei ihnen der Fehler auch auftritt. 

Ich kenne mich mit JavaFX nicht aus, würde aber den Listener explizit abmelden:

```
public void dispose() {
      if (player != null)
        player.setOnEndOfMedia(null);
        player.dispose();
    }
```


----------



## Joob (2. Feb 2019)

Gute Idee mit der Sounddatei, ich lege sie hier mal ab.
Es wird bis elf gezählt.
Bei mir wird beim ersten mal bis 3 oder 4 abgespielt , beim nächsten mal bis 6 aber nicht bis zum Ende.


----------



## Joob (2. Feb 2019)

Ich habe deinen Vorschlag probiert vor dem dispose, welches ja alle Resourcen vom Mediaplayer freigeben soll das setonendofmedia zu setzten, was aber zu einer nullpointerEx führt.


----------



## mihe7 (2. Feb 2019)

Joob hat gesagt.:


> was aber zu einer nullpointerEx führt.


Wieso das denn?!? 

Kannst Du noch ein Minimalbeispiel einstellen? Dann kann man sich das zusammen mit Deiner Audiodatei mal live ansehen.


----------



## Joob (2. Feb 2019)

Was meinst du mit Minibeispiel.
Ist doch alles da, ich habe die bestehende Methode nur um die von dir vorgeschlagenen Zeile erweitert.
Die U1.zip enthält die wave, da habe ich extra gezählt um den Effekt deutlicher zu machen.

Die geposteten Methoden enthalten die Abläufe, um es nachzustellen braucht man doch nur die
Variable workingdir... anzupassen und eine zusätzliche eigene Audiodatei da rein zu legen.

Oder meinst Du etwas anderes. 
Was brauchst du um es zu testen.


----------



## mihe7 (2. Feb 2019)

Joob hat gesagt.:


> Was meinst du mit Minibeispiel.


Ich meine auf ein Minimum reduzierten Quellcode, den man - so wie er ist(*) - übersetzen und ausführen kann und in dem das Problem gerade noch auftritt (http://sscce.org/)

* Namen der verwendeten Audiodateien muss man natürlich ggf. anpassen.



Joob hat gesagt.:


> welches ja alle Resourcen vom Mediaplayer freigeben soll


Ich weiß nicht, wie das in JavaFX ist, in AWT/Swing sind mit Ressourcen nur Dinge gemeint, die von der Komponente selbst erzeugt bzw. angefordert wurden. Dazu zählen z. B. Betriebssystem-Ressourcen wie Fensterhandle etc. Listener gehören dagegen nicht dazu. 

Warum ich den Listener explizit abmelden würde hat zwei Gründe. Erstens werden Events nicht sofort ausgeführt sondern in eine Queue eingereiht, die vom UI-Thread abgearbeitet wird. Zweitens können Memory Leaks entstehen.


----------



## Joob (2. Feb 2019)

Ich mach das mit dem Code gleich mal.

Zu dem was du als zweites anführst, das war auch mein Gedanke. Um da zu einem Ergebniss zu kommen habe ich erst aufgenommen.
Dann habe ich die Datei mit einem Player vom Betriebssystem abspielen lassen, was auch komplett hingehauen hat.
Dann habe ich in der App die Methode laufen lassen, immer noch der Effekt.

Bei einem weiteren Test habe ich die aufgenommene Datei gelöscht, hat hingehauen, also sind an der Datei keine Prozesse mehr aktiv.

Damit war ich der Meinung nun der Meinung das es sich nicht um einen Effekt handeln kann der mit einem noch nicht abgeschlossenen Prozess beim erstellen der wav handeln kann.

Ich finde einfach keine Erklärung, zumal nach mehrfachen abspielen ja immer mehr der wav abgespielt wird.
Also ein Fehler in der wav kann jetzt auch nicht unbedingt dazu führen, auch wird die wav ja beim abspielen nicht verändert. ggf. die Größe und irgenwelche Effekte beim streamen des Mediaplayers aber wie dafür habe ich keine Erklärung.


Hier noch mal der Code, sollte in einer leeren Klasse laufen
SequentialMedia ist innere Klasse


```
public void handletrainyourvoicedone () throws InterruptedException {
    
           
        String yourmp3file = "";
        String mywave = "";
        Media teachervoice = new Media(new File(yourmp3file).toURI().toString());


        Media yourvoice = new Media(new File(mywave).toURI().toString());
       
        Media[] medialst = new Media[2];
        medialst[0] = teachervoice;
        medialst[1] = yourvoice;

       
        SequentialMedia sqmed = new SequentialMedia(medialst);
        sqmed.play();
 
                      
    }

private class SequentialMedia {
   
    LocalFileToolClass  lftc                = new LocalFileToolClass();
    TransformClass      trfc                = new TransformClass();
   
    private final Media[] medias;
    private int next;
    private MediaPlayer player;
   
   
    public SequentialMedia(Media... medias) {
      this.medias = medias;
    }
   
   
    private void playNext() {
      dispose();
        System.out.println("next : " + next);
      if (next >= medias.length){
          System.out.println("mache schluss");
        allDone();
        return;
      }
         
      player = new MediaPlayer(medias[next++]);
      player.setOnEndOfMedia(this::playNext);
      player.play();
       
    }
   
   
    public void play() {
      next = 0;
      playNext();
    }
   
   
    public void dispose() {
      if (player != null)
       
        player.dispose();

    }
   
    public void allDone() {
       
       
        // delete voicefiles in tyv
        lftc.DelFile(mywave);
        
        lftc.DelFile(yourmp3file);           
       
    }
}
```


----------



## Joob (2. Feb 2019)

Eins noch, das löschen in allDone muss du noch deaktivieren


----------



## mihe7 (2. Feb 2019)

Zwei Dinge:
1. Im Code oben habe ich übersehen, dass Dein if keinen then-Block sondern nur ein then-Statement hat, richtig muss es also heißen:

```
public void dispose() {
      if (player != null) {
        player.setOnEndOfMedia(null);
        player.dispose();
      }
    }
```
2. Es leigt definitiv am Wave. Der Player hängt sich einfach auf, es gibt keine Statusmeldungen, nichts.


----------



## Joob (2. Feb 2019)

Ja, leider ohne Exception, aber zumindest kann man das reproduzieren.
Wie kann man da den Fehler/Ursache finden ?


----------



## Joob (2. Feb 2019)

Ach so, was man beachten muss: Der Player vom System spielt das wav korrekt ab.


----------



## mihe7 (2. Feb 2019)

OMG, ich nehme alles zurück. Deine lokale Variable wird vermutlich einfach Opfer des Garbage Collectors... Instanzvariable -> schon funktionierts.


```
import java.io.File;
import javafx.application.*;
import javafx.beans.value.*;
import javafx.stage.*;
import javafx.scene.media.*;

public class Test extends Application {
    private SequentialMedia sqmed;

    public void start(Stage stage) {
        try {
            handletrainyourvoicedone();
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }

    public void handletrainyourvoicedone () throws InterruptedException {
        String yourmp3file = "my.mp3";
        String mywave = "your.wav";
        Media teachervoice = new Media(new File(yourmp3file).toURI().toString());
        Media yourvoice = new Media(new File(mywave).toURI().toString());

        Media[] medialst = new Media[2];
        medialst[1] = teachervoice;
        medialst[0] = yourvoice;

        sqmed = new SequentialMedia(medialst);
        sqmed.play();
    }

    private class SequentialMedia {
        private final Media[] medias;
        private int next;
        private MediaPlayer player;
        private ChangeListener l = (s,o,n) -> System.out.println(n);

        public SequentialMedia(Media... medias) {
          this.medias = medias;
        }


        private void playNext() {
          System.err.println("next : " + next);
          dispose();
          if (next >= medias.length){
              System.err.println("mache schluss");
            allDone();
            return;
          }

          player = new MediaPlayer(medias[next++]);
          player.setOnEndOfMedia(this::playNext);
          player.statusProperty().addListener(l);
          player.setOnStalled(() -> System.err.println("STALLED"));

          player.play();
        }

        public void play() {
          next = 0;
          playNext();
        }


        public void dispose() {
          if (player != null) {
            player.setOnEndOfMedia(null);
            player.statusProperty().removeListener(l);
            player.dispose();
          }
        }

        public void allDone() {
        }
    }
}
```


----------



## Joob (2. Feb 2019)

Ich verstehe noch nicht was du genau gemacht hast.
Könntest du mir das kurz erklären ?

Aber erst mal Danke für deine Mühe.


----------



## Joob (2. Feb 2019)

Insbesondere das mit dem Listener, hast dadurch eine Exception vom Player bekommen.
Das mit der Klasse habe ich kapiert, oben instanziert und sie ist bleibt unberührt solange die Klasse ein Objekt ist ohne das der garbagecollector sie entsorgen kann, oder


----------



## mihe7 (2. Feb 2019)

Joob hat gesagt.:


> Ich verstehe noch nicht was du genau gemacht hast.


Einfach auf das SequentialMedia-Objekt mit einer Instanzvariablen referenziert. Die lokale Variable, die Du vorher hattest, existiert ja nicht mehr, sobald die Methode verlassen wird.

Den Listener habe ich vorhin nur zu Testzwecken registriert, damit ich die Stati gemeldet bekomme.


----------



## Joob (2. Feb 2019)

Es gibt noch einen interessanten Aspekt
Die erste Datei wurde ja immer ganz abgespielt, und das war unabhängig von der Position der Instanzierung des Players.

Bei dieser Datei ist es so das sie sich nicht löschen läßt bevor die jar beendet wird, das hat dann eben dazu geführt das sie ganz abgespielt wurde weil, wahrscheinlich der Garbagecollector den Player nicht oder sonst was nicht löschen konnte.

Ich versuche nun herauszufinden welcher Prozess auf der Datei sitzt.
Weißt du wie man das ermitteln kann ?


----------



## mihe7 (2. Feb 2019)

Windows: Process Explorer (gibts bei MS)
Linux: lsof


----------



## Joob (3. Feb 2019)

Hallo mihe7,
danke für den Hinweis auf den Process Explorer, leider konnte ich damit keine Erkenntnisse gewinnen.
Also habe ich ein wenig tryanderror betrieben:

Dabei kam heraus:
sobald die mp3 in die Medialist geladen ist, ist sie gesperrt
und das ändert sich auch nicht mehr bis die jar beendet ist.

Also habe ich die mp3 erst in eine wav umgewandelt und dann die wav in die Medialist eingefügt
Dann haut es hin, die mp3 kann gelöscht werden und die wav auch.

Hast du für das Verhalten eine Erklärung ?

by the way: die mp3 lade ich nur herunter weil ich wenn diese beim Hoster liegt, ich nicht die Abspielzeit feststellen kann und die benötige ich als Basiswert für die Aufnahme.


----------



## mihe7 (3. Feb 2019)

Hm... das Problem scheint nicht nur Dich zu betreffen: https://stackoverflow.com/questions...-seem-to-release-mp3-file-even-when-using-dis

Und wenn ich mir den folgenden Link so ansehe, scheint das Ding auch nicht sonderlich stabil zu sein: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8210828

Ich würde sagen: file a bug report  Spätestens, wenn MediaPlayer#dispose() aufgerufen wird, sollte die Sperre aufgehoben werden. Wobei man auch die Meinung vertreten könnte, dass das alleinige Erzeugen einer Media-Instanz noch keine Sperre setzen sollte. Der tatsächliche Zugriff erfolgt ggf. erst später.



Joob hat gesagt.:


> und das ändert sich auch nicht mehr bis die jar beendet ist.


Du könntest - falls nicht sowieso geschehen - mal schauen, ob bei Verwendung eines GUIs die Datei tatsächlich bis zum Beenden der Anwendung gesperrt bleibt. Falls nicht, könnte es eventuell helfen, wenn Du allDone() zu einem späteren Zeitpunkt aufrufst (z. B. via Platform.runLater()).


----------



## Joob (3. Feb 2019)

Interessant zu hören das auch andere das Problem haben.
Aber mein workaround funktioniert. 

Hab heut den Teil fertig programmiert und dabei 50 mal die Funktion verwendet. 
Läuft so, kannst du den Betroffenen ja mal mitteilen, also bei wav's hauts hin, 
scheint mit einer Spezialität von mp3 zu tun zu haben.


----------



## mihe7 (3. Feb 2019)

Joob hat gesagt.:


> scheint mit einer Spezialität von mp3 zu tun zu haben.


Ach, das hatte ich ganz verdrängt... Meine Vermutung wäre, dass WAV mit Boardmitteln abgespielt wird, für MP3 (und vermutlich auch andere Formate) eine Lib verwendet wird und da der Hund begraben liegt.



Joob hat gesagt.:


> kannst du den Betroffenen ja mal mitteilen


lol - ich bin nicht in Kontakt mit anderen Betroffenen


----------



## Joob (4. Feb 2019)

Ich hab auch noch ne Kleinigkeit beizusteuern, obwohl ich nicht weiß ob das wirklich relevant ist.

Ich hab dir ja geschrieben das ich die Länge der mp3 bestimme.
Das mache ich mit: JLayer

und diese JLayer jar hat für seinen bitstream, eine seperate close Methode für Mp3's wo offensichtlich die Frames geschlossen werden.  

Ggf. ist es ja so das beim mp3 Format nicht nur die Headerdatei geschlossen werden muss sondern seperat die Frames, welche wahrscheinlich einer nach dem anderen aufgerufen werden, dekompremiert werden und dann abgespielt werden. 

Da hab aber ich nicht wirklich Ahnung von, wollte es aber nicht unerwähnt lassen.
Vielleicht zu viel spekuliert.


----------



## mihe7 (4. Feb 2019)

Joob hat gesagt.:


> Ich hab auch noch ne Kleinigkeit beizusteuern, obwohl ich nicht weiß ob das wirklich relevant ist.


Wenn Du den Code aus #13 nimmst, bleibt dann die Datei in allDone gesperrt oder nicht?


----------

