Interface AudioSourceProvider


public interface AudioSourceProvider
Service Provider Interface (SPI) for format-specific audio decoders. Each format module (for example dtmf-io-wav, dtmf-io-mp3, or a future FLAC/OGG module) ships exactly one implementation of this interface and registers it via META-INF/services/com.tino1b2be.dtmf.io.AudioSourceProvider (Requirement 9.2, 10.2). The AudioSources facade discovers every registered provider through ServiceLoader, asks each one to score the input with canOpen(Path) or canOpen(InputStream, String), and dispatches open(Path) or open(InputStream, String) on the provider with the strictly greatest score (Requirement 5.6).

Implementation requirements

Public no-arg constructor. Implementations must expose a public no-argument constructor so that ServiceLoader can instantiate them reflectively. Providers with no state are fine; stateful providers must make their own-state instantiation cheap, because ServiceLoader creates one instance per classloader and caches it for the lifetime of the loader.

Thread safety. Individual provider instances may be called concurrently by AudioSources from multiple threads. Implementations should keep canOpen(Path), canOpen(InputStream, String), open(Path), and open(InputStream, String) stateless (allocating fresh FileChannel or InputStream objects per call). Returned AudioSource instances are not required to be thread-safe — see AudioSource for that contract.

SPI Priority Score

The score returned by canOpen(...) is an integer in the closed range [0, 100] or the sentinel value -1 (Requirement 4.4, 4.5). Higher scores indicate stronger confidence that this provider can open the input:

  • 100 — the input's magic bytes match unambiguously (for example "RIFF" ... "WAVE" at offset 0 for WAV).
  • 099 — a weaker signal, typically because the provider's identifying pattern is heuristic rather than magic (for example an MPEG audio frame sync pattern).
  • -1 — the provider is not applicable to this input and must not be selected, even if it is the only provider on the classpath. A -1 return is also how a provider declines when asked about a non-markable stream it cannot read ahead on (see canOpen(InputStream, String) below).

When two providers return the same positive score, AudioSources breaks the tie with priority() (higher wins; Requirement 4.3).

Stream ownership

open(Path) and open(InputStream, String) never close caller-supplied InputStreams (Requirement 4.10). The AudioSource.close() method on the returned source closes only the resources the provider opened itself — for example a FileChannel the provider opened on a Path, or a BufferedInputStream the provider wrapped internally around a caller stream. When the caller supplies the InputStream, closing the returned AudioSource must not close that caller stream; ownership stays with whoever opened it.

Null parameters

Every parameter of every SPI method is non-null unless explicitly documented otherwise. Implementations must throw NullPointerException identifying the offending parameter when a non-null argument is null (Requirement 4.11). The hint parameter on canOpen(InputStream, String) and open(InputStream, String) is the one exception: it is explicitly nullable and implementations must tolerate null without throwing.

