1package commands 2 3import ( 4 "fmt" 5 6 "github.com/ambientsound/gompd/mpd" 7 "github.com/ambientsound/pms/api" 8 "github.com/ambientsound/pms/input/lexer" 9) 10 11// Play plays songs in the MPD playlist. 12type Play struct { 13 newcommand 14 api api.API 15 cursor bool 16 selection bool 17} 18 19// NewPlay returns Play. 20func NewPlay(api api.API) Command { 21 return &Play{ 22 api: api, 23 } 24} 25 26// Parse implements Command. 27func (cmd *Play) Parse() error { 28 tok, lit := cmd.ScanIgnoreWhitespace() 29 30 cmd.setTabCompleteVerbs(lit) 31 32 switch tok { 33 case lexer.TokenEnd: 34 // No parameters; just send 'play' command to MPD 35 return nil 36 case lexer.TokenIdentifier: 37 default: 38 return fmt.Errorf("Unexpected '%s', expected identifier", lit) 39 } 40 41 switch lit { 42 // Play song under cursor 43 case "cursor": 44 cmd.cursor = true 45 // Play selected songs 46 case "selection": 47 cmd.selection = true 48 default: 49 return fmt.Errorf("Unexpected '%s', expected identifier", lit) 50 } 51 52 cmd.setTabCompleteEmpty() 53 54 return cmd.ParseEnd() 55} 56 57// Exec implements Command. 58func (cmd *Play) Exec() error { 59 60 // Ensure MPD connection. 61 client := cmd.api.MpdClient() 62 if client == nil { 63 return fmt.Errorf("Cannot play: not connected to MPD") 64 } 65 66 switch { 67 case cmd.cursor: 68 // Play song under cursor. 69 return cmd.playCursor(client) 70 case cmd.selection: 71 // Play selected songs. 72 return cmd.playSelection(client) 73 } 74 75 // If a selection is not given, start playing with default parameters. 76 return client.Play(-1) 77} 78 79// playCursor plays the song under the cursor. 80func (cmd *Play) playCursor(client *mpd.Client) error { 81 82 // Get the song under the cursor. 83 song := cmd.api.Songlist().CursorSong() 84 if song == nil { 85 return fmt.Errorf("Cannot play: no song under cursor") 86 } 87 88 // Check if the currently selected song has an ID. If it doesn't, it's not 89 // from the queue, and the song will have to be added beforehand. 90 id := song.ID 91 if song.NullID() { 92 var err error 93 id, err = client.AddID(song.StringTags["file"], -1) 94 if err != nil { 95 return err 96 } 97 } 98 99 // Play the correct song. 100 return client.PlayID(id) 101} 102 103// playSelection plays the currently selected songs. 104func (cmd *Play) playSelection(client *mpd.Client) error { 105 106 // Get the track selection. 107 selection := cmd.api.Songlist().Selection() 108 if selection.Len() == 0 { 109 return fmt.Errorf("Cannot play: no selection") 110 } 111 112 // Check if the first song has an ID. If it does, just start playing. The 113 // playback order cannot be guaranteed as the selection might be 114 // fragmented, so don't touch the selection. 115 first := selection.Song(0) 116 if !first.NullID() { 117 return client.PlayID(first.ID) 118 } 119 120 // We are not operating directly on the queue; add all songs to the queue now. 121 queue := cmd.api.Queue() 122 queueLen := queue.Len() 123 err := queue.AddList(selection) 124 if err != nil { 125 return err 126 } 127 cmd.api.Songlist().ClearSelection() 128 cmd.api.Message("Playing %d new songs", selection.Len()) 129 130 // We haven't got the ID from the first added song, so use positions 131 // instead. In case of simultaneous operation with another client, this 132 // might lead to a race condition. Ignore this for now. 133 return client.Play(queueLen) 134} 135 136// setTabCompleteVerbs sets the tab complete list to the list of available sub-commands. 137func (cmd *Play) setTabCompleteVerbs(lit string) { 138 cmd.setTabComplete(lit, []string{ 139 "cursor", 140 "selection", 141 }) 142} 143