package main import ( "fmt" "log" "os" "time" "github.com/gesellix/bose-soundtouch/pkg/client" "github.com/gesellix/bose-soundtouch/pkg/models" ) func main() { if len(os.Args) < 2 { printUsage() os.Exit(1) } // Get device IP from command line deviceIP := os.Args[1] // Create client config := &client.Config{ Host: deviceIP, Port: 8090, Timeout: 10 * time.Second, } c := client.NewClient(config) fmt.Printf("🎡 SoundTouch Preset Management Example\n") fmt.Printf("πŸ“± Device: %s:%d\n\n", config.Host, config.Port) // Demonstrate all preset management features if err := demonstratePresetManagement(c); err != nil { log.Fatalf("Demo failed: %v", err) } fmt.Println("\nβœ… Preset management demo completed!") } func demonstratePresetManagement(c *client.Client) error { // 1. Get current presets fmt.Println("πŸ“‹ Step 1: Getting current presets...") if err := showCurrentPresets(c); err != nil { return fmt.Errorf("failed to get presets: %w", err) } // 2. Check if current content is presetable fmt.Println("\nπŸ” Step 2: Checking current content...") if err := checkCurrentContent(c); err != nil { return fmt.Errorf("failed to check current content: %w", err) } // 3. Store current content as preset (if possible) fmt.Println("\nπŸ’Ύ Step 3: Storing current content as preset...") if err := storeCurrentAsPreset(c); err != nil { fmt.Printf("⚠️ Cannot store current content: %v\n", err) // 4. Store a Spotify playlist as alternative example fmt.Println("\nπŸ’Ώ Step 4: Storing Spotify playlist as preset...") if err := storeSpotifyPlaylist(c); err != nil { return fmt.Errorf("failed to store Spotify playlist: %w", err) } } // 5. Store a radio station fmt.Println("\nπŸ“» Step 5: Storing radio station as preset...") if err := storeRadioStation(c); err != nil { return fmt.Errorf("failed to store radio station: %w", err) } // 6. Show updated presets fmt.Println("\nπŸ“‹ Step 6: Showing updated presets...") if err := showCurrentPresets(c); err != nil { return fmt.Errorf("failed to get updated presets: %w", err) } // 7. Select a preset fmt.Println("\n🎯 Step 7: Selecting preset 1...") if err := selectPreset(c, 1); err != nil { return fmt.Errorf("failed to select preset: %w", err) } // 8. Demonstrate WebSocket events fmt.Println("\nπŸ“‘ Step 8: Demonstrating preset events...") if err := demonstrateWebSocketEvents(c); err != nil { return fmt.Errorf("failed to demonstrate WebSocket events: %w", err) } return nil } func showCurrentPresets(c *client.Client) error { presets, err := c.GetPresets() if err != nil { return err } // Filter out placeholder presets (issue #308): self-closing // entries from a factory-reset device and // INVALID_SOURCE placeholders from healthy devices both panic if // their fields are dereferenced directly. configured := make([]models.Preset, 0, len(presets.Preset)) for _, p := range presets.Preset { if !p.IsEmpty() { configured = append(configured, p) } } if len(configured) == 0 { fmt.Println(" πŸ“­ No presets configured") return nil } fmt.Printf(" πŸ“» Found %d configured presets:\n", len(configured)) for _, preset := range configured { fmt.Printf(" %d. %s\n", preset.ID, preset.GetDisplayName()) fmt.Printf(" Source: %s\n", preset.GetSource()) if location := preset.GetLocation(); location != "" { fmt.Printf(" Location: %s\n", location) } if preset.CreatedOn != nil && *preset.CreatedOn != 0 { createdTime := time.Unix(*preset.CreatedOn, 0) fmt.Printf(" Created: %s\n", createdTime.Format("2006-01-02 15:04:05")) } fmt.Println() } // Show available slots emptySlots := presets.GetEmptyPresetSlots() if len(emptySlots) > 0 { fmt.Printf(" πŸ†“ Available slots: %v\n", emptySlots) } else { fmt.Println(" 🈡 All preset slots are occupied") } return nil } func checkCurrentContent(c *client.Client) error { nowPlaying, err := c.GetNowPlaying() if err != nil { return err } if nowPlaying.IsEmpty() { fmt.Println(" ⏸️ No content currently playing") return nil } fmt.Printf(" 🎡 Now Playing: %s\n", nowPlaying.Track) if nowPlaying.Artist != "" { fmt.Printf(" Artist: %s\n", nowPlaying.Artist) } fmt.Printf(" Source: %s\n", nowPlaying.Source) if nowPlaying.ContentItem == nil { fmt.Println(" ❌ No content item available") return nil } fmt.Printf(" Presetable: %t\n", nowPlaying.ContentItem.IsPresetable) if nowPlaying.ContentItem.Location != "" { fmt.Printf(" Location: %s\n", nowPlaying.ContentItem.Location) } return nil } func storeCurrentAsPreset(c *client.Client) error { // Check if current content is presetable presetable, err := c.IsCurrentContentPresetable() if err != nil { return err } if !presetable { return fmt.Errorf("current content is not presetable") } // Find an available slot nextSlot, err := c.GetNextAvailablePresetSlot() if err != nil { return err } fmt.Printf(" πŸ’Ύ Storing current content as preset %d...\n", nextSlot) err = c.StoreCurrentAsPreset(nextSlot) if err != nil { return err } fmt.Printf(" βœ… Successfully stored as preset %d\n", nextSlot) return nil } func storeSpotifyPlaylist(c *client.Client) error { // Find an available slot nextSlot, err := c.GetNextAvailablePresetSlot() if err != nil { return err } // Example Spotify playlist spotifyContent := &models.ContentItem{ Source: "SPOTIFY", Type: "uri", Location: "spotify:playlist:37i9dQZF1DXcBWIGoYBM5M", // Today's Top Hits SourceAccount: "spotify_user", IsPresetable: true, ItemName: "Today's Top Hits", ContainerArt: "https://i.scdn.co/image/ab67706f00000003c13b4f1084cea7bededbcadc", } fmt.Printf(" πŸ’Ώ Storing Spotify playlist as preset %d...\n", nextSlot) fmt.Printf(" Playlist: %s\n", spotifyContent.ItemName) fmt.Printf(" URI: %s\n", spotifyContent.Location) err = c.StorePreset(nextSlot, spotifyContent) if err != nil { return err } fmt.Printf(" βœ… Successfully stored Spotify playlist as preset %d\n", nextSlot) return nil } func storeRadioStation(c *client.Client) error { // Find an available slot nextSlot, err := c.GetNextAvailablePresetSlot() if err != nil { return err } // Example radio station radioContent := &models.ContentItem{ Source: "TUNEIN", Type: "stationurl", Location: "/v1/playback/station/s33828", // K-LOVE SourceAccount: "", IsPresetable: true, ItemName: "K-LOVE Radio", ContainerArt: "http://cdn-profiles.tunein.com/s33828/images/logog.png", } fmt.Printf(" πŸ“» Storing radio station as preset %d...\n", nextSlot) fmt.Printf(" Station: %s\n", radioContent.ItemName) fmt.Printf(" Location: %s\n", radioContent.Location) err = c.StorePreset(nextSlot, radioContent) if err != nil { return err } fmt.Printf(" βœ… Successfully stored radio station as preset %d\n", nextSlot) return nil } func selectPreset(c *client.Client, presetNumber int) error { // First check if the preset exists presets, err := c.GetPresets() if err != nil { return err } preset := presets.GetPresetByID(presetNumber) if preset == nil || preset.IsEmpty() { return fmt.Errorf("preset %d is empty", presetNumber) } fmt.Printf(" 🎯 Selecting preset %d: %s\n", presetNumber, preset.GetDisplayName()) err = c.SelectPreset(presetNumber) if err != nil { return err } fmt.Printf(" βœ… Successfully selected preset %d\n", presetNumber) // Wait a moment and show what's now playing time.Sleep(2 * time.Second) fmt.Println(" 🎡 Checking what's now playing...") nowPlaying, err := c.GetNowPlaying() if err != nil { fmt.Printf(" ⚠️ Could not get now playing: %v\n", err) return nil } if !nowPlaying.IsEmpty() { fmt.Printf(" Now Playing: %s\n", nowPlaying.Track) if nowPlaying.Artist != "" { fmt.Printf(" Artist: %s\n", nowPlaying.Artist) } fmt.Printf(" Source: %s\n", nowPlaying.Source) } return nil } func demonstrateWebSocketEvents(c *client.Client) error { // Create WebSocket client wsClient := c.NewWebSocketClient(nil) // Set up preset event handler wsClient.OnPresetUpdated(func(event *models.PresetUpdatedEvent) { fmt.Printf(" πŸ“‘ Preset Update Event Received!\n") fmt.Printf(" Device: %s\n", event.DeviceID) fmt.Printf(" Presets count: %d\n", len(event.Presets.Preset)) for _, preset := range event.Presets.Preset { if !preset.IsEmpty() { fmt.Printf(" - Preset %d: %s (%s)\n", preset.ID, preset.GetDisplayName(), preset.GetSource()) } } }) // Connect to WebSocket fmt.Printf(" πŸ“‘ Connecting to WebSocket for real-time events...\n") err := wsClient.Connect() if err != nil { return err } defer wsClient.Disconnect() fmt.Printf(" βœ… WebSocket connected, listening for preset events...\n") fmt.Printf(" πŸ”„ Making a preset change to trigger an event...\n") // Find an available slot and store something to trigger an event nextSlot, err := c.GetNextAvailablePresetSlot() if err != nil { // If no slots available, remove the last preset we created nextSlot = 6 fmt.Printf(" πŸ—‘οΈ Removing preset %d to trigger event...\n", nextSlot) c.RemovePreset(nextSlot) } else { // Store a simple test preset testContent := &models.ContentItem{ Source: "TUNEIN", Type: "stationurl", Location: "/v1/playback/station/s25111", // BBC Radio 1 SourceAccount: "", IsPresetable: true, ItemName: "BBC Radio 1", } fmt.Printf(" πŸ’Ύ Storing test preset %d to trigger event...\n", nextSlot) c.StorePreset(nextSlot, testContent) } // Wait for event fmt.Println(" ⏳ Waiting 3 seconds for WebSocket event...") time.Sleep(3 * time.Second) fmt.Println(" πŸ“‘ WebSocket events demonstration complete") return nil } func printUsage() { fmt.Println("🎡 SoundTouch Preset Management Example") fmt.Println() fmt.Println("This example demonstrates all preset management features:") fmt.Println("β€’ List current presets") fmt.Println("β€’ Check if content is presetable") fmt.Println("β€’ Store current content as preset") fmt.Println("β€’ Store Spotify playlists as presets") fmt.Println("β€’ Store radio stations as presets") fmt.Println("β€’ Select presets") fmt.Println("β€’ Handle preset WebSocket events") fmt.Println() fmt.Println("Usage:") fmt.Printf(" %s \n", os.Args[0]) fmt.Println() fmt.Println("Example:") fmt.Printf(" %s 192.0.2.100\n", os.Args[0]) fmt.Println() fmt.Println("Prerequisites:") fmt.Println("β€’ SoundTouch device on your network") fmt.Println("β€’ Device IP address") fmt.Println("β€’ Device powered on and connected") }