is_audio_playing
ProductionOctober 1, 2023
My first published Flutter package — a plugin that detects whether audio is currently playing on the device. Needed to be notified when audio was active so a particular operation could kill the noise. A walkthrough of package building and publishing across every major ecosystem: pub.dev, npm, SPM, Maven, and more.
Purpose
Needed to detect if audio was already playing on the device — if it was, a particular operation needed to silence it. No existing package solved this cleanly, so I built one. It became my first published Flutter package on pub.dev.
Stack
What I Learned
- Building a Flutter plugin (vs. a pure Dart package) means writing platform-specific native code. On iOS: Swift or Objective-C to query AVAudioSession for active audio. On Android: Kotlin or Java to check AudioManager's isMusicActive(). The Dart side defines the API. The native side does the actual work. Platform channels bridge them — same message-passing architecture as the Flutter plugins entry, but now you are the one building it.
- Publishing to pub.dev (Flutter/Dart's package registry) requires: a pubspec.yaml with name, version, description, and homepage. A LICENSE file. A CHANGELOG.md. A README with usage examples. Then: dart pub publish. The pub.dev scoring system grades your package on documentation, maintenance, platform support, and code quality. A high score means more visibility. A low score means obscurity.
- npm (Node.js/JavaScript) publishing: package.json with name, version, main entry point, and metadata. npm login, then npm publish. Scoped packages (@yourname/package) prevent name collisions. Semantic versioning is expected. The npm registry has 2+ million packages — discoverability depends on keywords, README quality, and download counts.
- Swift Package Manager (SPM) for iOS/macOS: a Package.swift manifest defining targets, dependencies, and platform requirements. Packages are distributed via Git repositories — SPM clones the repo and builds from source. No central registry (though the Swift Package Index serves as an unofficial one). Publishing means pushing a tagged release to a public Git repo.
- Maven Central (Java/Kotlin/Android): the most bureaucratic publishing process. Requires a POM file, GPG-signed artifacts, Javadoc, sources JAR, and a Sonatype OSSRH account. First-time setup involves creating a JIRA ticket, proving domain ownership, and configuring GPG keys. The payoff: Maven Central is the gold standard for JVM package distribution, trusted by enterprise teams worldwide.
- CocoaPods (iOS, pre-SPM era): a .podspec file describing the library, then pod trunk push. PyPI (Python): a setup.py or pyproject.toml, then twine upload. NuGet (.NET): a .nuspec or .csproj with package metadata, then dotnet nuget push. Crates.io (Rust): a Cargo.toml, then cargo publish. Every ecosystem has its own registry, its own manifest format, and its own publishing ritual. The concept is identical: declare metadata, upload artifact, make it searchable. The ceremony varies wildly.
Key Insights
- Publishing your first package to any registry is a career milestone that too few developers pursue. It transforms you from a consumer of the ecosystem into a contributor. Your name is on a package that other developers can install with a single command. That is a fundamentally different relationship with the community than just using other people's packages.
- The package publishing landscape mirrors the plugin landscape from the Flutter Plugins entry: every platform has the same problem (distribute reusable code), every platform solves it slightly differently, and learning the pattern once makes every subsequent registry feel familiar. pub.dev → npm → SPM → Maven → PyPI — the manifest format changes, the publishing command changes, but the mental model is: describe it, version it, ship it.
- Building a package forces you to think about API design in a way that building apps does not. Your app can have messy internals as long as the UI works. Your package's public API IS the product — every function name, every parameter, every return type is a contract with other developers. This discipline transfers back to app development and makes your internal APIs cleaner.
- The is_audio_playing package solved a problem small enough to be a single package but real enough that someone needed it. This is the sweet spot for first packages: narrow scope, clear use case, minimal API surface. Do not try to publish a framework as your first package. Publish a utility. Then iterate.
This post was composed through a conversation between Brett Owers and Claude Code (Anthropic). The content reflects Brett's recollection of each project and the lessons drawn from it. Some details may be approximate or omitted — the purpose is to paint an honest picture of a software engineer's development over time, not to serve as a precise historical record.