Since:
2.1.0
  • Method Details

    • formatName

      String formatName()
      Human-readable identifier for this provider. Returned values must be non-null and non-empty (Requirement 4.2). Typical values are short uppercase tags such as "WAV" or "MP3"; AudioSources.registeredFormats() exposes the list of every registered provider's name in discovery order, and UnsupportedAudioFormatException.providerScores() keys its score map on this value, so the returned string should be stable across JVM runs and unique across the providers a caller expects to have on the classpath at the same time.
      Returns:
      this provider's format name; never null, never empty
    • priority

      default int priority()
      Tie-breaker priority used when two providers return the same positive score from canOpen(Path) or canOpen(InputStream, String) (Requirement 4.3). Higher values win. The default implementation returns 0, which is the right answer for every built-in provider; a format module only overrides this when it ships two providers for overlapping inputs and wants to express a preferred order.
      Returns:
      this provider's tie-break priority
    • canOpen

      int canOpen(Path path) throws IOException
      Score this provider's confidence that it can open path (Requirement 4.4). Implementations typically open a FileChannel or a short-lived InputStream on the file, read a small prefix (the WAV provider reads 12 bytes; the MP3 provider reads up to 10 KiB after any ID3v2 tag), score against that prefix, and close the channel or stream before returning. The file itself is not opened for reading beyond the prefix — that happens in open(Path).

      The returned value is an SPI Priority Score: an integer in [0, 100] or -1 (see the class-level "SPI Priority Score" section). Implementations must not return any other value; AudioSources does not validate the score range and out-of-range scores will break tie-breaking.

      If the file cannot be read at all (for example a NoSuchFileException, a permission error, or a disk-level I/O failure), this method propagates IOException. The AudioSources facade catches those exceptions, treats the provider as having returned -1, logs a warning, and continues scoring the remaining providers (Requirement 5.9) — so implementations do not need to defend against "the file is unreadable" themselves.

      Parameters:
      path - absolute or relative path to the file to score; must be non-null
      Returns:
      an SPI Priority Score in [0, 100], or -1 if this provider is not applicable
      Throws:
      IOException - if the file cannot be read to score it
      NullPointerException - if path is null
    • canOpen

      int canOpen(InputStream stream, String hint) throws IOException
      Score this provider's confidence that it can open stream (Requirement 4.5). The hint parameter is an optional caller-supplied file name, URL path segment, MIME type, or null; providers that cannot read ahead on a non-markable stream may use the hint as a fallback signal, but content-based scoring always takes precedence over any hint on a markable stream.

      Mark/reset contract. When stream supports mark/reset, implementations call stream.mark(readLimit), read a header prefix, score against that prefix, and call stream.reset() before returning — leaving the stream positioned exactly as it was on entry so that AudioSources can pass the same stream to subsequent providers and eventually to open(InputStream, String) (Requirement 4.6).

      Non-markable streams. If stream.markSupported() is false, implementations return -1 without consuming any bytes (Requirement 4.7). Reading from a non-markable stream would leave it in a consumed state that neither the caller nor any subsequent provider can recover from. In practice AudioSources.open(InputStream, String) guarantees the stream it forwards is always markable by wrapping non-markable inputs in a BufferedInputStream sized for at least 16 KiB of header inspection (Requirement 5.12), so providers see a markable stream every time they are called through the facade; this branch exists for direct callers who invoke a provider without going through AudioSources.

      The returned value is an SPI Priority Score: an integer in [0, 100] or -1 — see canOpen(Path) and the class-level "SPI Priority Score" section.

      Parameters:
      stream - the stream to score; must be non-null
      hint - an optional caller-supplied hint (file name, URL path segment, MIME type); may be null
      Returns:
      an SPI Priority Score in [0, 100], or -1 if this provider is not applicable
      Throws:
      IOException - on I/O failure while reading the header prefix
      NullPointerException - if stream is null
    • open

      AudioSource open(Path path) throws IOException
      Open an AudioSource for path (Requirement 4.8). Callers typically reach this method indirectly through AudioSources.open(Path), which first picks the winning provider by scoring and then delegates to its open(...) — but providers may be invoked directly when a caller already knows which format they have.

      The returned AudioSource owns the underlying file handle or stream the provider opened on path: AudioSource.close() closes that handle and releases any decoder resources. Caller-supplied streams are never involved on this overload, so there is nothing for the provider to avoid closing.

      A non-negative score from canOpen(Path) does not guarantee open(Path) will succeed: the header prefix may match but a later structural defect (for example a WAV fmt chunk declaring a compressed encoding such as μ-law; Requirement 9.10) only surfaces during the full parse. In that case implementations throw UnsupportedAudioFormatException identifying the defect; real I/O failures propagate as plain IOException.

      Parameters:
      path - file to open; must be non-null
      Returns:
      an opened AudioSource
      Throws:
      UnsupportedAudioFormatException - if the file's header matched but the full parse rejected it
      IOException - on any other I/O failure
      NullPointerException - if path is null
    • open

      AudioSource open(InputStream stream, String hint) throws IOException
      Open an AudioSource for a markable InputStream (Requirement 4.9). The hint parameter has the same semantics as on canOpen(InputStream, String): file name, URL path segment, MIME type, or null.

      This overload never closes stream. Ownership stays with the caller (Requirement 4.10); the returned AudioSource's AudioSource.close() only closes resources the provider opened internally (for example a BufferedInputStream wrapper, decoder buffers, or a spawned worker). If the caller needs the underlying stream closed, they must close it themselves after closing the AudioSource.

      As with open(Path), a prior non-negative canOpen(InputStream, String) score does not guarantee success here: structural defects past the header surface as UnsupportedAudioFormatException during the full parse.

      Parameters:
      stream - markable stream to open; must be non-null
      hint - an optional caller-supplied hint; may be null
      Returns:
      an opened AudioSource
      Throws:
      UnsupportedAudioFormatException - if the stream's header matched but the full parse rejected it
      IOException - on any other I/O failure
      NullPointerException - if stream is null