Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

clients: add audio notes #660

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/noaudio.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Build CGOless brclient
on: [push, pull_request]
permissions:
contents: read

jobs:
build:
name: Build CGOless brclient
runs-on: ubuntu-latest
strategy:
matrix:
go: ["1.23"]
steps:
- name: Set up Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with:
go-version: ${{ matrix.go }}
- name: Check out source
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Build
run: go build ./...
env:
CGO_ENABLED: 0
9 changes: 9 additions & 0 deletions brclient/appstate.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/companyzero/bisonrelay/client/resources/simplestore"
"github.com/companyzero/bisonrelay/client/rpcserver"
"github.com/companyzero/bisonrelay/clientrpc/types"
"github.com/companyzero/bisonrelay/internal/audio"
"github.com/companyzero/bisonrelay/internal/mdembeds"
"github.com/companyzero/bisonrelay/internal/strescape"
"github.com/companyzero/bisonrelay/internal/tlsconn"
Expand Down Expand Up @@ -220,6 +221,8 @@ type appState struct {
ssPayType simpleStorePayType
ssAcct string
ssShipCharge float64

noterec *audio.NoteRecorder
}

type appStateErr struct {
Expand Down Expand Up @@ -3946,6 +3949,11 @@ func newAppState(sendMsg func(tea.Msg), lndLogLines *sloglinesbuffer.Buffer,
})
go r.Run(ctx)

noterec, err := audio.NewRecorder(logBknd.logger("AREC"))
if err != nil {
return nil, fmt.Errorf("unable to init audio subsystem: %v", err)
}

