Passed
Push — master ( 688987...81d234 )
by Viktor
01:47
created

bot.*Connection.Play   D

Complexity

Conditions 12

Size

Total Lines 41
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 32
nop 1
dl 0
loc 41
rs 4.8
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) error {
29
	if connection.playing {
30
		return errors.New("song already playing")
31
	}
32
	ffmpeg := exec.Command("ffmpeg", "-i", source, "-f", "s16le", "-ar", strconv.Itoa(FRAME_RATE), "-ac", strconv.Itoa(CHANNELS), "pipe:1")
33
	connection.stopRunning = false
34
	out, err := ffmpeg.StdoutPipe()
35
	if err != nil {
36
		return err
37
	}
38
	buffer := bufio.NewReaderSize(out, 16384)
39
	err = ffmpeg.Start()
40
	if err != nil {
41
		return err
42
	}
43
	connection.playing = true
44
	defer func() {
45
		connection.playing = false
46
	}()
47
	_=connection.voiceConnection.Speaking(true)
48
	defer func() {_=connection.voiceConnection.Speaking(false)}()
49
	if connection.send == nil {
50
		connection.send = make(chan []int16, 2)
51
	}
52
	go connection.sendPCM(connection.voiceConnection, connection.send)
53
	for {
54
		if connection.stopRunning {
55
			_=ffmpeg.Process.Kill()
56
			break
57
		}
58
		audioBuffer := make([]int16, FRAME_SIZE*CHANNELS)
59
		err = binary.Read(buffer, binary.LittleEndian, &audioBuffer)
60
		if err == io.EOF || err == io.ErrUnexpectedEOF {
61
			return nil
62
		}
63
		if err != nil {
64
			return err
65
		}
66
		connection.send <- audioBuffer
67
	}
68
	return nil
69
}
70
71
// sendPCM sends pulse code modulation to discord voice channel
72
func (connection *Connection) sendPCM(voice *discordgo.VoiceConnection, pcm <-chan []int16) {
73
	connection.lock.Lock()
74
	if connection.sendpcm || pcm == nil {
75
		connection.lock.Unlock()
76
		return
77
	}
78
	connection.sendpcm = true
79
	connection.lock.Unlock()
80
	defer func() {
81
		connection.sendpcm = false
82
	}()
83
	encoder, err := gopus.NewEncoder(FRAME_RATE, CHANNELS, gopus.Audio)
84
	if err != nil {
85
		fmt.Println("NewEncoder error,", err)
86
		return
87
	}
88
	for {
89
		receive, ok := <-pcm
90
		if !ok {
91
			fmt.Println("PCM channel closed")
92
			return
93
		}
94
		opus, err := encoder.Encode(receive, FRAME_SIZE, MAX_BYTES)
95
		if err != nil {
96
			fmt.Println("Encoding error,", err)
97
			return
98
		}
99
		if !voice.Ready || voice.OpusSend == nil {
100
			fmt.Printf("Discordgo not ready for opus packets. %+v : %+v", voice.Ready, voice.OpusSend)
101
			return
102
		}
103
		voice.OpusSend <- opus
104
	}
105
}
106
107
// PlayYoutube starts playing song from youtube
108
func (connection *Connection) PlayYoutube(ffmpeg *exec.Cmd) error {
109
	if connection.playing {
110
		return errors.New("song already playing")
111
	}
112
	connection.stopRunning = false
113
	out, err := ffmpeg.StdoutPipe()
114
	if err != nil {
115
		return err
116
	}
117
	buffer := bufio.NewReaderSize(out, 16384)
118
	err = ffmpeg.Start()
119
	if err != nil {
120
		return err
121
	}
122
	connection.playing = true
123
	defer func() {
124
		connection.playing = false
125
	}()
126
	_=connection.voiceConnection.Speaking(true)
127
	defer func() {_=connection.voiceConnection.Speaking(false)}()
128
	if connection.send == nil {
129
		connection.send = make(chan []int16, 2)
130
	}
131
	go connection.sendPCM(connection.voiceConnection, connection.send)
132
	for {
133
		if connection.stopRunning {
134
			_=ffmpeg.Process.Kill()
135
			break
136
		}
137
		audioBuffer := make([]int16, FRAME_SIZE*CHANNELS)
138
		err = binary.Read(buffer, binary.LittleEndian, &audioBuffer)
139
		if err == io.EOF || err == io.ErrUnexpectedEOF {
140
			return nil
141
		}
142
		if err != nil {
143
			return err
144
		}
145
		connection.send <- audioBuffer
146
	}
147
	return nil
148
}
149
150
// Stop stops playback
151
func (connection *Connection) Stop() {
152
	connection.stopRunning = true
153
	connection.playing = false
154
}
155