Files
Bose-SoundTouch/pkg/models/speaker.go
T
Tobias Gesellchen 3a33cadbd7 feat: implement /speaker endpoint for TTS and URL playback
- Add PlayInfo model for TTS and URL content playback requests
- Add SpeakerResponse model for endpoint responses
- Implement client methods: PlayTTS, PlayURL, PlayCustom, PlayNotificationBeep
- Add comprehensive CLI commands for speaker functionality:
  - speaker tts: Text-to-Speech with Google TTS and language support
  - speaker url: Audio content playback from HTTP/HTTPS URLs
  - speaker beep: Simple notification beep sound
  - speaker help: Detailed functionality documentation
- Support for volume control (0-100 or current volume)
- Multi-language TTS support (EN, DE, ES, FR, IT, NL, PT, RU, ZH, JA, etc.)
- Custom metadata support for NowPlaying display
- Comprehensive validation and error handling
- Full test suite with XML marshaling/unmarshaling tests
- Complete documentation with API reference and usage examples
- Compatible with ST-10 (Series III) and other supported SoundTouch devices

The /speaker endpoint enables notification and audio content playback,
automatically managing volume restoration and content interruption.
Perfect for home automation, alerts, and custom audio notifications.
2026-02-01 23:21:15 +01:00

128 lines
3.0 KiB
Go

package models
import (
"encoding/xml"
"errors"
)
// Error constants for speaker validation
var (
ErrInvalidURL = errors.New("URL cannot be empty")
ErrInvalidAppKey = errors.New("app key cannot be empty")
ErrInvalidService = errors.New("service cannot be empty")
ErrInvalidVolume = errors.New("volume must be between 0 and 100")
)
// PlayInfo represents the request body for the /speaker endpoint to play TTS or URL content
type PlayInfo struct {
XMLName xml.Name `xml:"play_info"`
URL string `xml:"url"`
AppKey string `xml:"app_key"`
Service string `xml:"service"`
Message string `xml:"message"`
Reason string `xml:"reason"`
Volume *int `xml:"volume,omitempty"`
}
// SpeakerResponse represents the response from the /speaker endpoint
type SpeakerResponse struct {
XMLName xml.Name `xml:"status"`
Value string `xml:",chardata"`
}
// SpeakerPlayStatus represents the status during speaker playback
type SpeakerPlayStatus struct {
Service string `json:"service"`
Message string `json:"message"`
Reason string `json:"reason"`
Volume int `json:"volume,omitempty"`
}
// NewPlayInfo creates a new PlayInfo instance for TTS or URL playback
func NewPlayInfo(url, appKey, service, message, reason string) *PlayInfo {
return &PlayInfo{
XMLName: xml.Name{Local: "play_info"},
URL: url,
AppKey: appKey,
Service: service,
Message: message,
Reason: reason,
}
}
// SetVolume sets the volume level for playback
func (p *PlayInfo) SetVolume(volume int) *PlayInfo {
p.Volume = &volume
return p
}
// NewTTSPlayInfo creates a PlayInfo for Google TTS playback
func NewTTSPlayInfo(text, appKey string, volume ...int) *PlayInfo {
// URL encode the text for Google TTS
url := "http://translate.google.com/translate_tts?ie=UTF-8&tl=EN&client=tw-ob&q=" + text
playInfo := &PlayInfo{
XMLName: xml.Name{Local: "play_info"},
URL: url,
AppKey: appKey,
Service: "TTS Notification",
Message: "Google TTS",
Reason: text,
}
if len(volume) > 0 {
playInfo.Volume = &volume[0]
}
return playInfo
}
// NewURLPlayInfo creates a PlayInfo for URL content playback
func NewURLPlayInfo(url, appKey, service, message, reason string, volume ...int) *PlayInfo {
playInfo := &PlayInfo{
XMLName: xml.Name{Local: "play_info"},
URL: url,
AppKey: appKey,
Service: service,
Message: message,
Reason: reason,
}
if len(volume) > 0 {
playInfo.Volume = &volume[0]
}
return playInfo
}
// Validate validates the PlayInfo request
func (p *PlayInfo) Validate() error {
if p.URL == "" {
return ErrInvalidURL
}
if p.AppKey == "" {
return ErrInvalidAppKey
}
if p.Service == "" {
return ErrInvalidService
}
if p.Volume != nil && (*p.Volume < 0 || *p.Volume > 100) {
return ErrInvalidVolume
}
return nil
}
// String returns a string representation of the PlayInfo
func (p *PlayInfo) String() string {
volumeStr := "current"
if p.Volume != nil {
volumeStr = string(rune(*p.Volume))
}
return "Service: " + p.Service + ", Message: " + p.Message + ", Volume: " + volumeStr
}