Pause playback while transmitting and declare microphone use.
macOS refuses capture without a usage string, and queued remote audio was still handed to the speaker after push-to-talk began. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -23,7 +23,7 @@ func (a *App) startup(ctx context.Context) {
|
|||||||
a.station.Fail("could not open the audio device")
|
a.station.Fail("could not open the audio device")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go func() { _ = a.station.Run(runCtx) }()
|
go func() { _ = a.station.Run(runCtx) }() // Run returns when the window closes.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) shutdown(context.Context) {
|
func (a *App) shutdown(context.Context) {
|
||||||
|
|||||||
@@ -23,6 +23,8 @@
|
|||||||
<string>true</string>
|
<string>true</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>{{.Info.Copyright}}</string>
|
<string>{{.Info.Copyright}}</string>
|
||||||
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
|
<string>funknetz uses the microphone to answer the talk group.</string>
|
||||||
{{if .Info.FileAssociations}}
|
{{if .Info.FileAssociations}}
|
||||||
<key>CFBundleDocumentTypes</key>
|
<key>CFBundleDocumentTypes</key>
|
||||||
<array>
|
<array>
|
||||||
|
|||||||
Vendored
-10
@@ -84,15 +84,5 @@ window.addEventListener("keyup", function (event) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener("blur", function () {
|
|
||||||
spaceHeld = false;
|
|
||||||
const app = api();
|
|
||||||
if (!app) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
app.ReleaseButton();
|
|
||||||
app.ReleaseSpace();
|
|
||||||
});
|
|
||||||
|
|
||||||
setInterval(refresh, 100);
|
setInterval(refresh, 100);
|
||||||
refresh();
|
refresh();
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// FrameSamples is the 20 ms frame at 16 kHz, which is what the reflector carries.
|
||||||
const FrameSamples = 320
|
const FrameSamples = 320
|
||||||
|
|
||||||
// Encoder produces one raw Opus packet per 20 ms frame.
|
// Encoder produces one raw Opus packet per 20 ms frame.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
//go:build !bindings
|
//go:build !bindings
|
||||||
|
|
||||||
|
// Command funknetz is the headset station window.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
//go:build bindings
|
//go:build bindings
|
||||||
|
|
||||||
|
// Command funknetz is the headset station window.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -360,6 +360,32 @@ func TestRemoteAudioAndTalker(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPlaybackPausesWhenTransmitting(t *testing.T) {
|
||||||
|
in := login(t, "DL1ABC", "key", 20, 7, []string{"OPUS"})
|
||||||
|
enc, err := opus.NewEncoder()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer enc.Close()
|
||||||
|
pkt, err := enc.Encode(make([]int16, opus.FrameSamples))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
in.refl.writeUDP(t, 101, 7, 1, appendBytes(nil, pkt))
|
||||||
|
in.refl.writeUDP(t, 101, 7, 2, appendBytes(nil, pkt))
|
||||||
|
if _, err := in.st.WaitPlayback(t.Context(), 2*opus.FrameSamples-1); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
in.st.HoldButton()
|
||||||
|
in.refl.writeFrame(t, 105, appendStr(appendU32(nil, 20), "DB0XYZ"))
|
||||||
|
if _, err := in.st.Wait(t.Context(), func(s station.State) bool { return s.Transmitting }); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if pcm := in.st.TakePlayback(); len(pcm) != 0 {
|
||||||
|
t.Fatalf("playback %d samples, want none while transmitting", len(pcm))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMicrophoneIsNotPlayed(t *testing.T) {
|
func TestMicrophoneIsNotPlayed(t *testing.T) {
|
||||||
in := login(t, "DL1ABC", "key", 20, 7, []string{"OPUS"})
|
in := login(t, "DL1ABC", "key", 20, 7, []string{"OPUS"})
|
||||||
in.st.HoldButton()
|
in.st.HoldButton()
|
||||||
|
|||||||
+7
-4
@@ -163,12 +163,12 @@ func (s *Station) session(ctx context.Context, conn net.Conn) error {
|
|||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
timer.Stop()
|
timer.Stop()
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
case <-tcpErr:
|
case err := <-tcpErr:
|
||||||
timer.Stop()
|
timer.Stop()
|
||||||
return errors.New("tcp closed")
|
return err
|
||||||
case <-udpFail:
|
case err := <-udpFail:
|
||||||
timer.Stop()
|
timer.Stop()
|
||||||
return errors.New("udp closed")
|
return err
|
||||||
case tcpf = <-tcpCh:
|
case tcpf = <-tcpCh:
|
||||||
timer.Stop()
|
timer.Stop()
|
||||||
reason = "tcp"
|
reason = "tcp"
|
||||||
@@ -465,12 +465,14 @@ func (s *Station) endTransmit() {
|
|||||||
copy(frame, s.mic)
|
copy(frame, s.mic)
|
||||||
s.mic = nil
|
s.mic = nil
|
||||||
if pkt, err := s.enc.Encode(frame); err == nil {
|
if pkt, err := s.enc.Encode(frame); err == nil {
|
||||||
|
// A lost datagram ends the session when the receive timer expires.
|
||||||
_ = s.sendUDP(udpAudio, appendBlob(nil, pkt))
|
_ = s.sendUDP(udpAudio, appendBlob(nil, pkt))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s.mic = nil
|
s.mic = nil
|
||||||
}
|
}
|
||||||
if s.udp != nil {
|
if s.udp != nil {
|
||||||
|
// A lost datagram ends the session when the receive timer expires.
|
||||||
_ = s.sendUDP(udpFlush, nil)
|
_ = s.sendUDP(udpFlush, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -511,6 +513,7 @@ func (s *Station) publish() {
|
|||||||
s.state.Transmitting = tx
|
s.state.Transmitting = tx
|
||||||
if tx {
|
if tx {
|
||||||
s.state.Talker = s.cfg.callsign
|
s.state.Talker = s.cfg.callsign
|
||||||
|
s.play = nil
|
||||||
} else {
|
} else {
|
||||||
s.state.Talker = s.remoteTalker
|
s.state.Talker = s.remoteTalker
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ func (s *Station) WriteMicrophone(pcm []int16) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// A lost datagram ends the session when the receive timer expires.
|
||||||
_ = s.sendUDP(udpAudio, appendBlob(nil, pkt))
|
_ = s.sendUDP(udpAudio, appendBlob(nil, pkt))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,6 +160,10 @@ func (s *Station) WriteMicrophone(pcm []int16) {
|
|||||||
func (s *Station) TakePlayback() []int16 {
|
func (s *Station) TakePlayback() []int16 {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
if s.transmitting() {
|
||||||
|
s.play = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
out := s.play
|
out := s.play
|
||||||
s.play = nil
|
s.play = nil
|
||||||
return out
|
return out
|
||||||
|
|||||||
Reference in New Issue
Block a user