Media Player Queue Enhancement
Enhanced AndroidMediaPlayer with queue support for multiple audio tracks, playback callbacks, and speech marks integration. Designed for AWS Polly-generated MP3s with timing data for word-by-word synchronization.
Overview
I’ve enhanced the AndroidMediaPlayer to support queuing multiple audio tracks with playback callbacks, specifically designed for demos using AWS Polly-generated MP3s and speech marks.
Files Created
shared/src/commonMain/kotlin/io/github/bsautner/krill/media/AudioTrack.kt- Data models:
AudioTrack,SpeechMark,TrackInfo - Interface:
TrackPlaybackListenerfor playback callbacks
- Data models:
shared/src/commonMain/kotlin/io/github/bsautner/krill/media/NarrationDemo.kt- Example code showing how to use the new queue functionality
- Demonstrates both simple and advanced usage patterns
docs/MEDIA_PLAYER_QUEUE.md- Comprehensive documentation
- Usage examples
- API reference
- Implementation details
Files Modified
shared/src/commonMain/kotlin/io/github/bsautner/krill/Platform.kt- Updated
MediaPlayerinterface to add:suspend fun playQueue(tracks: List<AudioTrack>, listener: TrackPlaybackListener?)fun stop()fun clearQueue()
- Updated
shared/src/androidMain/kotlin/io/github/bsautner/krill/media/AndroidMediaPlayer.kt- Implemented the new queue functionality
- Added track transition detection via ExoPlayer listeners
- Fetches and parses marks.json files
- Calculates duration from speech marks
- Provides callbacks for track start, complete, and errors
Key Features
✅ Queue Support: Add multiple MP3s with their marks.json URLs
✅ Track Callbacks: Get notified when each track starts and completes
✅ Duration Info: Automatically calculated from speech marks
✅ Speech Marks Access: Full access to word and sentence timing data
✅ Error Handling: Graceful error handling with callbacks
✅ Simple API: Can be used with or without listener
Usage Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
suspend fun playDemo() {
val tracks = listOf(
AudioTrack(
mp3Url = "https://cdn.example.com/intro.mp3",
marksJsonUrl = "https://cdn.example.com/intro.marks.json"
),
AudioTrack(
mp3Url = "https://cdn.example.com/features.mp3",
marksJsonUrl = "https://cdn.example.com/features.marks.json"
)
)
val listener = object : TrackPlaybackListener {
override fun onTrackStarted(trackIndex: Int, trackInfo: TrackInfo) {
Logger.d("Track $trackIndex started - ${trackInfo.durationMs}ms")
}
override fun onTrackCompleted(trackIndex: Int) {
Logger.d("Track $trackIndex done")
}
override fun onQueueCompleted() {
Logger.d("All done!")
}
override fun onError(trackIndex: Int, error: String) {
Logger.d("Error: $error")
}
}
mediaPlayer.playQueue(tracks, listener)
}
How It Works
sequenceDiagram
participant App
participant MediaPlayer
participant ExoPlayer
participant CDN
App->>MediaPlayer: playQueue(tracks, listener)
MediaPlayer->>CDN: Fetch marks.json files (parallel)
CDN-->>MediaPlayer: Speech marks data
MediaPlayer->>MediaPlayer: Calculate durations
MediaPlayer->>ExoPlayer: Queue all MP3s
ExoPlayer->>ExoPlayer: Start playback
loop For each track
ExoPlayer->>MediaPlayer: onPlaybackStateChanged(STATE_READY)
MediaPlayer->>App: onTrackStarted(index, trackInfo)
ExoPlayer->>MediaPlayer: onMediaItemTransition
MediaPlayer->>App: onTrackCompleted(index)
end
ExoPlayer->>MediaPlayer: onPlaybackStateChanged(STATE_ENDED)
MediaPlayer->>App: onQueueCompleted()
Architecture
- Queue Setup: Call
playQueue()with a list ofAudioTrackobjects - Marks Fetching: The player fetches all marks.json files in parallel
- Duration Calculation: Duration is calculated from the last mark’s timestamp
- Playback: ExoPlayer plays all tracks in sequence
- Callbacks:
onTrackStartedwhen each track begins (includes TrackInfo with marks and duration)onTrackCompletedwhen each track finishesonQueueCompletedwhen all tracks are doneonErrorif any errors occur
Next Steps
To use this in your demo:
- Upload your Polly-generated MP3s and marks.json files to a CDN or server
- Create
AudioTrackobjects with the URLs - Implement a
TrackPlaybackListenerto handle callbacks - Use speech marks for synchronized text display or animations