Skip to content

Add configurable playlists to console app#390

Open
Tapanila wants to merge 6 commits intomainfrom
feature/playlist
Open

Add configurable playlists to console app#390
Tapanila wants to merge 6 commits intomainfrom
feature/playlist

Conversation

@Tapanila
Copy link
Owner

No description provided.

RobertK66 and others added 2 commits September 20, 2025 10:13
My first take as basis for discussions.

I added micrososfts configuration.json package to parse
application.json. Hope this is compatible with the AOT and multi target
goal.

Not yet clear how to really structure the config. I tend to have a
List<playlists> to be able to have one level of 'category' like
"Classic-Cds", "Pop-Cds", "Web-Radios".

On the other hand I want to avoid to much keystrokes to navigate. So
That would be a Command line switch where I need to address somehow
which playlist to use. (by "cat"-"Name" or some - self generated unique
IDs?). Or I could start with a category only and directly reach the
according playlist selection menu....
Replaced hierarchical playlist management with a flat structure loaded from `userSettings.json`, removing reliance on `Microsoft.Extensions.Configuration` and `appsettings.json`.

Refactored `PlaylistService` to deserialize playlists directly from JSON, simplifying the API and removing unused classes (`Category`, `MenuNode`).

Updated `Playlist` to use `QueueItem[]` for better alignment with `Sharpcaster.Models.Queue`.

Simplified `QueueController` logic for playlist selection and casting.
Copilot AI review requested due to automatic review settings September 20, 2025 10:42
@coderabbitai
Copy link

coderabbitai bot commented Sep 20, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds playlist support across the console app: new models (Playlist, UserSettingsModel), a PlaylistService loading userSettings.json, DI wiring, CLI parsing for playlist IDs, command execution to play playlists, queue UI to load playlists, and a new “Back” option in media URL selection. Serialization context and tests updated accordingly.

Changes

Cohort / File(s) Summary
Controllers: Playlist & Media navigation
SharpCaster.Console/Controllers/QueueController.cs, SharpCaster.Console/Controllers/MediaController.cs
QueueController now depends on PlaylistService, adds CastPlaylistAsync, updates queue management UI, safer next-track handling, and richer queue display. MediaController adds a “Back” option to URL selection, returning early when chosen.
Flows & Menu integration
SharpCaster.Console/Flows/ApplicationFlows.cs
Main menu gains “Cast playlist”; conversion/display updated; invokes QueueController.CastPlaylistAsync.
CLI parsing & DI wiring
SharpCaster.Console/Program.cs, SharpCaster.Console/Models/CommandLineArgs.cs
CommandLineArgs adds PlaylistId. Parser now takes PlaylistService, recognizes playlist IDs, and defaults to “play”. Program registers PlaylistService and passes it to the parser.
Command execution
SharpCaster.Console/Services/CommandExecutor.cs
Constructor updated to include PlaylistService. Adds private PlayPlaylistAsync and extends play command to handle PlaylistId. Improves connection flow to join existing app or launch DMR.
Models & Serialization
SharpCaster.Console/Models/Playlist.cs, SharpCaster.Console/Models/UserSettingsModel.cs, SharpCaster.Console/Models/ConsoleJsonContext.cs
Adds Playlist and UserSettingsModel. ConsoleJsonContext now serializes Playlist, Playlist[], and UserSettingsModel for source-gen JSON.
Playlist service & data
SharpCaster.Console/Services/PlaylistService.cs, SharpCaster.Console/userSettings.json, SharpCaster.Console/SharpCaster.Console.csproj
New PlaylistService loads playlists from userSettings.json and validates IDs. Project copies userSettings.json to output. Adds initial playlists data file.
Tests & helpers
Sharpcaster.Test/LoggingTester.cs, Sharpcaster.Test/helper/TestHelper.cs
Updates expected MessageTypes to include PONG. Enhances test logs with receiver Name/Version and fixes array initialization in CreateTestCd.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User as User
  participant App as Console App
  participant Flow as ApplicationFlows
  participant QC as QueueController
  participant PS as PlaylistService
  participant Dev as DeviceService/Chromecast

  User->>App: Start / Open menu
  App->>Flow: Show main actions
  User-->>Flow: Select "Cast playlist"
  Flow->>QC: CastPlaylistAsync()
  QC->>PS: List/validate playlists
  PS-->>QC: Playlist selection/instance
  QC->>Dev: ConnectToDeviceQuietAsync()
  Dev-->>QC: Connected (join/launch app)
  QC->>Dev: QueueLoadAsync(playlist.QueueItems)
  Dev-->>QC: Load status
  QC-->>Flow: Result (success/error)
  Flow-->>User: Display outcome
Loading
sequenceDiagram
  autonumber
  participant User as User
  participant MC as MediaController
  participant UI as UIHelper

  User->>MC: CastMediaAsync()
  MC->>UI: Prompt URL options ["Back", ...]
  alt User selects "Back"
    UI-->>MC: "Back"
    MC-->>User: Return without loading media
  else User selects a URL
    UI-->>MC: Selected URL
    MC->>MC: Proceed to cast selected media
    MC-->>User: Status/result
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

