Class AudioSources

java.lang.Object
com.tino1b2be.dtmf.io.AudioSources

public final class AudioSources extends Object
Entry point for opening audio from any source. Discovers AudioSourceProvider instances via ServiceLoader, runs content-based scoring against every registered provider, and returns an AudioSource produced by the provider with the strictly greatest SPI Priority Score. Ties on score are broken by priority() (Requirement 5.6).

AudioSources is the dtmf-io module's façade over the provider SPI: callers hand in a Path, a markable InputStream (plus optional file-name / URL-segment / MIME-type hint), or a URL, and get back an AudioSource without having to know which format module is on the classpath. Non-markable streams are transparently wrapped in a BufferedInputStream sized to 16384 bytes before scoring, so providers always see a markable stream they can rewind after reading the header prefix (Requirement 5.12, 11.2).

Dispatch

For every public open(...) call, the facade:

  1. Discovers providers via ServiceLoader.load(AudioSourceProvider.class, loader) on the context class loader (falling back to AudioSources's own class loader when the context loader is null), caching the result for the lifetime of the JVM (Requirement 5.5).
  2. Invokes canOpen(...) on every cached provider, catching IOException and logging it at WARNING before treating the provider as having returned -1 (Requirement 5.9).
  3. Picks the winner as the provider with the strictly greatest (score, priority()) lexicographic pair over all scores >= 0 (Requirement 5.6).
  4. Delegates the actual open to the winner's open(...) method.

Error handling

If every registered provider returns -1 (including the case where each one's canOpen threw an IOException), this facade throws UnsupportedAudioFormatException with UnsupportedAudioFormatException.providersConsulted() and UnsupportedAudioFormatException.providerScores() populated from the scoring loop (Requirements 5.7, 6.4, 6.5, 6.6). If no providers are registered at all, the thrown exception carries a distinct message pointing the caller at the dtmf-io-wav / dtmf-io-mp3 modules (Requirement 5.8).

File-not-found special case. On open(Path), if every provider returned -1 because each one's canOpen(Path) threw an IOException, the facade re-throws the first captured IOException instead of throwing UnsupportedAudioFormatException. This preserves NoSuchFileException (and its siblings — permission errors, access-denied, etc.) as distinct error signals rather than masquerading as "format not supported" (Requirement 12.3, 12.4). The special case does not apply to open(InputStream, String) because an IOException from canOpen(InputStream, ...) is a read-failure during header inspection, not a missing-source signal.

Thread safety

AudioSources is thread-safe. The provider cache is a volatile List<AudioSourceProvider> field populated under double-checked lazy initialization guarded by the class monitor; the ServiceLoader iterator — which is not itself thread-safe — is consumed exclusively inside the synchronized block. The cache is computed once per JVM and returned immutably for every subsequent call. Provider instances themselves may be called concurrently from multiple threads; see AudioSourceProvider for that contract.

Since:
2.1.0
See Also:
  • Method Details

    • open

      public static AudioSource open(Path path) throws IOException
      Open an AudioSource for the file at path by scoring every registered AudioSourceProvider against it and dispatching to the winner (Requirement 5.2).

      See the class Javadoc for the dispatch algorithm and error handling — including the special case where the sole error signal from every provider is an IOException (the first such exception is re-thrown verbatim to preserve NoSuchFileException and permission errors).

      Parameters:
      path - the file to open; must be non-null
      Returns:
      an opened AudioSource produced by the winning provider
      Throws:
      UnsupportedAudioFormatException - if no provider is applicable, or if no providers are registered at all
      IOException - on any other I/O failure, including the pass-through of a captured IOException when every provider rejected path by throwing one
      NullPointerException - if path is null
    • open

      public static AudioSource open(InputStream stream, String hint) throws IOException
      Open an AudioSource for the given InputStream, scoring every registered provider against it and dispatching to the winner (Requirement 5.3).

      The optional hint argument — a file name, URL path segment, or MIME type — is forwarded verbatim to every provider's canOpen and (on dispatch) open methods. Providers may use it as a fallback signal when content-based detection is ambiguous or when the stream is not markable; content-based scoring always takes precedence on a markable stream.

      If stream does not support mark/reset, it is wrapped in a BufferedInputStream sized to at least 16384 bytes before being forwarded to providers (Requirement 5.12, 11.2). Providers can therefore always assume the stream they receive is markable.

      The returned AudioSource does not close stream when its own AudioSource.close() is invoked; ownership of the caller-supplied stream stays with the caller (Requirement 4.10). If the facade wrapped the stream in a BufferedInputStream, that wrapper is also not closed on the caller's behalf.

      Parameters:
      stream - markable (or wrappable) stream to open; must be non-null
      hint - optional caller-supplied hint (file name, URL path segment, or MIME type); may be null
      Returns:
      an opened AudioSource produced by the winning provider
      Throws:
      UnsupportedAudioFormatException - if no provider is applicable, or if no providers are registered at all
      IOException - on any other I/O failure
      NullPointerException - if stream is null
    • open

      public static AudioSource open(URL url) throws IOException
      Open an AudioSource for the resource at url. Opens the URL via url.openStream(), derives the hint from the last '/'-separated segment of url.getPath(), and delegates to open(InputStream, String) (Requirement 5.4, 11.1).

      If open(InputStream, String) throws, the stream opened by url.openStream() is closed before the exception propagates so the underlying URL connection does not leak (Requirement 11.4). On a successful return, the stream's lifecycle is governed by the returned AudioSource: the caller should close the AudioSource to release the connection.

      Parameters:
      url - the URL to open; must be non-null
      Returns:
      an opened AudioSource produced by the winning provider
      Throws:
      UnsupportedAudioFormatException - if no provider is applicable, or if no providers are registered at all
      IOException - on any other I/O failure, including URL.openStream() failing to connect
      NullPointerException - if url is null
    • registeredFormats

      public static List<String> registeredFormats()
      Names of every loaded AudioSourceProvider in ServiceLoader discovery order (Requirement 5.11). Provider discovery is cached, so repeated calls return the same list.
      Returns:
      immutable list of provider format names in discovery order