Skip to content

Commit

Permalink
fix(command): fixed problem with spawning zombie process in spock com…
Browse files Browse the repository at this point in the history
…mand
  • Loading branch information
Wittano committed Oct 26, 2023
1 parent 97d55fb commit 3da769e
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 7 deletions.
1 change: 0 additions & 1 deletion cmd/komputer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ func init() {
})
}

// TODO Export registration commends to CLI tool
func init() {
checkEnvVariables("APPLICATION_ID", "SERVER_GUID")

Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ require (
)

require (
github.com/bwmarrin/dgvoice v0.0.0-20210225172318-caaac756e02e // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
Expand Down
2 changes: 1 addition & 1 deletion internal/assets/audio.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func GetAudioPaths(filename string) ([]string, error) {
return nil, err
}

var paths = make([]string, len(ls))
var paths []string

for _, d := range ls {
fName := d.Name()
Expand Down
8 changes: 4 additions & 4 deletions internal/command/spock.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"github.com/bwmarrin/dgvoice"
"github.com/bwmarrin/discordgo"
"github.com/wittano/komputer/internal/assets"
"github.com/wittano/komputer/internal/interaction"
Expand Down Expand Up @@ -58,9 +57,10 @@ func execSpookSpeak(ctx context.Context, s *discordgo.Session, i *discordgo.Inte
ch := make(chan bool)
SpockMusicStopCh[i.GuildID] = ch

// TODO Rewrite this function cause ffmpeg generate zombie process
// TODO Add multi-server playing same song
dgvoice.PlayAudioFile(voiceJoin, path[rand.Int()%len(path)], ch)
songPath := path[rand.Int()%len(path)]
if err = voice.PlayAudio(voiceJoin, songPath, ch); err != nil {
log.Error(ctx, fmt.Sprintf("Failed play '%s' audio", songPath), err)
}

voiceJoin.Disconnect()
}()
Expand Down
126 changes: 126 additions & 0 deletions internal/voice/audio.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package voice

import (
"bufio"
"encoding/binary"
"errors"
"github.com/bwmarrin/discordgo"
"io"
"layeh.com/gopus"
"os/exec"
"strconv"
"syscall"
)

const (
channels int = 2 // 1 for mono, 2 for stereo
frameRate int = 48000 // audio sampling rate
frameSize int = 960 // uint16 size of each audio frame
maxBytes = (frameSize * 2) * 2 // max size of opus data
)

func PlayAudio(vc *discordgo.VoiceConnection, path string, stop <-chan bool) (err error) {
cmd := exec.Command("ffmpeg", "-i", path, "-f", "s16le", "-ar", strconv.Itoa(frameRate), "-ac", strconv.Itoa(channels), "pipe:1")
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}

ffmpegout, err := cmd.StdoutPipe()
if err != nil {
return
}

ffmpegbuf := bufio.NewReaderSize(ffmpegout, 16384)

// Starts the ffmpeg command
err = cmd.Start()
if err != nil {
return
}

defer cmd.Wait()

//when stop is sent, kill ffmpeg
go func() {
<-stop
cmd.Process.Kill()
}()

// Send "speaking" packet over the voice websocket
err = vc.Speaking(true)
if err != nil {
return
}

// Send not "speaking" packet over the websocket when we finish
defer func() {
err := vc.Speaking(false)
if err != nil {
return
}
}()

send := make(chan []int16, 2)
defer close(send)

stopPlaying := make(chan bool)
go func() {
sendPCM(vc, send)
stopPlaying <- true
}()

for {
// read data from ffmpeg stdout
audiobuf := make([]int16, frameSize*channels)
err = binary.Read(ffmpegbuf, binary.LittleEndian, &audiobuf)
if err == io.EOF || err == io.ErrUnexpectedEOF {
return
}
if err != nil {
return
}

// Send received PCM to the sendPCM channel
select {
case send <- audiobuf:
case <-stopPlaying:
return
}
}
}

func sendPCM(v *discordgo.VoiceConnection, pcm chan []int16) (err error) {
if pcm == nil {
return nil
}

opusEncoder, err := gopus.NewEncoder(frameRate, channels, gopus.Audio)

if err != nil {
return
}

for {

// read pcm from chan, exit if channel is closed.
recv, ok := <-pcm
if !ok {
return errors.New("PCM Channel closed")
}

// try encoding pcm frame with Opus
opus, err := opusEncoder.Encode(recv, frameSize, maxBytes)
if err != nil {
return err
}

if v.Ready == false || v.OpusSend == nil {
// Sending errors here might not be suited
return nil
}
// send encoded opus data to the sendOpus channel
v.OpusSend <- opus
}

return
}

0 comments on commit 3da769e

Please sign in to comment.