A rabbit queued a moonlight mix, so spry,
From userSettings.json, oh my!
It tapped “Cast playlist” with a twitch,
Hopped back with “Back” if plans should switch.
Tracks leapt in line, a burrowed beat—
PING, PONG, then play—such tidy feat! 🐇🎶

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description Check ❓ Inconclusive No pull request description was provided (the description field is empty), so there is no descriptive text to confirm intent, motivation, or usage; this makes the description check inconclusive. Although the title and raw change summaries indicate playlist support was added, the absent description conveys no meaningful information to reviewers about rationale, migration, or CLI usage. Because the description is effectively missing, it cannot be judged as related or unrelated. Please ask the author to add a short PR description summarizing the intent and key changes (e.g., Playlist model, PlaylistService, CLI parsing updates, Program DI changes, CommandExecutor and QueueController updates, and userSettings.json), note any migration or configuration steps, and include at least one example of how to use the new playlist feature from the CLI or UI.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "Add configurable playlists to console app" succinctly and accurately summarizes the primary change set: adding Playlist and UserSettings models, a PlaylistService, DI registration, CLI and UI flow updates to load/cast playlists, and related controller/service changes. It is specific, concise, and directly related to the changes in the provided summary, so a reviewer scanning history will understand the main intent. The title avoids noisy details and is appropriate as-is.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/playlist

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds configurable playlist functionality to the SharpCaster console application, allowing users to define playlists in a JSON configuration file and play them via command line or interactive mode.

  • Introduces a JSON-based playlist configuration system through userSettings.json
  • Adds playlist support to both command-line and interactive UI workflows
  • Enhances the queue management system with playlist loading capabilities

Reviewed Changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
userSettings.json Adds playlist configuration file with sample radio stations and music tracks
PlaylistService.cs Implements playlist loading and validation from JSON configuration
Program.cs Integrates PlaylistService into dependency injection and command parsing
CommandLineArgs.cs Adds playlist ID support to command line argument parsing
CommandExecutor.cs Adds playlist playback functionality to command execution
Playlist.cs Defines playlist data model with queue items
UserSettingsModel.cs Defines user settings container for playlists
ConsoleJsonContext.cs Adds JSON serialization context for new models
QueueController.cs Adds interactive playlist selection and enhanced queue display
MediaController.cs Adds back navigation option to media casting
ApplicationFlows.cs Integrates playlist casting into main application flow
SharpCaster.Console.csproj Configures userSettings.json to copy to output directory
TestHelper.cs Enhances test logging with additional receiver information
LoggingTester.cs Updates test assertion to include PONG message type

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

System.Console.WriteLine("Error: Media URL or valid Playlist ID is required for play command.");
return 1;
}
return await PlayPlaylistAsync(_playlistService.Playlists.First(p => p.Name == args.PlaylistId));
Copy link

Copilot AI Sep 20, 2025

Choose a reason for hiding this comment

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

The First() method will throw an exception if no playlist with the specified name is found. Since IsPlaylistId() has the same issue, this could fail even after validation. Use FirstOrDefault() and add null checking.

Suggested change
return await PlayPlaylistAsync(_playlistService.Playlists.First(p => p.Name == args.PlaylistId));
var playlist = _playlistService.Playlists.FirstOrDefault(p => p.Name == args.PlaylistId);
if (playlist == null)
{
System.Console.WriteLine($"Error: Playlist with name '{args.PlaylistId}' not found.");
return 1;
}
return await PlayPlaylistAsync(playlist);

