Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ name: Publish to pub.dev
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
- "v[0-9][0-9]*.[0-9][0-9]*.[0-9][0-9]*"
- "v[0-9][0-9]*.[0-9][0-9]*.[0-9][0-9]*-*"

permissions:
contents: read
Expand Down
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,35 @@
## 0.2.0-beta.3 - 2026-02-16

### Changed

- Replaced `MtAudioSource` sealed class (`MtSingleSource`, `MtPlaylistSource`, `MtLiveSource`) with two explicit methods on `MtAudioPlayer`: `setAudioItem(MtAudioItem)` and `setPlaylist(List<MtAudioItem>, {int initialIndex})`.
- Simplified internal playback state stream and added `.distinct()` to prevent redundant emissions.
Comment on lines +1 to +6

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CHANGELOG introduces a 0.2.0-beta.3 entry, but the package pubspec.yaml still declares version: 0.2.0-beta.2. Please align the changelog entry with the actual package version being released (or bump pubspec.yaml accordingly) so tags/publishing and user expectations match.

Copilot uses AI. Check for mistakes.

### Fixed

- `ffRewindInterval` from `MtAudioPlayerConfig` is now correctly forwarded to system media controls (lock screen, notification, Android Auto, CarPlay). Previously, system-level fast-forward/rewind buttons used default intervals instead of the configured value.

### Breaking changes

- **Removed** `MtAudioSource`, `MtSingleSource`, `MtPlaylistSource`, and `MtLiveSource` classes.
- **Removed** `MtAudioPlayer.setSource(MtAudioSource)`.
- **Added** `MtAudioPlayer.setAudioItem(MtAudioItem)` -- replaces `setSource(MtSingleSource(...))` and `setSource(MtLiveSource(...))`.
- **Added** `MtAudioPlayer.setPlaylist(List<MtAudioItem>, {int initialIndex})` -- replaces `setSource(MtPlaylistSource(...))`.

#### Migration

```dart
// Before
await player.setSource(MtSingleSource(item: track));
await player.setSource(MtLiveSource(item: stream));
await player.setSource(MtPlaylistSource(items: tracks, initialIndex: 2));

// After
await player.setAudioItem(track);
await player.setAudioItem(stream);
await player.setPlaylist(tracks, initialIndex: 2);
```

## 0.2.0-beta.2 - 2026-02-13

Reclassified `mt_audio` as a beta release while testing is ongoing.
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ There are no tests, code generation, or build_runner steps in this module.

5. **CarPlay uses `mt_carplay`**: `MtCarPlayHandler` manages the connection lifecycle, navigation stack (max 5 deep), template rendering (list + grid + tab bar), and automatic playback state sync via player streams.

6. **Sealed class for audio sources**: `MtAudioSource` is a sealed class with three variants: `MtSingleSource`, `MtPlaylistSource`, `MtLiveSource`.
6. **Two-method source API**: Audio sources are set via `setAudioItem(MtAudioItem)` for single items (both regular tracks and live streams) and `setPlaylist(List<MtAudioItem>, {initialIndex})` for playlists. The live/non-live distinction is encoded in `MtAudioItem.isLive`.

7. **`MtAudioItem` ↔ `MediaItem` conversion**: `MtAudioItem` stores URI, headers, and `isLive` flag in the `extras` map of `MediaItem` for round-trip conversion. The `uri` field is stored as `extras['uri']`, not `MediaItem.id`.

Expand Down Expand Up @@ -92,7 +92,7 @@ Queue state tracks `shuffleIndices` separately. `MtAudioHandler.getQueueIndex()`

