DFPlayer detect when song is done playing

I've just started playing around with these DFPlayer modules using the DFRobotDFPlayermini library. I was trying to modify the getStarted example to play a series of MP3s end to end rather than 3 second bursts of each one. I was also frustrated that there didn't seem to be a reliable way to detect when an item has finished playing (without using the BUSY pin).

However, I found where you can switch on DEBUG mode within the library (remove the comment in front of #define _DEBUG in the .h file). You can then see the command sequences being sent to the DFPlayer and what is being received back from it. If the command is sent with a 'Reply required' bit set (as the library does) in the command the module acknowledges the receipt of the command immediately. A little later it may send further messages reporting on the progress of the command. For commands that start a song playing this will include the message that the song has finished. In the example the arrival of these replies is detected and processed by

  if (myDFPlayer.available()) {
    printDetail(myDFPlayer.readType(), myDFPlayer.read()); 
  }

. However, once processed the information is no longer available. You need to set a busy flag when you start the sound playing and reset it e.g. within case DFPlayerPlayFinished:

Having added a flag I was still finding my sounds were not playing to the end. Every second sound was being cut short by the next sound being started. This turned out to be because the Finished message is apparently being sent twice by the module. The first message was clearing my busy flag so I was queuing up another sound but the second copy of the Finished message was then clearing the busy flag of the subsequent sound. You can resolve this by changing the if (myDFPlayer) { } to while (myDFPlayer) { }. All copies of the finished messages then get consumed before your code continues.

I can now play a series of sounds and reliably detect when each one finishes before queuing the next.

NB I also had noise problems with the on-board amplifier and have concluded the solution is not to use it and amplify the DAC signals externally.

1 Like

I'll have to check and see if that fixes the issues I had in my particular sketch jumping from a .play() command to a .loop() command..

I would 'randomly' have the initial .play() clip get stuck in a loop.. (maybe what you suggested/pointed out is what was getting the internal 'pointer' all wonky)..

Also.. the on-board amp is fine....nice even. You can fix the pop by commenting out an extra reset() call/line in the library files.

I wasn't getting a pop. I found that if I used the on-board amp the power drain was causing the player to brown out and reset randomly. I couldn't fix it with a smoothing capacitor and anyway I'll be using a separate amp as I want stereo output. I tried commenting out that reset() and it didn't make any difference to my problem.

Well if you werent getting the pop like the rest of us.. them commenting out the reset() call in one lib files wont do anything for you.

How are you powering the DFPlayer?

I'm powering the Arduino from a 5V bench supply and then stepping down to 3.3v for the DFPlayer with a AMS111733 regulator because the datasheet said the preferred working voltage was 4.2v rather than 5v. To be fair I'm not sure whether I was getting the pop or not. Using the amplifier so destabilised the module that I gave up on it very quickly. I was also getting a loud 10Hz (roughly) ticking sound through the speaker while it was initialising the SD card which went when I took output from the DACs.

hmm...

I personally have only been running them @ 5v.

Do you have some 1k resistors on the speaker lines?

I was getting 'noise' through the speaker = 1k resistors on each line stopped this (well enough)

I was getting a huge 'pop' from the speaker upon power up and down (not during playback or anything).. = this was traced back to a line in the library files (I posted on it a few times around here I thought?...but if your not getting the pop, then its no big deal)

I have not enabled the DAC to external headphones or amp... so I cant comment there is any differences.

Maybe your switching regulator is adding in more noise? (I hear/read that switch regulators introduce a lot of noise)

I don't think you mean 1k resistors in the speaker lines. That would stop the amplifier driving the speaker. I've seen people propose 1k resistors in the RX and TX lines but I'm using MOSFETs to convert 5v RX TX signals to 3.3v which is a better solution. I don't think the 3.3v regulator is the problem, more likely it is that the amp wants a bit more than 3.3v. I might try jacking up the GND of the regulator with a diode so it produces more like 4v just as an experiment but it's working fine for me at 3.3v if I ignore the amp and use the DAC output instead.

LMAO!..

Yes you are correct (my mistake).. on the RX/TX lines!!! (not speaker lines!) haha

Also.. so I'm clear you suggested minor updated/change..

Your saying if you use this:

You have no issues with reliably detecting an audio clip upon completion?

while(myDFPlayer.available()) {
    //do whatever checking you want here
  }

I have been using it like so in the past (with random success).. although pretty stable.. when trying to transition from a .play(#) command... (checking for it to be finished).. and then going to a .loop(#) command...

it would work flawlessly sometimes.. and other times.. it would get stuck on the .play(#) file... trying to loop that!??

if(myDFPlayer.readType()==DFPlayerPlayFinished && myDFPlayer.readCurrentFileNumber()==2) { 
    Serial.println(F("audio finished..."));
    myDFPlayer.loop(3);
}

Check the readType doesnt inherently do anything by itself.... right?

So you need to actually check for a specific 'state' of the readType response..

If the DFPlayer throws out '2' 'finished' responses.... the first one will 'trigger' a false positive...... no?

And if it doesnt 'always' kickback 2 responses... then how to you always consistently know when any given clip is complete?

There are also some other ways I have seen people try to check for 'completeness/finished state'

//if(myDFPlayer.readType()==DFPlayerPlayFinished){
              
//if(myDFPlayer.readState() != 513 && myDFPlayer.readType()==DFPlayerPlayFinished){
              
//if(myDFPlayer.readState() != 513){

I had posted a sketch on the DFRobot fourms (garbage)... to see if anyone could duplicate the same error I was getting (as described above)

I'd circle back to it if your updated code/approach fixes that.

The funny things is..

internally the DFPlayer MUST reliably detecting when a song is complete (whether its sending callbacks once or twice).. because you call a .loop(#) command.. internally the boards know when its done..... and (for the most part) seamlessly plays it again.

What I'm saying is that the DFPlayer sometimes returns multiple messages. The example code processes a single message within the if (). If you request a track and then wait for a Finished message, when it arrives the example code just processes the first Finished message. If you assume you can go ahead and request another track, the second Finished message is still lurking and the next time you call myDFPlayer.available() it will appear that the DFPlayer is telling you that the second track has finished while in fact it is just a repeat of the first Finished.

By replacing the if ( ) with a while( ) the code will process all the messages that are queued up before returning control back to your code.

I agree it's very odd behaviour and difficult to accept is some sort of bug in the silicon. I don't rule out that the library has a bug and is returning the same message twice under some circumstances. When I get time I'll put my logic analyser on the output of the DFPlayer to confirm if the message is really being repeated.

However, using the library as it is you can rely on the first Finished message being a reliable indication that the track has finished. The second is the false one. One way of fixing the library (if the DFPlayer is indeed returning duplicate messages) could be to save each message that has been processed and discard any message that is exactly the same.

I haven't tried using the loop command so I can't comment on strange behaviour there but I suspect it's
down to the same root cause - repeated replies.

I should have added that you still need to set a global flag (e.g. below "Play Finished" in printDetail) when the DFPlayer tells you that the track has finished so that you know when you can start the next track.

The other point I meant to add is that the Finished message contains a reference to the track that has finished. It would be nice to use this to cross-check which track has now finished however it appears to be returning the index number of the track on the SD card which is not necessarily the number that you requested to play. If you requested to play a track in the MP3 folder (playMp3Folder() command = 0x12) or play a track in a numbered folder (playFolder() command = 0x0F) the file number returned is different. So I reckon the best solution is to ignore the returned file number and just suppress duplicate replies.

I've now tested the DFPlayer mini with my Saleae Logic analyser and I can confirm that usually the DFPlayer sends the 'Finished' message twice (but sometimes only once).

My sketch cycles through a sequence of several sounds waiting for each one to finish before playing the next.

In the screenshots the upper line is the TX into the DFPlayer and the lower line is the RX from the DFPlayer.

The first (ascii1.jpg) starts with a single 'Finished' message (7E FF 06 3D 00 00 15 FE A9 EF) from the previous sound which initiates the request to playback the next sound (7E FF 06 12 01 00 15 FE D3 EF). The DFPlayer then acknowledges the playback command (7E FF 06 41 00 00 00 FE BA EF) and starts playing.

The second (ascii2.jpg) starts with a Finished message (7E FF 06 3D 00 00 10 FE AE EF) which triggers the request to play the next sound but as that request is being sent out you can see a second copy of the Finished message arriving from the DFPlayer. Interestingly, as SoftwareSerial has to handle the RX message you can see it causes a little delay in sending out some of the characters of the TX message. Then shortly after the DFPlayer acknowledges the play request.

So, ideally, the library needs to be altered to ignore repeated RX messages. The workaround in the meantime is to ensure that all incoming messages are handled as soon as they are received and not left lurking to be read later when they may be misinterpreted.

My test sketch (omitting the setup) is as follows:

bool _finished = true;
bool _ready = true;
int _playList[] = { 21, 22, 23, 24, 25, 26 };
int idx = 0;
void loop() 
{ 
  if (_finished && _ready){
    myDFPlayer.playMp3Folder(_playList[idx++]);  //Play next mp3 
    _finished = false;
  } 
  if (idx == 6) idx = 0;
  while (myDFPlayer.available()) { 
    printDetail(myDFPlayer.readType(), myDFPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states. 
  }
} 

 
void printDetail(uint8_t type, int value){ 
  switch (type) { 
    case TimeOut: 
      Serial.println(F("Time Out!")); 
      _finished = true;
      break; 
    case WrongStack: 
      Serial.println(F("Stack Wrong!")); 
      break; 
    case DFPlayerCardInserted: 
      Serial.println(F("Card Inserted!")); 
      break; 
    case DFPlayerCardRemoved: 
      Serial.println(F("Card Removed!")); 
      break; 
    case DFPlayerCardOnline: 
      Serial.println(F("Card Online!")); 
      _ready = true;
      _finished = true;
      break; 
    case DFPlayerPlayFinished: 
      //Serial.print(F("Number:")); 
      //Serial.print(value); 
      //Serial.println(F(" Play Finished!")); 
      _finished = true;
      
      break; 
    case DFPlayerError: 
      Serial.print(F("DFPlayerError:")); 
      switch (value) { 
        case Busy: 
          Serial.println(F("Card not found")); 
          break; 
        case Sleeping: 
          Serial.println(F("Sleeping")); 
          break; 
        case SerialWrongStack: 
          Serial.println(F("Get Wrong Stack")); 
          break; 
        case CheckSumNotMatch: 
          Serial.println(F("Check Sum Not Match")); 
          break; 
        case FileIndexOut: 
          Serial.println(F("File Index Out of Bound")); 
          break; 
        case FileMismatch: 
          Serial.println(F("Cannot Find File")); 
          break; 
        case Advertise: 
          Serial.println(F("In Advertise")); 
          break; 
        default: 
          break; 
      } 
      break; 
    default:
    Serial.println(F("??"));  
      break; 
  } 
 //delay(100);
}

This will eliminate the DFPlayer module's power on/off speaker 'pop' (when using the DFRobotDFPlayerMini-master library).

As has been noted in this thread, a call to reset() in the library causes the audio amp to pop the speaker; resetting the player while the audio amp is active causes the pop. You do not need to modify the library.

To prevent reset() from being called add the appropriate parameter to the begin() function call, as shown in the code below.

The 'false' parameter does the trick (prevents call to reset()). The 'true' parameter enables ACK in the module, which is what you want if your code reads data from the player:

 if (!DFPlayer.begin(serialVoice, true, false)) {
   Serial.println(F("Unable to begin:"));
   Serial.println(F("1.Recheck the connection."));
   Serial.println(F("2.Insert the SD card."));
   while(true)
   {
     delay(0);  // the ESP8266 watchdog likes this.
   }
 }

Viola! No more 'pop'.

This is my function to play the designated file in a folder at a specified volume. The function returns after file playback is complete:

int playVoice(int folder, int file, int volume) {
  DFPlayer.volume(volume);
  DFPlayer.playFolder(folder, file);
  delay(100);
  int playerState = 0;
  while(playerState != 512) {
    delay(500);          // prevents clicks and static during playback - increase if necessary.
    playerState = DFPlayer.readState();
  }
  DFPlayer.volume(0);
  return playerState;
}

Add an exit conditional to the while loop in case of module/playback failure. If playerState does not change from 0 at any time then a problem occurred... use that and a time elapsed to exit.

2 Likes

For playing mp3 till the end i use this code, and it works on mine NodeMCU and DFPlayer Mini.

     myDFPlayer.playFolder(01, 4);  //play 
     delay(300);  //Wait for 
     int stt = myDFPlayer.readState();
     while ( stt == 529 || stt == 513 || stt == 512){ 
        stt = myDFPlayer.readState();
        //Serial.println( stt );
        //Serial.println( myDFPlayer.readType() );
        delay(300);
     }

The only thing that worked in my case is analog read DFPlayer Mini (pin 16) BUSY. At the end of MP3 file BUSY is HIGH 3,3V When MP3 is playing BUSY is low.

myDFPlayer.playFolder(02, 3);  //specific mp3 in SD:/02/003.mp3; Folder Name(1~99); File Name(1~255)
delay(300);  //Wait for read file
endOfFile = analogRead(A5);
while (endOfFile <= 600) {  //Approximately 3.3V = 675 on Arduino UNO
delay(300);
endOfFile = analogRead(A5); //Analog read BUSY on DFPlayer Mini
}

For what it's worth, DFPlayerMini_Fast.h has a built-in function, "player.isPlaying()", that returns a boolean based on if the module is playing a song currently or not.