Copilot uses AI. Check for mistakes.
.StartAsync("Loading playlist", async ctx =>
{
ctx.Status("Loading queue...");
var status = await _state.Client.MediaChannel.QueueLoadAsync(_playlistService.Playlists.First(p => p.Name == urlChoice).QueueItems);
Copy link

Copilot AI Sep 20, 2025

Choose a reason for hiding this comment

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

The First() method will throw an exception if no playlist with the specified name is found. Use FirstOrDefault() and add null checking to prevent runtime exceptions.

Suggested change
var status = await _state.Client.MediaChannel.QueueLoadAsync(_playlistService.Playlists.First(p => p.Name == urlChoice).QueueItems);
var playlist = _playlistService.Playlists.FirstOrDefault(p => p.Name == urlChoice);
if (playlist == null)
throw new Exception($"Playlist '{urlChoice}' not found.");
var status = await _state.Client.MediaChannel.QueueLoadAsync(playlist.QueueItems);

Copilot uses AI. Check for mistakes.
Tapanila and others added 4 commits September 20, 2025 13:44
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@Tapanila
Copy link
Owner Author

@RobertK66 I took your pull request and made some changes on it.
Largest part is the changing from Configuration Builder to JSON and simplified the models quite a bit.
This lost certain features like categories you had implemented but now it's way more simplified.

I have few todos still left before I will merge with this and make a new release:

  • I plan to only deliver the binaries on releases. So we need to create the initial version if the file doesn't exist and make sure we don't overwrite it if there's changes
  • Error handling on loading the queue items. Currently you can have issues on the json and they popup strangely so I was thinking of adding some checks on the playlist service

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (3)
SharpCaster.Console/Models/UserSettingsModel.cs (1)

1-5: Remove unused using statements.

The file includes several using statements that aren't used by the simple UserSettingsModel class definition.

Apply this diff to remove unused imports:

-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
+
SharpCaster.Console/Controllers/QueueController.cs (1)

55-59: Avoid First() throw when playlist not found; use FirstOrDefault() + null check.
Even though the choice comes from the list, this prevents edge cases and aligns with prior feedback.

Apply this diff:

-                    var status = await _state.Client.MediaChannel.QueueLoadAsync(_playlistService.Playlists.First(p => p.Name == urlChoice).QueueItems);
+                    var playlist = _playlistService.Playlists.FirstOrDefault(p => p.Name == urlChoice);
+                    if (playlist == null)
+                        throw new Exception($"Playlist '{urlChoice}' not found.");
+                    var status = await _state.Client.MediaChannel.QueueLoadAsync(playlist.QueueItems);
SharpCaster.Console/Services/CommandExecutor.cs (1)

291-297: Case-insensitive check and null-safe lookup for playlist; avoid First() throw.
Matches CLI lowercasing and prevents exceptions.

Apply this diff:

-                        if (string.IsNullOrEmpty(args.PlaylistId) || !_playlistService.IsPlaylistId(args.PlaylistId))
+                        if (string.IsNullOrEmpty(args.PlaylistId) || !_playlistService.IsPlaylistId(args.PlaylistId))
                         {
                             System.Console.WriteLine("Error: Media URL or valid Playlist ID is required for play command.");
                             return 1;
                         }
-                        return await PlayPlaylistAsync(_playlistService.Playlists.First(p => p.Name == args.PlaylistId));
+                        var playlist = _playlistService.Playlists.FirstOrDefault(p => string.Equals(p.Name, args.PlaylistId, StringComparison.OrdinalIgnoreCase));
+                        if (playlist == null)
+                        {
+                            System.Console.WriteLine($"Error: Playlist '{args.PlaylistId}' not found.");
+                            return 1;
+                        }
+                        return await PlayPlaylistAsync(playlist);
🧹 Nitpick comments (4)
SharpCaster.Console/Flows/ApplicationFlows.cs (2)

196-206: Add option LGTM; make PageSize dynamic to avoid future truncation.
Hard‑coding 11 risks truncation if options change.

Apply this diff:

-                    .PageSize(11)
+                    .PageSize(choices.Length)

Also applies to: 211-217


237-240: Prevent nested menus; invoke Queue Management from here instead of inside CastPlaylistAsync.
Calling ShowQueueManagementAsync inside CastPlaylistAsync leads to nested loops when invoked from Queue Management. Trigger it here after casting.

Apply this diff:

-                case "Cast playlist":
-                    _ui.AddSeparator("Casting Playlist");
-                    await _queueController.CastPlaylistAsync();
-                    break;
+                case "Cast playlist":
+                    _ui.AddSeparator("💿 Casting Playlist");
+                    await _queueController.CastPlaylistAsync();
+                    await _queueController.ShowQueueManagementAsync();
+                    break;
SharpCaster.Console/Services/PlaylistService.cs (1)

7-10: Prefer a property and encapsulate mutation. Remove unused using.
Exposing a public field invites unintended writes.

Apply this diff:

-    public class PlaylistService
+    public class PlaylistService
     {
-        public Playlist[] Playlists;
+        public Playlist[] Playlists { get; private set; }

And at the top:

-using Microsoft.Extensions.Configuration;
+using System;
 using SharpCaster.Console.Models;
 using System.Text.Json;
+using System.IO;
SharpCaster.Console/Controllers/QueueController.cs (1)

131-146: Guard next-track when queue is empty or status unavailable.
Avoid misleading “last track” message when there’s no queue.

Apply this diff:

-                        var ids = await mediaChannel.QueueGetItemIdsAsync();
-
-                        if (ids?.LastOrDefault() == mediaChannel.MediaStatus?.CurrentItemId)
+                        var ids = await mediaChannel.QueueGetItemIdsAsync();
+                        if (ids == null || ids.Length == 0 || mediaChannel.MediaStatus?.CurrentItemId == null)
+                        {
+                            AnsiConsole.MarkupLine("[yellow]📋 No queue loaded or no current item.[/]");
+                        }
+                        else if (ids.LastOrDefault() == mediaChannel.MediaStatus.CurrentItemId)
                         {
                             AnsiConsole.MarkupLine("[yellow]⚠️  Already at the last track in the queue. Cannot skip to next track.[/]");
                         }
                         else
                         {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d89e7d8 and 9da27c9.

📒 Files selected for processing (14)
  • SharpCaster.Console/Controllers/MediaController.cs (3 hunks)
  • SharpCaster.Console/Controllers/QueueController.cs (6 hunks)
  • SharpCaster.Console/Flows/ApplicationFlows.cs (3 hunks)
  • SharpCaster.Console/Models/CommandLineArgs.cs (3 hunks)
  • SharpCaster.Console/Models/ConsoleJsonContext.cs (1 hunks)
  • SharpCaster.Console/Models/Playlist.cs (1 hunks)
  • SharpCaster.Console/Models/UserSettingsModel.cs (1 hunks)
  • SharpCaster.Console/Program.cs (3 hunks)
  • SharpCaster.Console/Services/CommandExecutor.cs (6 hunks)
  • SharpCaster.Console/Services/PlaylistService.cs (1 hunks)
  • SharpCaster.Console/SharpCaster.Console.csproj (1 hunks)
  • SharpCaster.Console/userSettings.json (1 hunks)
  • Sharpcaster.Test/LoggingTester.cs (1 hunks)
  • Sharpcaster.Test/helper/TestHelper.cs (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{cs,csproj}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer System.Text.Json for JSON serialization (do not use Newtonsoft.Json)

Files:

  • SharpCaster.Console/Models/UserSettingsModel.cs
  • SharpCaster.Console/SharpCaster.Console.csproj
  • SharpCaster.Console/Models/CommandLineArgs.cs
  • SharpCaster.Console/Controllers/MediaController.cs
  • SharpCaster.Console/Models/Playlist.cs
  • SharpCaster.Console/Services/CommandExecutor.cs
  • SharpCaster.Console/Flows/ApplicationFlows.cs
  • SharpCaster.Console/Services/PlaylistService.cs
  • SharpCaster.Console/Controllers/QueueController.cs
  • SharpCaster.Console/Program.cs
  • SharpCaster.Console/Models/ConsoleJsonContext.cs
  • Sharpcaster.Test/LoggingTester.cs
  • Sharpcaster.Test/helper/TestHelper.cs
Sharpcaster.Test/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

Use xUnit for tests with Moq for mocking in the main test suite

Files:

  • Sharpcaster.Test/LoggingTester.cs
  • Sharpcaster.Test/helper/TestHelper.cs
🧠 Learnings (8)
📓 Common learnings
Learnt from: RobertK66
PR: Tapanila/SharpCaster#384
File: SharpCaster.Console/appsettings.json:88-100
Timestamp: 2025-09-08T09:11:28.905Z
Learning: In SharpCaster.Console appsettings.json playlist configuration, inconsistent ID usage is intentional flexibility - some playlists have explicit IDs while others rely on auto-generation from names. This design choice allows configuration authors to choose between explicit control and automatic ID generation.
📚 Learning: 2025-09-08T09:11:28.905Z
Learnt from: RobertK66
PR: Tapanila/SharpCaster#384
File: SharpCaster.Console/appsettings.json:88-100
Timestamp: 2025-09-08T09:11:28.905Z
Learning: In SharpCaster.Console appsettings.json playlist configuration, inconsistent ID usage is intentional flexibility - some playlists have explicit IDs while others rely on auto-generation from names. This design choice allows configuration authors to choose between explicit control and automatic ID generation.

Applied to files:

  • SharpCaster.Console/Models/UserSettingsModel.cs
  • SharpCaster.Console/Models/CommandLineArgs.cs
  • SharpCaster.Console/Models/Playlist.cs
  • SharpCaster.Console/Services/PlaylistService.cs
  • SharpCaster.Console/userSettings.json
  • SharpCaster.Console/Models/ConsoleJsonContext.cs
📚 Learning: 2025-08-29T20:06:59.311Z
Learnt from: CR
PR: Tapanila/SharpCaster#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-29T20:06:59.311Z
Learning: Applies to Sharpcaster/Sharpcaster.csproj : Target .NET Standard 2.0 and .NET 9 for the main library (AOT compatibility)

Applied to files:

  • SharpCaster.Console/SharpCaster.Console.csproj
📚 Learning: 2025-08-29T20:06:59.311Z
Learnt from: CR
PR: Tapanila/SharpCaster#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-29T20:06:59.311Z
Learning: Applies to Sharpcaster.Test.Aot/Sharpcaster.Test.Aot.csproj : Maintain a separate AOT test project to verify Native AOT compatibility

Applied to files:

  • SharpCaster.Console/SharpCaster.Console.csproj
📚 Learning: 2025-08-29T20:06:59.311Z
Learnt from: CR
PR: Tapanila/SharpCaster#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-29T20:06:59.311Z
Learning: Applies to Sharpcaster/Sharpcaster.csproj : Use Google.Protobuf for binary communication with Chromecast devices

Applied to files:

  • SharpCaster.Console/SharpCaster.Console.csproj
📚 Learning: 2025-08-29T20:06:59.311Z
Learnt from: CR
PR: Tapanila/SharpCaster#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-29T20:06:59.311Z
Learning: Applies to Sharpcaster/**/*.cs : Use Microsoft.Extensions.DependencyInjection for channel management and logging

Applied to files:

  • SharpCaster.Console/Services/CommandExecutor.cs
  • SharpCaster.Console/Controllers/QueueController.cs
  • SharpCaster.Console/Program.cs
📚 Learning: 2025-08-29T20:06:59.311Z
Learnt from: CR
PR: Tapanila/SharpCaster#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-29T20:06:59.311Z
Learning: Applies to Sharpcaster/**/*.cs : Make operations async and support CancellationToken throughout the Sharpcaster library

Applied to files:

  • SharpCaster.Console/Services/CommandExecutor.cs
  • SharpCaster.Console/Controllers/QueueController.cs
📚 Learning: 2025-08-29T20:06:59.311Z
Learnt from: CR
PR: Tapanila/SharpCaster#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-29T20:06:59.311Z
Learning: Applies to Sharpcaster/Sharpcaster.csproj : Use Zeroconf for mDNS device discovery to find Chromecast devices

Applied to files:

  • SharpCaster.Console/Program.cs
🧬 Code graph analysis (11)
SharpCaster.Console/Models/UserSettingsModel.cs (1)
SharpCaster.Console/Models/Playlist.cs (1)
  • Playlist (5-10)
SharpCaster.Console/Models/CommandLineArgs.cs (1)
SharpCaster.Console/Services/PlaylistService.cs (3)
  • PlaylistService (7-36)
  • PlaylistService (11-29)
  • IsPlaylistId (31-34)
SharpCaster.Console/Models/Playlist.cs (2)
Sharpcaster/Models/Queue/QueueItem.cs (1)
  • QueueItem (5-39)
Sharpcaster/Models/Queue/QueueData.cs (2)
  • Sharpcaster (5-73)
  • QueueData (10-72)
SharpCaster.Console/Services/CommandExecutor.cs (6)
SharpCaster.Console/Services/PlaylistService.cs (3)
  • PlaylistService (7-36)
  • PlaylistService (11-29)
  • IsPlaylistId (31-34)
SharpCaster.Console/Services/DeviceService.cs (2)
  • DeviceService (11-285)
  • DeviceService (18-24)
SharpCaster.Console/Controllers/QueueController.cs (4)
  • Task (25-78)
  • Task (80-227)
  • Task (229-288)
  • Task (290-328)
SharpCaster.Console/Program.cs (2)
  • Task (16-46)
  • Task (113-137)
SharpCaster.Console/Models/Playlist.cs (1)
  • Playlist (5-10)
Sharpcaster/Channels/MediaChannel.cs (2)
  • MediaChannel (22-378)
  • MediaChannel (41-43)
SharpCaster.Console/Flows/ApplicationFlows.cs (4)
SharpCaster.Console/UI/UIHelper.cs (1)
  • AddSeparator (17-33)
Sharpcaster/Messages/Queue/QueueUpdateMessage.cs (2)
  • QueueUpdateMessage (12-46)
  • Sharpcaster (7-47)
Sharpcaster/Messages/Queue/QueueInsertMessage.cs (2)
  • QueueInsertMessage (10-26)
  • Sharpcaster (5-27)
Sharpcaster/Models/Queue/QueueItem.cs (1)
  • QueueItem (5-39)
SharpCaster.Console/Services/PlaylistService.cs (2)
SharpCaster.Console/Models/Playlist.cs (1)
  • Playlist (5-10)
SharpCaster.Console/Models/UserSettingsModel.cs (1)
  • UserSettingsModel (8-11)
SharpCaster.Console/Controllers/QueueController.cs (6)
SharpCaster.Console/Services/PlaylistService.cs (2)
  • PlaylistService (7-36)
  • PlaylistService (11-29)
SharpCaster.Console/Services/DeviceService.cs (2)
  • DeviceService (11-285)
  • DeviceService (18-24)
SharpCaster.Console/UI/UIHelper.cs (3)
  • UIHelper (8-131)
  • UIHelper (12-15)
  • AddSeparator (17-33)
SharpCaster.Console/Flows/ApplicationFlows.cs (4)
  • Task (35-81)
  • Task (83-162)
  • Task (164-311)
  • Task (313-332)
SharpCaster.Console/Services/CommandExecutor.cs (10)
  • Task (42-172)
  • Task (174-192)
  • Task (194-206)
  • Task (208-253)
  • Task (282-355)
  • Task (357-378)
  • Task (380-414)
  • Task (416-434)
  • Task (436-449)
  • Task (451-473)
Sharpcaster/Channels/MediaChannel.cs (2)
  • MediaChannel (22-378)
  • MediaChannel (41-43)
SharpCaster.Console/Program.cs (2)
SharpCaster.Console/Models/CommandLineArgs.cs (1)
  • CommandLineParser (23-272)
SharpCaster.Console/Services/PlaylistService.cs (2)
  • PlaylistService (7-36)
  • PlaylistService (11-29)
SharpCaster.Console/userSettings.json (4)
Sharpcaster/Models/Media/Media.cs (2)
  • Sharpcaster (5-133)
  • Media (11-132)
Sharpcaster/Models/Media/MusicTrackMetadata.cs (2)
  • Sharpcaster (3-44)
  • MusicTrackMetadata (5-43)
Sharpcaster/Extensions/SharpcasteSerialiationContext.cs (1)
  • Sharpcaster (16-106)
Sharpcaster.Test/JsonTester.cs (1)
  • Sharpcaster (11-212)
SharpCaster.Console/Models/ConsoleJsonContext.cs (3)
SharpCaster.Console/Models/CommandLineArgs.cs (2)
  • CommandLineArgs (5-21)
  • CommandLineArgs (25-140)
SharpCaster.Console/Models/Playlist.cs (1)
  • Playlist (5-10)
SharpCaster.Console/Models/UserSettingsModel.cs (1)
  • UserSettingsModel (8-11)
Sharpcaster.Test/LoggingTester.cs (1)
Sharpcaster/Channels/HeartbeatChannel.cs (1)
  • OnMessageReceived (61-76)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build windows arm64
  • GitHub Check: Build windows x64
🔇 Additional comments (18)
Sharpcaster.Test/LoggingTester.cs (1)

19-19: Test correctly reflects updated message types.

The test expectation now includes "PONG" in the logged MessageTypes string, which aligns with the Chromecast CASTV2 heartbeat protocol where PONG messages acknowledge PING messages to maintain connection aliveness. The relevant code in HeartbeatChannel.cs shows PONG handling logic, confirming this message type should be logged.

Sharpcaster.Test/helper/TestHelper.cs (2)

64-64: Enhanced receiver logging with additional metadata.

The log now includes receiver Name and Version alongside existing Model and DeviceUri information, providing better visibility during test execution.


258-260: Fixed array initialization syntax error.

The QueueItem array initialization was missing a closing brace, which has been corrected. The syntax fix ensures proper compilation and test data structure.

SharpCaster.Console/SharpCaster.Console.csproj (1)

30-38: Proper configuration for userSettings.json deployment.

The project correctly copies userSettings.json to the output directory, ensuring the PlaylistService can load playlist data at runtime. The empty Properties folder inclusion is standard for .NET project structure.

SharpCaster.Console/Controllers/MediaController.cs (2)

35-49: Good UX enhancement with Back navigation.

Adding the "Back" option with appropriate emoji icon (🔙) provides users with a clean exit path from media selection without loading media. The converter mapping maintains UI consistency.


77-78: Clean early return for Back navigation.

The "Back" case correctly returns early from the method without performing media operations, maintaining the expected navigation flow.

SharpCaster.Console/Models/UserSettingsModel.cs (1)

8-11: Simple and effective model structure.

The UserSettingsModel correctly exposes the Playlists array that aligns with the JSON structure in userSettings.json. The array initialization with [] is appropriate for the required property.

SharpCaster.Console/userSettings.json (1)

1-70: Well-structured playlist configuration data.

The JSON structure provides good variety of playlist types (radio streams, music tracks) with appropriate metadata. The use of different content structures (some with metadata, others without) demonstrates the flexibility mentioned in the retrieved learnings about intentional ID usage patterns.

SharpCaster.Console/Models/CommandLineArgs.cs (3)

20-20: Playlist support integrated into command-line arguments.

The PlaylistId property addition enables playlist-driven command execution alongside existing URL-based flows.


25-25: Enhanced parser signature with PlaylistService dependency.

The updated Parse method signature correctly accepts PlaylistService to enable playlist ID validation during argument parsing.


127-134: Consistent playlist handling with URL logic.

The playlist ID detection logic mirrors the URL handling pattern - using PlaylistService.IsPlaylistId for validation and defaulting to "play" command when no explicit command is provided. This maintains consistency with existing argument parsing behavior.

SharpCaster.Console/Program.cs (2)

74-74: PlaylistService properly registered as singleton.

The DI registration ensures PlaylistService is available throughout the application lifecycle, appropriate for a service that loads configuration data.


28-29: Command-line parsing integrated with playlist support.

The Parse call correctly retrieves PlaylistService from the service provider, enabling playlist-aware argument parsing. This maintains the separation of concerns while providing necessary dependencies.

SharpCaster.Console/Models/Playlist.cs (1)

5-10: LGTM: Simple, correct model using System.Text.Json.
Matches QueueItem shape and keeps default empty array to avoid nulls.

SharpCaster.Console/Models/ConsoleJsonContext.cs (1)

8-11: LGTM: Added source-gen types for playlists and user settings.
Keeps to System.Text.Json as per guidelines.

SharpCaster.Console/Controllers/QueueController.cs (1)

17-23: DI wiring LGTM.
Injecting PlaylistService here is appropriate.

SharpCaster.Console/Services/CommandExecutor.cs (2)

23-34: DI wiring LGTM.
PlaylistService added cleanly to constructor and stored.


357-378: PlayPlaylistAsync LGTM.
Straightforward QueueLoad with clear messages.

Comment on lines +61 to +65
_ui.AddSeparator();
AnsiConsole.MarkupLine("[green]✅ Playlist loaded and playing successfully![/]");
_ui.AddSeparator("📝 Queue Management");
await ShowQueueManagementAsync();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove internal call to ShowQueueManagementAsync to prevent nested loops.
CastPlaylistAsync should just load; let callers decide what to show next.

Apply this diff:

-            _ui.AddSeparator();
-            AnsiConsole.MarkupLine("[green]✅ Playlist loaded and playing successfully![/]");
-            _ui.AddSeparator("📝 Queue Management");
-            await ShowQueueManagementAsync();
+            _ui.AddSeparator();
+            AnsiConsole.MarkupLine("[green]✅ Playlist loaded and playing successfully![/]");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_ui.AddSeparator();
AnsiConsole.MarkupLine("[green]✅ Playlist loaded and playing successfully![/]");
_ui.AddSeparator("📝 Queue Management");
await ShowQueueManagementAsync();
}
_ui.AddSeparator();
AnsiConsole.MarkupLine("[green]✅ Playlist loaded and playing successfully![/]");
}
🤖 Prompt for AI Agents
In SharpCaster.Console/Controllers/QueueController.cs around lines 61 to 65, the
CastPlaylistAsync method currently calls await ShowQueueManagementAsync() which
can create nested interactive loops; remove the internal call so
CastPlaylistAsync only loads and reports success and let callers decide whether
to invoke ShowQueueManagementAsync. Edit the method to delete the await
ShowQueueManagementAsync(); (and any immediate surrounding blank line if
desired) so the method ends after the success markup/separator, leaving UI
separators and success message intact.

Comment on lines +230 to +243
// Check for loaded app
var application = _state.Client.ChromecastStatus.Application;
if (application == null)
{
AnsiConsole.MarkupLine("[dim]No existing application found. Launching Default Media Receiver...[/]");
await _state.Client.LaunchApplicationAsync("B3419EF5", false);
AnsiConsole.MarkupLine("[green]? Default Media Receiver launched successfully![/]");
return;
}
else
{
AnsiConsole.MarkupLine("[green]Joining existing application {0:appId} ...[/]", application.AppId);
await _state.Client.LaunchApplicationAsync(application.AppId, true);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix Spectre.Console MarkupLine usage and message text.
Current call passes a format arg to MarkupLine (no such overload) and shows an odd “?” glyph.

Apply this diff:

-            if (application == null)
+            if (application == null)
             {
                 AnsiConsole.MarkupLine("[dim]No existing application found. Launching Default Media Receiver...[/]");
                 await _state.Client.LaunchApplicationAsync("B3419EF5", false);
-                AnsiConsole.MarkupLine("[green]? Default Media Receiver launched successfully![/]");
+                AnsiConsole.MarkupLine("[green]✅ Default Media Receiver launched successfully![/]");
                 return;
             }
             else
             {
-                AnsiConsole.MarkupLine("[green]Joining existing application {0:appId} ...[/]", application.AppId);
-                await _state.Client.LaunchApplicationAsync(application.AppId, true);
+                AnsiConsole.MarkupLine($"[green]Joining existing application {application.AppId}...[/]");
+                await _state.Client.LaunchApplicationAsync(application.AppId, joinExistingApplicationSession: true);
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Check for loaded app
var application = _state.Client.ChromecastStatus.Application;
if (application == null)
{
AnsiConsole.MarkupLine("[dim]No existing application found. Launching Default Media Receiver...[/]");
await _state.Client.LaunchApplicationAsync("B3419EF5", false);
AnsiConsole.MarkupLine("[green]? Default Media Receiver launched successfully![/]");
return;
}
else
{
AnsiConsole.MarkupLine("[green]Joining existing application {0:appId} ...[/]", application.AppId);
await _state.Client.LaunchApplicationAsync(application.AppId, true);
}
// Check for loaded app
var application = _state.Client.ChromecastStatus.Application;
if (application == null)
{
AnsiConsole.MarkupLine("[dim]No existing application found. Launching Default Media Receiver...[/]");
await _state.Client.LaunchApplicationAsync("B3419EF5", false);
AnsiConsole.MarkupLine("[green] Default Media Receiver launched successfully![/]");
return;
}
else
{
AnsiConsole.MarkupLine($"[green]Joining existing application {application.AppId}...[/]");
await _state.Client.LaunchApplicationAsync(application.AppId, joinExistingApplicationSession: true);
}
🤖 Prompt for AI Agents
In SharpCaster.Console/Services/CommandExecutor.cs around lines 230 to 243, the
Spectre.Console.MarkupLine calls incorrectly pass a format argument and include
an extraneous “?” glyph; change the messages to use string interpolation (or
build the full string before passing) instead of format args and remove the “?”
glyph from the success message, e.g. construct the markup strings with the AppId
inserted via $"..." and update "[green]? Default Media Receiver launched
successfully![/]" to "[green]Default Media Receiver launched successfully![/]".

Comment on lines +13 to +21
var playlists = File.ReadAllText("userSettings.json");
if ( string.IsNullOrWhiteSpace(playlists))
{
Playlists = [];
return;
}

var userSettings = JsonSerializer.Deserialize<UserSettingsModel>(playlists, ConsoleJsonContext.Default.UserSettingsModel);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle missing/invalid userSettings.json safely (FileNotFound, JSON errors).
Reading a relative file without existence check will throw and break startup.

Apply this diff:

-            var playlists = File.ReadAllText("userSettings.json");
-            if ( string.IsNullOrWhiteSpace(playlists))
-            {
-                Playlists = [];
-                return;
-            }
-
-            var userSettings = JsonSerializer.Deserialize<UserSettingsModel>(playlists, ConsoleJsonContext.Default.UserSettingsModel);
+            var path = Path.Combine(AppContext.BaseDirectory, "userSettings.json");
+            if (!File.Exists(path))
+            {
+                Playlists = [];
+                return;
+            }
+
+            var json = File.ReadAllText(path);
+            if (string.IsNullOrWhiteSpace(json))
+            {
+                Playlists = [];
+                return;
+            }
+
+            UserSettingsModel? userSettings;
+            try
+            {
+                userSettings = JsonSerializer.Deserialize<UserSettingsModel>(json, ConsoleJsonContext.Default.UserSettingsModel);
+            }
+            catch (JsonException)
+            {
+                Playlists = [];
+                return;
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var playlists = File.ReadAllText("userSettings.json");
if ( string.IsNullOrWhiteSpace(playlists))
{
Playlists = [];
return;
}
var userSettings = JsonSerializer.Deserialize<UserSettingsModel>(playlists, ConsoleJsonContext.Default.UserSettingsModel);
var path = Path.Combine(AppContext.BaseDirectory, "userSettings.json");
if (!File.Exists(path))
{
Playlists = [];
return;
}
var json = File.ReadAllText(path);
if (string.IsNullOrWhiteSpace(json))
{
Playlists = [];
return;
}
UserSettingsModel? userSettings;
try
{
userSettings = JsonSerializer.Deserialize<UserSettingsModel>(json, ConsoleJsonContext.Default.UserSettingsModel);
}
catch (JsonException)
{
Playlists = [];
return;
}
🤖 Prompt for AI Agents
In SharpCaster.Console/Services/PlaylistService.cs around lines 13–21, reading
"userSettings.json" directly can throw if the file is missing or contains
invalid JSON; to fix, first construct a safe path (e.g.
Path.Combine(AppContext.BaseDirectory, "userSettings.json")) and check
File.Exists before reading — if missing, set Playlists = new List<...>() and
return; if the file exists, read its contents and guard against empty/whitespace
by setting Playlists to an empty list and returning; wrap
JsonSerializer.Deserialize in a try/catch catching JsonException and IOException
(or general Exception), log the error, and on any failure set Playlists to an
empty list instead of letting startup crash; ensure you continue to use
ConsoleJsonContext.Default.UserSettingsModel when deserializing.

Comment on lines +31 to +34
public bool IsPlaylistId(string arg)
{
return Playlists.Any(p => p.Name == arg);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Make playlist ID checks case‑insensitive (CLI lowercases args).
Current check will fail if playlist names in settings use mixed case.

Apply this diff:

-        public bool IsPlaylistId(string arg)
+        public bool IsPlaylistId(string arg)
         {
-            return Playlists.Any(p => p.Name == arg);
+            return Playlists.Any(p => string.Equals(p.Name, arg, StringComparison.OrdinalIgnoreCase));
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public bool IsPlaylistId(string arg)
{
return Playlists.Any(p => p.Name == arg);
}
public bool IsPlaylistId(string arg)
{
return Playlists.Any(p => string.Equals(p.Name, arg, StringComparison.OrdinalIgnoreCase));
}
🤖 Prompt for AI Agents
In SharpCaster.Console/Services/PlaylistService.cs around lines 31 to 34, the
IsPlaylistId method compares playlist names case-sensitively which fails when
CLI args are lowercased; update the comparison to be case-insensitive by using
string.Equals with StringComparison.OrdinalIgnoreCase (or compare both strings
with ToLowerInvariant) and make the comparison null-safe (e.g., p.Name != null
&& string.Equals(p.Name, arg, StringComparison.OrdinalIgnoreCase)) so playlist
lookup succeeds regardless of case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants