@@ -62,7 +62,7 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
6262 * @param output
6363 * @param decoder
6464 */
65- AudioPlayer (AudioSource & source, AudioOutput & output, AudioDecoder & decoder) {
65+ AudioPlayer (AudioSource& source, AudioOutput& output, AudioDecoder& decoder) {
6666 TRACED ();
6767 this ->p_source = &source;
6868 this ->p_decoder = &decoder;
@@ -81,8 +81,8 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
8181 * @param decoder
8282 * @param notify
8383 */
84- AudioPlayer (AudioSource & source, Print & output, AudioDecoder & decoder,
85- AudioInfoSupport * notify = nullptr ) {
84+ AudioPlayer (AudioSource& source, Print& output, AudioDecoder& decoder,
85+ AudioInfoSupport* notify = nullptr ) {
8686 TRACED ();
8787 this ->p_source = &source;
8888 this ->p_decoder = &decoder;
@@ -99,7 +99,7 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
9999 * @param output
100100 * @param decoder
101101 */
102- AudioPlayer (AudioSource & source, AudioStream & output, AudioDecoder & decoder) {
102+ AudioPlayer (AudioSource& source, AudioStream& output, AudioDecoder& decoder) {
103103 TRACED ();
104104 this ->p_source = &source;
105105 this ->p_decoder = &decoder;
@@ -109,13 +109,13 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
109109 }
110110
111111 // / Non-copyable: copy constructor is deleted
112- AudioPlayer (AudioPlayer const &) = delete ;
112+ AudioPlayer (AudioPlayer const &) = delete ;
113113
114114 // / Non-assignable: assignment operator is deleted
115- AudioPlayer & operator =(AudioPlayer const &) = delete ;
115+ AudioPlayer& operator =(AudioPlayer const &) = delete ;
116116
117117 // / Sets the final output to an AudioOutput (adds Volume/Fade for PCM)
118- void setOutput (AudioOutput & output) {
118+ void setOutput (AudioOutput& output) {
119119 if (p_decoder->isResultPCM ()) {
120120 this ->fade .setOutput (output);
121121 this ->volume_out .setOutput (fade);
@@ -130,7 +130,7 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
130130 }
131131
132132 // / Sets the final output to a Print (adds Volume/Fade for PCM)
133- void setOutput (Print & output) {
133+ void setOutput (Print& output) {
134134 if (p_decoder->isResultPCM ()) {
135135 this ->fade .setOutput (output);
136136 this ->volume_out .setOutput (fade);
@@ -145,7 +145,7 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
145145 }
146146
147147 // / Sets the final output to an AudioStream (adds Volume/Fade for PCM)
148- void setOutput (AudioStream & output) {
148+ void setOutput (AudioStream& output) {
149149 if (p_decoder->isResultPCM ()) {
150150 this ->fade .setOutput (output);
151151 this ->volume_out .setOutput (fade);
@@ -182,15 +182,15 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
182182 // start dependent objects
183183 out_decoding.begin ();
184184
185- if (!p_source->begin ()){
185+ if (!p_source->begin ()) {
186186 LOGE (" Could not start audio source" );
187187 return false ;
188188 }
189- if (!meta_out.begin ()){
189+ if (!meta_out.begin ()) {
190190 LOGE (" Could not start metadata output" );
191191 return false ;
192192 }
193- if (!volume_out.begin ()){
193+ if (!volume_out.begin ()) {
194194 LOGE (" Could not start volume control" );
195195 return false ;
196196 }
@@ -222,6 +222,7 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
222222 void end () {
223223 TRACED ();
224224 active = false ;
225+ eof_called = false ;
225226 out_decoding.end ();
226227 meta_out.end ();
227228 // remove any data in the decoder
@@ -233,19 +234,19 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
233234 }
234235
235236 // / Returns the active AudioSource
236- AudioSource & audioSource () { return *p_source; }
237+ AudioSource& audioSource () { return *p_source; }
237238
238239 // / Sets or replaces the AudioSource
239- void setAudioSource (AudioSource & source) { this ->p_source = &source; }
240+ void setAudioSource (AudioSource& source) { this ->p_source = &source; }
240241
241242 // / Sets or replaces the AudioDecoder
242- void setDecoder (AudioDecoder & decoder) {
243+ void setDecoder (AudioDecoder& decoder) {
243244 this ->p_decoder = &decoder;
244245 out_decoding.setDecoder (p_decoder);
245246 }
246247
247248 // / Adds/updates a listener notified on audio info changes
248- void addNotifyAudioChange (AudioInfoSupport * notify) {
249+ void addNotifyAudioChange (AudioInfoSupport* notify) {
249250 this ->p_final_notify = notify;
250251 // notification for audio configuration
251252 if (p_decoder != nullptr ) {
@@ -282,7 +283,7 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
282283 // / plays a complete audio file or url from start to finish (blocking call)
283284 // / @param path path to the audio file or url to play (depending on source)
284285 // / @return true if file was found and played successfully, false otherwise
285- bool playPath (const char * path) {
286+ bool playPath (const char * path) {
286287 TRACED ();
287288 if (!setPath (path)) {
288289 LOGW (" Could not open file: %s" , path);
@@ -300,8 +301,7 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
300301 }
301302
302303 // / Obsolete: use PlayPath!
303- bool playFile (const char *path) { return playPath (path); }
304-
304+ bool playFile (const char * path) { return playPath (path); }
305305
306306 // / Halts playback; equivalent to setActive(false)
307307 void stop () {
@@ -328,7 +328,7 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
328328 }
329329
330330 // / Selects stream by path without changing the source iterator
331- bool setPath (const char * path) {
331+ bool setPath (const char * path) {
332332 TRACED ();
333333 writeEnd ();
334334 stream_increment = 1 ;
@@ -346,10 +346,11 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
346346 }
347347
348348 // / Activates the provided Stream as current input
349- bool setStream (Stream * input) {
349+ bool setStream (Stream* input) {
350350 end ();
351351 out_decoding.begin ();
352352 p_input_stream = input;
353+ eof_called = false ; // reset EOF state for new stream
353354 if (p_input_stream != nullptr ) {
354355 LOGD (" open selected stream" );
355356 meta_out.begin ();
@@ -362,7 +363,7 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
362363 }
363364
364365 // / Returns the currently active input Stream (e.g., file)
365- Stream * getStream () { return p_input_stream; }
366+ Stream* getStream () { return p_input_stream; }
366367
367368 // / Checks whether playback is active
368369 bool isActive () { return active; }
@@ -437,12 +438,21 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
437438 LOGD (" copy: %d -> 0" , (int )bytes);
438439 return 0 ;
439440 }
441+ // EOF callback: when no bytes were copied, trigger once per stream
442+ if (result == 0 && p_input_stream != nullptr && !eof_called) {
443+ eof_called = true ;
444+ if (on_eof_callback != nullptr ) {
445+ on_eof_callback (*this );
446+ }
447+ }
448+
440449 // handle sound
441450 result = copier.copyBytes (bytes);
442451 if (result > 0 || timeout == 0 ) {
443452 // reset timeout if we had any data
444453 timeout = millis () + p_source->timeoutAutoNext ();
445454 }
455+
446456 // move to next stream after timeout
447457 moveToNextFileOnTimeout ();
448458
@@ -462,11 +472,11 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
462472 }
463473
464474 // / Sets a custom VolumeControl implementation
465- void setVolumeControl (VolumeControl & vc) { volume_out.setVolumeControl (vc); }
475+ void setVolumeControl (VolumeControl& vc) { volume_out.setVolumeControl (vc); }
466476
467477 // / Provides access to StreamCopy to register additional callbacks
468478 // / callbacks
469- StreamCopy & getStreamCopy () { return copier; }
479+ StreamCopy& getStreamCopy () { return copier; }
470480
471481 // / When enabled, writes zeros while inactive to keep sinks alive
472482 void setSilenceOnInactive (bool active) { silence_on_inactive = active; }
@@ -485,7 +495,7 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
485495 }
486496
487497 // / Returns the VolumeStream used by the player
488- VolumeStream & getVolumeStream () { return volume_out; }
498+ VolumeStream& getVolumeStream () { return volume_out; }
489499
490500 // / Enables/disables automatic fade in/out to prevent pops
491501 void setAutoFade (bool active) { is_auto_fade = active; }
@@ -497,10 +507,10 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
497507 void setMetaDataSize (int size) { meta_out.resize (size); }
498508
499509 // / Sets a user reference passed to the stream-change callback
500- void setReference (void * ref) { p_reference = ref; }
510+ void setReference (void * ref) { p_reference = ref; }
501511
502512 // / Defines the metadata callback
503- void setMetadataCallback (void (*callback)(MetaDataType type, const char * str,
513+ void setMetadataCallback (void (*callback)(MetaDataType type, const char * str,
504514 int len),
505515 ID3TypeSelection sel = SELECT_ID3) {
506516 TRACEI ();
@@ -518,12 +528,19 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
518528 }
519529
520530 // / Defines a callback that is called when the stream is changed
521- void setOnStreamChangeCallback (void (*callback)(Stream * stream_ptr,
522- void * reference)) {
531+ void setOnStreamChangeCallback (void (*callback)(Stream* stream_ptr,
532+ void * reference)) {
523533 on_stream_change_callback = callback;
524- if (p_input_stream!= nullptr ) callback (p_input_stream, p_reference);
534+ if (p_input_stream != nullptr ) callback (p_input_stream, p_reference);
525535 }
526-
536+
537+ // / Defines a callback that is invoked exactly once when the current stream
538+ // / reaches EOF (no more bytes can be read from the input stream).
539+ // / Signature: void onEOF(AudioPlayer& player)
540+ void setOnEOFCallback (void (*callback)(AudioPlayer& player)) {
541+ on_eof_callback = callback;
542+ }
543+
527544 // / Mutes or unmutes the audio player
528545 void setMuted (bool muted) {
529546 if (muted) {
@@ -542,22 +559,21 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
542559 // / Returns true if the player is currently muted
543560 bool isMuted () { return current_volume == 0 .0f ; }
544561
545-
546562 protected:
547563 bool active = false ;
548564 bool autonext = true ;
549565 bool silence_on_inactive = false ;
550- AudioSource * p_source = nullptr ;
566+ AudioSource* p_source = nullptr ;
551567 VolumeStream volume_out; // Volume control
552568 FadeStream fade; // Phase in / Phase Out to avoid popping noise
553569 MetaDataID3 meta_out; // Metadata parser
554570 EncodedAudioOutput out_decoding; // Decoding stream
555571 CopyDecoder no_decoder{true };
556- AudioDecoder * p_decoder = &no_decoder;
557- Stream * p_input_stream = nullptr ;
558- AudioOutput * p_final_print = nullptr ;
559- AudioStream * p_final_stream = nullptr ;
560- AudioInfoSupport * p_final_notify = nullptr ;
572+ AudioDecoder* p_decoder = &no_decoder;
573+ Stream* p_input_stream = nullptr ;
574+ AudioOutput* p_final_print = nullptr ;
575+ AudioStream* p_final_stream = nullptr ;
576+ AudioInfoSupport* p_final_notify = nullptr ;
561577 StreamCopy copier; // copies sound into i2s
562578 AudioInfo info;
563579 bool meta_active = false ;
@@ -567,9 +583,12 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
567583 float muted_volume = 0 .0f ;
568584 int delay_if_full = 100 ;
569585 bool is_auto_fade = true ;
570- void *p_reference = nullptr ;
571- void (*on_stream_change_callback)(Stream *stream_ptr,
572- void *reference) = nullptr ;
586+ void * p_reference = nullptr ;
587+ void (*on_stream_change_callback)(Stream* stream_ptr,
588+ void * reference) = nullptr ;
589+ // EOF callback and guard (invoked once when current stream reaches end)
590+ void (*on_eof_callback)(AudioPlayer& player) = nullptr ;
591+ bool eof_called = false ;
573592
574593 void setupFade () {
575594 if (p_final_print != nullptr ) {
@@ -609,14 +628,15 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
609628 // restart the decoder to make sure it does not contain any audio when we
610629 // continue
611630 p_decoder->begin ();
631+ eof_called = false ; // prepare for next stream
612632 }
613633
614634 // / Callback implementation which writes to metadata
615- static void decodeMetaData (void * obj, void * data, size_t len) {
635+ static void decodeMetaData (void * obj, void * data, size_t len) {
616636 LOGD (" %s, %zu" , LOG_METHOD, len);
617- AudioPlayer * p = (AudioPlayer *)obj;
637+ AudioPlayer* p = (AudioPlayer*)obj;
618638 if (p->meta_active ) {
619- p->meta_out .write ((const uint8_t *)data, len);
639+ p->meta_out .write ((const uint8_t *)data, len);
620640 }
621641 }
622642};
0 commit comments