@@ -487,31 +487,9 @@ class MultiStreamingDecoder : public StreamingDecoder {
487
487
488
488
// Automatically select decoder if not already selected
489
489
if (is_first) {
490
- if (actual_decoder.decoder == nullptr ) {
491
- // Read some data to determine the mime type
492
- detection_buffer.resize (80 );
493
- size_t bytesRead =
494
- readBytes (detection_buffer.data (), detection_buffer.size ());
495
- if (bytesRead == 0 ) return false ;
496
-
497
- // Make data available again
498
- buffered_stream.setBuffer (detection_buffer.data (), bytesRead);
499
-
500
- // Use the mime detector to determine format
501
- mime_detector.write (detection_buffer.data (), bytesRead);
502
- const char * mime = mime_detector.mime ();
503
-
504
- if (mime != nullptr ) {
505
- LOGI (" mime from mime_detector: %s" , mime);
506
- // Select the decoder based on the determined mime type
507
- if (!selectDecoder (mime)) {
508
- LOGE (" The decoder could not be found for %s" , mime);
509
- return false ;
510
- }
511
- } else {
512
- LOGE (" Could not determine mime type" );
513
- return false ;
514
- }
490
+ // determine the mime and select the decoder
491
+ if (!selectDecoder ()) {
492
+ return false ;
515
493
}
516
494
517
495
// Set up buffered input stream that includes the detection data
@@ -532,7 +510,6 @@ class MultiStreamingDecoder : public StreamingDecoder {
532
510
return actual_decoder.decoder ->copy ();
533
511
}
534
512
535
-
536
513
/* *
537
514
* @brief Selects the actual decoder by MIME type
538
515
*
@@ -545,59 +522,73 @@ class MultiStreamingDecoder : public StreamingDecoder {
545
522
*/
546
523
bool selectDecoder (const char * mime) {
547
524
bool result = false ;
525
+
526
+ // Guard against null MIME type - cannot proceed without valid MIME
548
527
if (mime == nullptr ) return false ;
549
528
550
- // Do nothing if no change
529
+ // Optimization: Check if the requested MIME type is already active
530
+ // This avoids unnecessary decoder switching when the same format is detected
551
531
if (StrView (mime).equals (actual_decoder.mime )) {
552
- is_first = false ;
553
- return true ;
532
+ is_first = false ; // Mark initialization as complete
533
+ return true ; // Already using the correct decoder
554
534
}
555
535
556
- // Close actual decoder if different
536
+ // Clean shutdown of currently active decoder before switching
537
+ // This ensures proper resource cleanup and state reset
557
538
if (actual_decoder.decoder != nullptr ) {
558
539
actual_decoder.decoder ->end ();
559
540
}
560
541
561
- // Find the corresponding decoder
562
- selected_mime = nullptr ;
542
+ // Search through all registered decoders to find one that handles this MIME type
543
+ selected_mime = nullptr ; // Clear previous selection
563
544
for (int j = 0 ; j < decoders.size (); j++) {
564
545
DecoderInfo info = decoders[j];
546
+
547
+ // Check if this decoder supports the detected MIME type
565
548
if (StrView (info.mime ).equals (mime)) {
566
549
LOGI (" Using StreamingDecoder for %s (%s)" , info.mime , mime);
550
+
551
+ // Switch to the matching decoder
567
552
actual_decoder = info;
568
553
569
- // Set up output for the selected decoder
554
+ // Configure the decoder's output stream to match our output
555
+ // This ensures decoded audio data flows to the correct destination
570
556
if (p_print != nullptr ) {
571
557
actual_decoder.decoder ->setOutput (*p_print);
572
558
}
573
559
574
- // Note: Input will be set up later with buffered stream
560
+ // Note: Input stream will be configured later with the buffered stream
561
+ // that preserves the data used for MIME detection
575
562
576
- // Start the decoder
563
+ // Initialize the selected decoder and mark it as active
577
564
if (actual_decoder.decoder ->begin ()) {
578
565
actual_decoder.is_open = true ;
579
566
LOGI (" StreamingDecoder %s started" , actual_decoder.mime );
580
567
} else {
568
+ // Decoder failed to start - this is a critical error
581
569
LOGE (" Failed to start StreamingDecoder %s" , actual_decoder.mime );
582
570
return false ;
583
571
}
584
572
573
+ // Successfully found and initialized a decoder
585
574
result = true ;
586
- selected_mime = mime;
587
- break ;
575
+ selected_mime = mime; // Store the MIME type that was selected
576
+ break ; // Stop searching once we find a match
588
577
}
589
578
}
579
+
580
+ // Mark initialization phase as complete regardless of success/failure
590
581
is_first = false ;
591
- return result;
582
+ return result; // true if decoder was found and started, false otherwise
592
583
}
593
584
594
585
/* *
595
586
* @brief Provides the MIME type of the selected decoder
596
- *
597
587
* @return MIME type string of the currently active decoder, or nullptr
598
588
* if no decoder is selected
599
589
*/
600
590
const char * mime () override {
591
+ // fallback to actual decoder
601
592
if (actual_decoder.decoder != nullptr ) {
602
593
return actual_decoder.decoder ->mime ();
603
594
}
@@ -646,6 +637,38 @@ class MultiStreamingDecoder : public StreamingDecoder {
646
637
*/
647
638
MimeDetector& mimeDetector () { return mime_detector; }
648
639
640
+ /* *
641
+ * @brief Sets an external MIME source for format detection
642
+ *
643
+ * Provides an alternative to automatic MIME detection by allowing an external
644
+ * source to provide the MIME type information. This is particularly useful
645
+ * when the MIME type is already known from other sources such as:
646
+ * - HTTP Content-Type headers
647
+ * - File extensions
648
+ * - Metadata from containers or playlists
649
+ * - User-specified format preferences
650
+ *
651
+ * When a MIME source is set, the automatic detection process (which requires
652
+ * reading and analyzing stream data) is bypassed, making the decoder
653
+ * initialization more efficient and faster.
654
+ *
655
+ * @param mimeSource Reference to a MimeSource object that provides the
656
+ * MIME type through its mime() method
657
+ *
658
+ * @note The MimeSource object must remain valid for the lifetime of this
659
+ * MultiStreamingDecoder instance, as only a reference is stored.
660
+ *
661
+ * @note Setting a MIME source takes precedence over automatic detection.
662
+ * To revert to automatic detection, the MIME source would need to
663
+ * return nullptr from its mime() method.
664
+ *
665
+ * @see MimeSource interface for implementing custom MIME providers
666
+ * @see selectDecoder() for how MIME type detection and selection works
667
+ *
668
+ * @since This feature allows integration with external metadata sources
669
+ */
670
+ void setMimeSource (MimeSource& mimeSource) { p_mime_source = &mimeSource; }
671
+
649
672
protected:
650
673
/* *
651
674
* @brief Simple buffered stream that prefixes buffered data before reading
@@ -811,6 +834,91 @@ class MultiStreamingDecoder : public StreamingDecoder {
811
834
Vector<uint8_t > detection_buffer{0 }; // /< Buffer for format detection data
812
835
bool is_first = true ; // /< Flag for first copy() call
813
836
const char * selected_mime = nullptr ; // /< MIME type that was selected
837
+ MimeSource* p_mime_source =
838
+ nullptr ; // /< Optional MIME source for custom logic
839
+
840
+ /* *
841
+ * @brief Automatically detects MIME type and selects appropriate decoder
842
+ *
843
+ * This method performs automatic format detection and decoder selection when
844
+ * no decoder is currently active. It supports two modes of operation:
845
+ * 1. External MIME source - Uses a provided MimeSource for format information
846
+ * 2. Auto-detection - Analyzes stream content to determine the audio format
847
+ *
848
+ * The method reads a small sample of data (80 bytes) from the input stream
849
+ * for format detection, then preserves this data in a buffered stream so it
850
+ * remains available to the selected decoder. This ensures no audio data is
851
+ * lost during the detection process.
852
+ *
853
+ * @note This method is automatically called by copy() on the first invocation.
854
+ * Subsequent calls will return immediately if a decoder is already selected.
855
+ *
856
+ * @note The detection data is preserved using BufferedPrefixStream, allowing
857
+ * the selected decoder to process the complete stream including the bytes
858
+ * used for format identification.
859
+ *
860
+ * @return true if a decoder was successfully selected and initialized, or if
861
+ * a decoder was already active; false if MIME detection failed or no
862
+ * matching decoder was found
863
+ *
864
+ * @see selectDecoder(const char* mime) for explicit decoder selection
865
+ * @see setMimeSource() for providing external MIME type information
866
+ * @see MimeDetector for details on automatic format detection
867
+ */
868
+ bool selectDecoder () {
869
+ // Only perform MIME detection and decoder selection if no decoder is active yet
870
+ // This prevents re-detection on subsequent calls during the same stream
871
+ if (actual_decoder.decoder == nullptr ) {
872
+ const char * mime = nullptr ;
873
+
874
+ // Two methods for MIME type determination: external source or auto-detection
875
+ if (p_mime_source) {
876
+ // Option 1: Use externally provided MIME source (e.g., from HTTP headers)
877
+ // This is more efficient as it avoids reading and analyzing stream data
878
+ mime = p_mime_source->mime ();
879
+ } else {
880
+ // Option 2: Auto-detect MIME type by analyzing stream content
881
+ // This requires reading a sample of data to identify the format
882
+
883
+ // Allocate buffer for MIME detection sample (80 bytes is typically sufficient
884
+ // for most audio format headers to be identified)
885
+ detection_buffer.resize (80 );
886
+ size_t bytesRead = readBytes (detection_buffer.data (), detection_buffer.size ());
887
+
888
+ // If no data is available, we cannot proceed with detection
889
+ if (bytesRead == 0 ) return false ;
890
+
891
+ // Preserve the detection data for the selected decoder by setting up
892
+ // the buffered stream. This ensures the data used for detection
893
+ // is not lost and will be available to the decoder
894
+ buffered_stream.setBuffer (detection_buffer.data (), bytesRead);
895
+
896
+ // Feed the sample data to the MIME detector for format analysis
897
+ // The detector examines file headers, magic numbers, etc.
898
+ mime_detector.write (detection_buffer.data (), bytesRead);
899
+ mime = mime_detector.mime ();
900
+ }
901
+
902
+ // Process the detected/provided MIME type
903
+ if (mime != nullptr ) {
904
+ LOGI (" mime from mime_detector: %s" , mime);
905
+
906
+ // Delegate to the overloaded selectDecoder(mime) method to find
907
+ // and initialize the appropriate decoder for this MIME type
908
+ if (!selectDecoder (mime)) {
909
+ LOGE (" The decoder could not be found for %s" , mime);
910
+ return false ; // No registered decoder can handle this format
911
+ }
912
+ } else {
913
+ // MIME detection failed - format is unknown or unsupported
914
+ LOGE (" Could not determine mime type" );
915
+ return false ;
916
+ }
917
+ }
918
+
919
+ // Success: either decoder was already selected or selection completed successfully
920
+ return true ;
921
+ }
814
922
815
923
/* *
816
924
* @brief Reads bytes from the input stream
0 commit comments