ctx, cancel := context.WithCancel(context.Background())
as = &appState{
ctx: ctx,
Expand All @@ -3963,6 +3971,7 @@ func newAppState(sendMsg func(tea.Msg), lndLogLines *sloglinesbuffer.Buffer,
lnRouter: lnRouter,
httpClient: &httpClient,
rates: r,
noterec: noterec,

network: args.Network,
isRestore: isRestore,
Expand Down
196 changes: 196 additions & 0 deletions brclient/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/companyzero/bisonrelay/client"
"github.com/companyzero/bisonrelay/client/clientdb"
"github.com/companyzero/bisonrelay/client/clientintf"
"github.com/companyzero/bisonrelay/internal/audio"
"github.com/companyzero/bisonrelay/internal/strescape"
"github.com/companyzero/bisonrelay/zkidentity"
"github.com/decred/dcrd/dcrutil/v4"
Expand Down Expand Up @@ -3549,6 +3550,183 @@ var myAvatarCmds = []tuicmd{
},
}

var audioCmds = []tuicmd{
{
cmd: "devices",
aliases: []string{"listdevices"},
descr: "List capture and playback devices",
usableOffline: true,
handler: func(args []string, as *appState) error {
devices, err := audio.ListAudioDevices(as.log)
if err != nil {
return err
}

as.manyDiagMsgsCb(func(pf printf) {
printDevice := func(i int, dev *audio.Device) {
defaultStr := ""
if dev.IsDefault {
defaultStr = "(default) "
}
pf("Device %d %s%s", i, defaultStr, dev.Name)
pf("ID: %s", strescape.Nick(dev.ID))
pf("")
}

pf("")
if len(devices.Capture) == 0 {
pf("No audio capture devices found")
} else {
pf("Audio capture devices")
pf("")
for i := range devices.Capture {
printDevice(i, &devices.Capture[i])
}
}

if len(devices.Playback) == 0 {
pf("No audio playback devices found")
} else {
pf("Audio playback devices")
pf("")
for i := range devices.Playback {
printDevice(i, &devices.Playback[i])
}
}
})

return nil
},
}, {
cmd: "capturedevice",
usableOffline: true,
aliases: []string{"capdevice", "cdevice", "capdev"},
usage: "Select the capture device",
descr: "[<device index>]",
handler: func(args []string, as *appState) error {
if len(args) == 0 {
as.noterec.SetCaptureDevice(nil)
as.diagMsg("Using default device for audio capture")
return nil
}

devIndex, err := strconv.ParseInt(args[0], 10, 32)
if err != nil {
return usageError{msg: "Argument not a number"}
}
if devIndex < 0 {
return usageError{msg: "Device index cannot be negative"}
}

devices, err := audio.ListAudioDevices(as.log)
if err != nil {
return err
}
if devIndex >= int64(len(devices.Capture)) {
return fmt.Errorf("device %d does not exist", devIndex)
}

err = as.noterec.SetCaptureDevice(&devices.Capture[devIndex])
return err
},
}, {
cmd: "playbackdevice",
usableOffline: true,
aliases: []string{"playdevice", "pdevice", "playdev"},
usage: "Select the playback device",
descr: "[<device index>]",
handler: func(args []string, as *appState) error {
if len(args) == 0 {
as.noterec.SetPlaybackDevice(nil)
as.diagMsg("Using default device for audio capture")
return nil
}

devIndex, err := strconv.ParseInt(args[0], 10, 32)
if err != nil {
return usageError{msg: "Argument not a number"}
}
if devIndex < 0 {
return usageError{msg: "Device index cannot be negative"}
}

devices, err := audio.ListAudioDevices(as.log)
if err != nil {
return err
}
if devIndex >= int64(len(devices.Playback)) {
return fmt.Errorf("device %d does not exist", devIndex)
}

err = as.noterec.SetPlaybackDevice(&devices.Playback[devIndex])
return err
},
}, {
cmd: "send",
descr: "Send an audio note",
usage: "[<target>]",
handler: func(args []string, as *appState) error {
var targetId clientintf.UserID
var targetIsGC bool

if len(args) > 0 {
uid, err := as.c.UIDByNick(args[0])
if err == nil {
targetId = uid
} else if gcid, err := as.c.GCIDByName(args[0]); err == nil {
targetId = gcid
targetIsGC = true
} else {
return usageError{"Target user or GC not found"}
}
} else {
cw := as.activeChatWindow()
if cw == nil || cw.isPage {
return usageError{"No target specified"}
}
if cw.isGC {
targetId = cw.gc
targetIsGC = true
} else {
targetId = cw.uid
}
}

as.sendMsg(msgSendAudioNote{targetID: targetId, targetIsGC: targetIsGC})
return nil
},
completer: func(args []string, arg string, as *appState) []string {
if len(args) == 0 {
return nickCompleter(arg, as)
}
return nil
},
}, {
cmd: "test",
usableOffline: true,
descr: "Record and playback a 3-second test note",
handler: func(args []string, as *appState) error {
go func() {
as.diagMsg("Starting 3 second capture")
ctx, cancel := context.WithTimeout(as.ctx, 3*time.Second)
defer cancel()
err := as.noterec.Capture(ctx)
if err != nil {
as.diagMsg("Error capturing audio: %v", err)
return
}

as.diagMsg("Starting playback")
err = as.noterec.Playback(as.ctx)
if err != nil {
as.diagMsg("Error playing back audio: %v", err)
}
}()
return nil
},
},
}

var commands = []tuicmd{
{
cmd: "backup",
Expand Down Expand Up @@ -4279,6 +4457,12 @@ var commands = []tuicmd{
as.cwHelpMsg("Cleared payment stats%s", forUser)
return nil
},
completer: func(args []string, arg string, as *appState) []string {
if len(args) == 0 {
return nickCompleter(arg, as)
}
return nil
},
}, {
cmd: "info",
usableOffline: true,
Expand Down Expand Up @@ -4452,6 +4636,18 @@ var commands = []tuicmd{
}()
return nil
},
}, {
cmd: "audio",
usableOffline: true,
descr: "Audio-related commands",
sub: audioCmds,
completer: func(args []string, arg string, as *appState) []string {
if len(args) == 0 {
return cmdCompleter(audioCmds, arg, false)
}
return nil
},
handler: subcmdNeededHandler,
}, {
cmd: "quit",
usableOffline: true,
Expand Down
3 changes: 3 additions & 0 deletions brclient/mainwindow.go
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,9 @@ func (mws mainWindowState) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
mws.completeIdx = 0
}

case msgSendAudioNote:
return newSendAudioNoteWindow(mws.as, msg)

default:
// Handle other messages.
mws.textArea, cmd = mws.textArea.Update(msg)
Expand Down
19 changes: 19 additions & 0 deletions brclient/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ type msgUnwelcomeError struct {

type msgReplaceCmd string

type msgRecordNote struct{}
type msgPlaybackNote struct{}
type msgRefreshAudioNoteUI struct{}
type msgRecordComplete struct{}
type msgPlaybackComplete struct{}
type msgAudioError error

func paste() tea.Msg {
str, err := clipboard.ReadAll()
if err != nil {
Expand Down Expand Up @@ -194,6 +201,13 @@ func emitMsg(msg tea.Msg) tea.Cmd {
}
}

func emitAfter(msg tea.Msg, delay time.Duration) tea.Cmd {
return func() tea.Msg {
time.Sleep(delay)
return msg
}
}

type msgRunCmd func() tea.Msg

type msgExternalCommentResult struct {
Expand All @@ -202,6 +216,11 @@ type msgExternalCommentResult struct {
parent *zkidentity.ShortID
}

type msgSendAudioNote struct {
targetID clientintf.UserID
targetIsGC bool
}

// isQuitMsg returns true if the app should quit as a response to the given
// msg. It returns an error with the reason for quitting.
func isQuitMsg(msg tea.Msg) error {
Expand Down
Loading
Loading