Conversation
- Updated installer version to 0.0.4. - Added support for online radio streaming, including URL input, recent station management, and live playback indicators. - Implemented podcast search and management functionality. - Enhanced UI with new navigation buttons for Radio and Podcasts. - Updated README to reflect new features and changes in application data structure.
- Added podcast search functionality via iTunes API. - Implemented subscription management, including saving and loading subscriptions. - Enhanced UI with new navigation for podcasts and updated episode tracking. - Updated README to include new podcast features and data structure changes. - Introduced .cursorindexingignore and .gitignore for SpecStory files.
Review Summary by QodoAdd radio streaming and podcast management features
WalkthroughsDescription• Added radio streaming with URL input, station management, and live playback • Implemented podcast search, subscription, and episode management features • Enhanced UI with Radio and Podcasts navigation tabs, replaced Visualizer/Media with "..." menu • Added WASAPI loopback capture for real-time spectrum visualization of live streams • Integrated iTunes API for podcast discovery and RSS feed parsing for episodes Diagramflowchart LR
A["Audio Player"] -->|"Stream URL"| B["Radio Streaming"]
B -->|"WASAPI Loopback"| C["Live Spectrum"]
D["iTunes API"] -->|"Search Query"| E["Podcast Discovery"]
E -->|"Feed URL"| F["Episode Fetching"]
F -->|"Play Episode"| A
B -->|"Save Station"| G["Settings Manager"]
E -->|"Subscribe"| G
F -->|"Mark Read"| G
File Changes1. Audiomatic/MainWindow.xaml.cs
|
Code Review by Qodo
1. Stream handlers accumulate
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (10)
📝 WalkthroughWalkthroughThe PR introduces radio streaming and podcast features to Audiomatic with new UI navigation views, a PodcastService for iTunes search and RSS episode parsing, MediaPlayer-based stream playback, WASAPI loopback capture for spectrum visualization during streaming, and local persistence for radio stations and podcast subscriptions. Changes
Sequence DiagramssequenceDiagram
participant User as User
participant UI as MainWindow (UI)
participant Radio as PlayRadioStreamAsync
participant APS as AudioPlayerService
participant MP as MediaPlayer
participant Spectrum as SpectrumAnalyzer
User->>UI: Enter radio URL + click Play
UI->>Radio: PlayRadioStreamAsync()
Radio->>APS: PlayStreamAsync(Uri)
APS->>MP: Create MediaPlaybackItem
APS->>MP: Wire MediaOpened/MediaFailed
APS->>MP: Subscribe BufferingChanged
APS->>MP: Play()
MP-->>APS: MediaOpened (IsStream=true)
APS->>Spectrum: StartLoopback()
Spectrum->>Spectrum: Enable WASAPI capture
APS-->>UI: PlaybackStarted
UI->>UI: UpdateNowPlaying (Radio)
UI->>Spectrum: GetSpectrum()
Spectrum->>Spectrum: GetLoopbackSpectrum()
Spectrum-->>UI: Spectrum data
UI->>UI: DrawVisualization()
sequenceDiagram
participant User as User
participant UI as MainWindow (UI)
participant PodService as PodcastService
participant iTunes as iTunes API
participant RSS as RSS Feed
participant APS as AudioPlayerService
User->>UI: Enter podcast search term
UI->>PodService: SearchAsync(query)
PodService->>iTunes: GET /search?term=...
iTunes-->>PodService: JSON results
PodService-->>UI: List[PodcastInfo]
UI->>UI: Display search results
User->>UI: Click Subscribe
UI->>PodService: SaveSubscriptions()
PodService->>PodService: Persist to podcasts.json
User->>UI: Select podcast
UI->>PodService: FetchEpisodesAsync(feedUrl)
PodService->>RSS: GET RSS feed
RSS-->>PodService: XML episodes
PodService-->>UI: List[PodcastEpisode]
UI->>UI: Display episodes
User->>UI: Click Play episode
UI->>APS: PlayStreamAsync(episodeAudioUrl)
APS-->>UI: PlaybackStarted
UI->>UI: UpdateNowPlaying (Podcast)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Tip CodeRabbit can suggest fixes for GitHub Check annotations.Configure the |
| // Wait for the source to open before playing to let MediaPlayer buffer | ||
| var tcs = new TaskCompletionSource<bool>(); | ||
| void onOpened(MediaPlayer mp, object args) | ||
| { | ||
| _mediaPlayer.MediaOpened -= onOpened; | ||
| tcs.TrySetResult(true); | ||
| } | ||
| void onFailed(MediaPlayer mp, MediaPlayerFailedEventArgs args) | ||
| { | ||
| _mediaPlayer.MediaFailed -= onFailed; | ||
| tcs.TrySetException(new Exception(args.ErrorMessage)); | ||
| } | ||
| _mediaPlayer.MediaOpened += onOpened; | ||
| _mediaPlayer.MediaFailed += onFailed; | ||
|
|
||
| // Subscribe to buffering events | ||
| var session = _mediaPlayer.PlaybackSession; | ||
| session.BufferingStarted += (_, _) => | ||
| _dispatcherQueue?.TryEnqueue(() => BufferingChanged?.Invoke(true)); | ||
| session.BufferingEnded += (_, _) => | ||
| _dispatcherQueue?.TryEnqueue(() => BufferingChanged?.Invoke(false)); |
There was a problem hiding this comment.
1. Stream handlers accumulate 🐞 Bug ⛯ Reliability
AudioPlayerService.PlayStreamAsync attaches MediaPlayer.MediaFailed and PlaybackSession.BufferingStarted/Ended handlers per stream play but never reliably detaches them, so repeated stream plays accumulate callbacks and retain stale TaskCompletionSource closures. This can cause duplicated BufferingChanged events, memory growth, and spurious callbacks after Stop().
Agent Prompt
### Issue description
`AudioPlayerService.PlayStreamAsync` subscribes to buffering and failure/open events every time a stream is played, but does not reliably unsubscribe them. Over multiple radio/podcast plays this will accumulate handlers, duplicate UI callbacks, and retain closures.
### Issue Context
- `onFailed` is never removed on the success path.
- `PlaybackSession.BufferingStarted/Ended` are subscribed per stream, and `Stop()` does not remove them.
### Fix Focus Areas
- Audiomatic/Services/AudioPlayerService.cs[190-259]
- Audiomatic/Services/AudioPlayerService.cs[289-307]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| _loopback.DataAvailable += (_, args) => | ||
| { | ||
| int channels = _loopback.WaveFormat.Channels; | ||
| int bytesPerSample = _loopback.WaveFormat.BitsPerSample / 8; | ||
| int sampleCount = args.BytesRecorded / bytesPerSample; | ||
|
|
||
| lock (_loopbackLock) | ||
| { | ||
| for (int i = 0; i < sampleCount; i += channels) | ||
| { | ||
| float sample = 0; | ||
| for (int c = 0; c < channels && (i + c) * bytesPerSample + bytesPerSample <= args.BytesRecorded; c++) | ||
| { | ||
| int offset = (i + c) * bytesPerSample; | ||
| sample += BitConverter.ToSingle(args.Buffer, offset); | ||
| } |
There was a problem hiding this comment.
2. Loopback sample format unsafe 🐞 Bug ⛯ Reliability
SpectrumAnalyzer.StartLoopback always decodes captured bytes using BitConverter.ToSingle, which assumes 32-bit float samples. If the loopback capture format is PCM (e.g., 16-bit), this will misinterpret data and can throw due to reading 4 bytes per sample.
Agent Prompt
### Issue description
Loopback capture decoding assumes 32-bit float samples (`BitConverter.ToSingle`) regardless of the actual `WasapiLoopbackCapture.WaveFormat`. Non-float formats will produce incorrect spectrum data and may throw.
### Issue Context
The code already reads `BitsPerSample` into `bytesPerSample` but does not use it to select a decoding path.
### Fix Focus Areas
- Audiomatic/Services/SpectrumAnalyzer.cs[88-129]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| if (!string.IsNullOrEmpty(podcast.ArtworkUrl)) | ||
| { | ||
| var img = new Image | ||
| { | ||
| Width = 44, Height = 44, | ||
| Stretch = Microsoft.UI.Xaml.Media.Stretch.UniformToFill | ||
| }; | ||
| img.Source = new BitmapImage(new Uri(podcast.ArtworkUrl)); | ||
| artGrid.Children.Add(img); |
There was a problem hiding this comment.
3. Artwork uri can crash 🐞 Bug ⛯ Reliability
Podcast list rendering constructs BitmapImage from new Uri(podcast.ArtworkUrl) without validation, so a malformed artwork URL can throw UriFormatException on the UI thread. The artwork URL string is taken directly from the iTunes API (and persisted) without validation.
Agent Prompt
### Issue description
Podcast artwork URLs are treated as trusted and passed into `new Uri(...)` during UI rendering, which can throw on malformed input and crash the app.
### Issue Context
Artwork URLs come from the network and may also be persisted and later reloaded.
### Fix Focus Areas
- Audiomatic/MainWindow.xaml.cs[2041-2075]
- Audiomatic/Services/PodcastService.cs[26-46]
- Audiomatic/MainWindow.xaml.cs[2394-2400]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Summary by CodeRabbit
Release Notes
New Features
Documentation
Chores