Passed
Push — master ( 624385...606925 )
by Viktor
02:26
created

bot.*Connection.Play   F

Complexity

Conditions 14

Size

Total Lines 47
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 36
nop 2
dl 0
loc 47
rs 3.6
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like bot.*Connection.Play often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
package bot
2
3
import (
4
	"bufio"
5
	"encoding/binary"
6
	"errors"
7
	"fmt"
8
	"io"
9
	"os/exec"
10
	"strconv" // https://github.com/layeh/gopus
11
12
	"github.com/FlameInTheDark/gopus"
13
	"github.com/bwmarrin/discordgo"
14
)
15
16
const (
17
	// CHANNELS count of audio channels
18
	CHANNELS int = 2
19
	// FRAME_RATE ...
20
	FRAME_RATE int = 48000
0 ignored issues
show
introduced by
don't use ALL_CAPS in Go names; use CamelCase
Loading history...
21
	// FRAME_SIZE ...
22
	FRAME_SIZE int = 960
0 ignored issues
show
introduced by
don't use ALL_CAPS in Go names; use CamelCase
Loading history...
23
	// MAX_BYTES max bytes per sample
24
	MAX_BYTES int = (FRAME_SIZE * 2) * 2
0 ignored issues
show
introduced by
don't use ALL_CAPS in Go names; use CamelCase
Loading history...
25
)
26
27
// Play start playback
28
func (connection *Connection) Play(source string, volume float32) error {
29
	if connection.playing {
30
		return errors.New("song already playing")
31
	}
32
	ffmpeg := exec.Command("ffmpeg", "-i", source, "-f", "s16le", "-reconnect", "1", "-reconnect_at_eof", "1", "-reconnect_streamed", "1", "-reconnect_delay_max", "2", "-filter:a", fmt.Sprintf("volume=%.3f", volume), "-ar", strconv.Itoa(FRAME_RATE), "-ac", strconv.Itoa(CHANNELS), "pipe:1")
33
	defer func() {
34
		fferr := ffmpeg.Process.Kill()
35
		if fferr != nil {
36
			fmt.Println("FFMPEG close err: ", fferr)
37
		}
38
	}()
39
	connection.stopRunning = false
40
	out, err := ffmpeg.StdoutPipe()
41
	if err != nil {
42
		return err
43
	}
44
	buffer := bufio.NewReaderSize(out, 16384)
45
	err = ffmpeg.Start()
46
	if err != nil {
47
		return err
48
	}
49
	connection.playing = true
50
	defer func() {
51
		connection.playing = false
52
	}()
53
	_ = connection.voiceConnection.Speaking(true)
54
	defer func() { _ = connection.voiceConnection.Speaking(false) }()
55
	if connection.send == nil {
56
		connection.send = make(chan []int16, 2)
57
	}
58
	go connection.sendPCM(connection.voiceConnection, connection.send)
59
	for {
60
		if connection.stopRunning {
61
			_ = ffmpeg.Process.Kill()
62
			break
63
		}
64
		audioBuffer := make([]int16, FRAME_SIZE*CHANNELS)
65
		err = binary.Read(buffer, binary.LittleEndian, &audioBuffer)
66
		if err == io.EOF || err == io.ErrUnexpectedEOF {
67
			return nil
68
		}
69
		if err != nil {
70
			return err
71
		}
72
		connection.send <- audioBuffer
73
	}
74
	return nil
75
}
76
77
// sendPCM sends pulse code modulation to discord voice channel
78
func (connection *Connection) sendPCM(voice *discordgo.VoiceConnection, pcm <-chan []int16) {
79
	connection.lock.Lock()
80
	if connection.sendpcm || pcm == nil {
81
		connection.lock.Unlock()
82
		return
83
	}
84
	connection.sendpcm = true
85
	connection.lock.Unlock()
86
	defer func() {
87
		connection.sendpcm = false
88
	}()
89
	encoder, err := gopus.NewEncoder(FRAME_RATE, CHANNELS, gopus.Audio)
90
	if err != nil {
91
		fmt.Println("NewEncoder error,", err)
92
		return
93
	}
94
	for {
95
		receive, ok := <-pcm
96
		if !ok {
97
			fmt.Println("PCM channel closed")
98
			return
99
		}
100
		opus, err := encoder.Encode(receive, FRAME_SIZE, MAX_BYTES)
101
		if err != nil {
102
			fmt.Println("Encoding error,", err)
103
			return
104
		}
105
		if !voice.Ready || voice.OpusSend == nil {
106
			fmt.Printf("Discordgo not ready for opus packets. %+v : %+v", voice.Ready, voice.OpusSend)
107
			return
108
		}
109
		voice.OpusSend <- opus
110
	}
111
}
112
113
// PlayYoutube starts playing song from youtube
114
func (connection *Connection) PlayYoutube(ffmpeg *exec.Cmd) error {
115
	if connection.playing {
116
		return errors.New("song already playing")
117
	}
118
	connection.stopRunning = false
119
	out, err := ffmpeg.StdoutPipe()
120
	if err != nil {
121
		return err
122
	}
123
	buffer := bufio.NewReaderSize(out, 16384)
124
	err = ffmpeg.Start()
125
	if err != nil {
126
		return err
127
	}
128
	connection.playing = true
129
	defer func() {
130
		connection.playing = false
131
	}()
132
	_ = connection.voiceConnection.Speaking(true)
133
	defer func() { _ = connection.voiceConnection.Speaking(false) }()
134
	if connection.send == nil {
135
		connection.send = make(chan []int16, 2)
136
	}
137
	go connection.sendPCM(connection.voiceConnection, connection.send)
138
	for {
139
		if connection.stopRunning {
140
			fmt.Println("Closing ffmpeg...")
141
			_ = ffmpeg.Process.Kill()
142
			break
143
		}
144
		audioBuffer := make([]int16, FRAME_SIZE*CHANNELS)
145
		err = binary.Read(buffer, binary.LittleEndian, &audioBuffer)
146
		if err == io.EOF || err == io.ErrUnexpectedEOF {
147
			return nil
148
		}
149
		if err != nil {
150
			return err
151
		}
152
		connection.send <- audioBuffer
153
	}
154
	return nil
155
}
156
157
// Stop stops playback
158
func (connection *Connection) Stop() {
159
	connection.stopRunning = true
160
	connection.playing = false
161
}
162