- Uses `very_good_analysis` linter rules with relaxed settings: no 80-char line limit, no public API docs requirement, trailing commas preserved
- All model classes extend `Equatable`
- Sealed classes for type-safe variants (`MtAudioSource`, `MtCarPlayItem`)
- Sealed classes for type-safe variants (`MtCarPlayItem`)
- All classes prefixed with `Mt` (Mobitouch namespace)
- Widgets take `MtAudioPlayer player` as required parameter
- The example app uses a simple `InheritedWidget` (`PlayerProvider`) — not Riverpod
68 changes: 31 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
![Stability](https://img.shields.io/badge/stability-beta-orange)
![License](https://img.shields.io/badge/license-MIT-green)

A beta, streams-based audio module for Flutter. Provides background playback, system notifications, queue management, and first-class **Android Auto** & **Apple CarPlay** support -- all behind a single facade class and zero external state management dependencies.
A stream-based audio module for Flutter. Provides background playback, system notifications, queue management, and first-class **Android Auto** & **Apple CarPlay** support -- all behind a single facade class and zero external state management dependencies.

This package reduces implementation overhead when combining packages such as `just_audio` and `audio_service`. It provides a simple wrapper API that captures our long-standing Flutter audio expertise in a single dependency.

<p align="center">
<img src="assets/home.png" width="220" alt="Now Playing" />
Expand All @@ -19,16 +21,16 @@ A beta, streams-based audio module for Flutter. Provides background playback, sy
## Features

- **Background playback** with lock screen controls and media notifications
- **Queue management** -- add, insert, remove, reorder, shuffle
- **Queue management** - add, insert, remove, reorder, shuffle
- **Seek forward / backward** with configurable intervals
- **Playback speed** control (0.5x -- 2.0x)
- **Repeat modes** -- off, one, all
- **Playback speed** control (0.5x - 2.0x)
- **Repeat modes** - off, one, all
- **Live stream** support with ICY metadata
- **Android Auto** integration via delegate pattern
- **Apple CarPlay** integration with list, grid, and tab bar templates
- **Pre-built widgets** -- seek bar, play/pause, skip, speed selector, queue list, artwork, now playing info, and a full player builder
- **State management agnostic** -- pure Dart `BehaviorSubject` streams with synchronous getters
- **Audio session handling** -- automatic interruption and becoming-noisy management
- **Pre-built widgets** - seek bar, play/pause, skip, speed selector, queue list, artwork, now playing info, and a full player builder
- **State management agnostic** - `rxdart` `BehaviorSubject` streams with synchronous getters
- **Audio session handling** - automatic interruption and becoming-noisy management

## Core Stack

Expand All @@ -37,7 +39,7 @@ A beta, streams-based audio module for Flutter. Provides background playback, sy
| [just_audio](https://pub.dev/packages/just_audio) | ^0.10.5 | Audio playback engine |
| [audio_service](https://pub.dev/packages/audio_service) | ^0.18.18 | Background playback & notifications |
| [audio_session](https://pub.dev/packages/audio_session) | ^0.2.2 | Audio session & interruption handling |
| [mt_carplay](https://pub.dev/packages/mt_carplay) | custom fork | Apple CarPlay integration |
| [mt_carplay](https://pub.dev/packages/mt_carplay) | ^1.2.11 | Apple CarPlay integration (fork) |
| [rxdart](https://pub.dev/packages/rxdart) | ^0.28.0 | Stream utilities & BehaviorSubjects |
| [equatable](https://pub.dev/packages/equatable) | ^2.0.8 | Value equality for models |

Expand All @@ -49,7 +51,7 @@ Add to your `pubspec.yaml`:

```yaml
dependencies:
mt_audio: ^0.2.0-beta.1
mt_audio: ^0.2.0-beta.3
Comment on lines 52 to +54

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

README now instructs installing mt_audio: ^0.2.0-beta.3, but pubspec.yaml in this repo still declares version: 0.2.0-beta.2. Either bump the package version (and regenerate locks) or keep the README version aligned with the actual package version to avoid publishing/install confusion.

Copilot uses AI. Check for mistakes.
```

---
Expand Down Expand Up @@ -266,40 +268,32 @@ final player = await MtAudioPlayer.init(

### Set an audio source

`MtAudioSource` is a sealed class with three variants:

```dart
// Single track
await player.setSource(
MtSingleSource(
item: MtAudioItem(
id: '1',
uri: Uri.parse('https://example.com/audio.mp3'),
title: 'My Song',
artist: 'Artist Name',
artworkUri: Uri.parse('https://example.com/artwork.jpg'),
duration: Duration(minutes: 3, seconds: 45),
),
await player.setAudioItem(
MtAudioItem(
id: '1',
uri: Uri.parse('https://example.com/audio.mp3'),
title: 'My Song',
artist: 'Artist Name',
artworkUri: Uri.parse('https://example.com/artwork.jpg'),
duration: Duration(minutes: 3, seconds: 45),
),
);

// Playlist
await player.setSource(
MtPlaylistSource(
items: [item1, item2, item3],
initialIndex: 0,
),
await player.setPlaylist(
[item1, item2, item3],
initialIndex: 0,
);

// Live stream
await player.setSource(
MtLiveSource(
item: MtAudioItem(
id: 'live',
uri: Uri.parse('https://example.com/stream'),
title: 'Live Radio',
isLive: true,
),
// Live stream (isLive flag on the item controls live behavior)
await player.setAudioItem(
MtAudioItem(
id: 'live',
uri: Uri.parse('https://example.com/stream'),
title: 'Live Radio',
isLive: true,
),
);
```
Expand Down Expand Up @@ -550,7 +544,7 @@ class MyAndroidAutoDelegate implements MtAndroidAutoDelegate {
@override
Future<void> onPlayFromMediaId(String mediaId) async {
final track = await repository.getTrackById(mediaId);
await player.setSource(MtSingleSource(item: track));
await player.setAudioItem(track);
await player.play();
}

Expand Down Expand Up @@ -634,7 +628,7 @@ class MyCarPlayDelegate extends MtCarPlayDelegate {
@override
Future<void> onPlayFromMediaId(String mediaId) async {
final track = await repository.getTrackById(mediaId);
await player.setSource(MtSingleSource(item: track));
await player.setAudioItem(track);
await player.play();
}
}
Expand Down
15 changes: 6 additions & 9 deletions example/lib/pages/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class _HomePageState extends State<HomePage> {

// Load source button
FilledButton.icon(
onPressed: () => _loadSource(player),
onPressed: () => _loadAudioSource(player),
icon: const Icon(Icons.play_circle_outline),
label: Text(
_sourceType == SourceType.playlist
Expand Down Expand Up @@ -289,22 +289,19 @@ class _HomePageState extends State<HomePage> {
);
}

Future<void> _loadSource(MtAudioPlayer player) async {
MtAudioSource source;

Future<void> _loadAudioSource(MtAudioPlayer player) async {
switch (_sourceType) {
case SourceType.single:
source = MtSingleSource(item: sampleTracks[_selectedTrackIndex]);
await player.setAudioItem(sampleTracks[_selectedTrackIndex]);
case SourceType.playlist:
source = MtPlaylistSource(
items: sampleTracks,
await player.setPlaylist(
sampleTracks,
initialIndex: _selectedTrackIndex,
);
case SourceType.live:
source = MtLiveSource(item: liveStreams[_selectedLiveIndex]);
await player.setAudioItem(liveStreams[_selectedLiveIndex]);
}

await player.setSource(source);
await player.play();
}
}
Expand Down
4 changes: 1 addition & 3 deletions example/lib/pages/queue_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ class QueuePage extends StatelessWidget {
Expanded(
child: FilledButton.icon(
onPressed: () async {
await player.setSource(
MtPlaylistSource(items: sampleTracks),
);
await player.setPlaylist(sampleTracks);
await player.play();
},
icon: const Icon(Icons.playlist_play),
Expand Down
2 changes: 1 addition & 1 deletion example/lib/providers/example_android_auto_delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class ExampleAndroidAutoDelegate implements MtAndroidAutoDelegate {
(i) => i.id == mediaId,
orElse: () => throw ArgumentError('Unknown media ID: $mediaId'),
);
await player.setSource(MtSingleSource(item: item));
await player.setAudioItem(item);
await player.play();
}

Expand Down
10 changes: 4 additions & 6 deletions example/lib/providers/example_carplay_delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -202,15 +202,13 @@ class ExampleCarPlayDelegate implements MtCarPlayDelegate {
if (playlist != null && playlist.length > 1) {
// Queue playback for playlist items
final initialIndex = playlist.indexWhere((i) => i.id == mediaId);
await player.setSource(
MtPlaylistSource(
items: playlist,
initialIndex: initialIndex >= 0 ? initialIndex : 0,
),
await player.setPlaylist(
playlist,
initialIndex: initialIndex >= 0 ? initialIndex : 0,
);
} else {
// Single item playback
await player.setSource(MtSingleSource(item: item));
await player.setAudioItem(item);
}

await player.play();
Expand Down
6 changes: 3 additions & 3 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ packages:
path: ".."
relative: true
source: path
version: "0.1.0"
version: "0.2.0-beta.2"
mt_carplay:
dependency: transitive
description:
Expand Down Expand Up @@ -488,10 +488,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "19a78f63e83d3a61f00826d09bc2f60e191bf3504183c001262be6ac75589fb8"
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
url: "https://pub.dev"
source: hosted
version: "0.7.8"
version: "0.7.9"
typed_data:
dependency: transitive
description:
Expand Down
1 change: 0 additions & 1 deletion lib/mt_audio.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export 'src/carplay/mt_carplay_item.dart';
// Models
export 'src/models/mt_audio_error.dart';
export 'src/models/mt_audio_item.dart';
export 'src/models/mt_audio_source.dart';
export 'src/models/mt_media_library_item.dart';
export 'src/models/mt_playback_state.dart';
export 'src/models/mt_position_state.dart';
Expand Down
2 changes: 1 addition & 1 deletion lib/src/android_auto/mt_android_auto_delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import 'package:mt_audio/src/models/mt_media_library_item.dart';
/// @override
/// Future<void> onPlayFromMediaId(String mediaId) async {
/// final track = await getTrack(mediaId);
/// await player.setSource(MtSingleSource(item: track));
/// await player.setAudioItem(track);
/// await player.play();
/// }
///
Expand Down
2 changes: 1 addition & 1 deletion lib/src/carplay/mt_carplay_delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class MtCarPlaySection {
/// @override
/// Future<void> onPlayFromMediaId(String mediaId) async {
/// final track = await getTrack(mediaId);
/// await player.setSource(MtSingleSource(item: track));
/// await player.setAudioItem(track);
/// await player.play();
/// }
/// }
Expand Down
Loading