Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

138 lines
2.9KB

  1. package aoiprxkit
  2. import (
  3. "context"
  4. "fmt"
  5. "sync"
  6. "time"
  7. )
  8. // StreamFinder keeps a live in-memory view of SAP/SDP announcements
  9. // and can wait for sessions by their SDP "s=" session name.
  10. type StreamFinder struct {
  11. listener *SAPListener
  12. mu sync.Mutex
  13. sessions map[string]SAPAnnouncement
  14. waiters map[string][]chan SAPAnnouncement
  15. }
  16. func NewStreamFinder(cfg SAPListenerConfig) (*StreamFinder, error) {
  17. sf := &StreamFinder{
  18. sessions: make(map[string]SAPAnnouncement),
  19. waiters: make(map[string][]chan SAPAnnouncement),
  20. }
  21. listener, err := NewSAPListener(cfg, sf.handleAnnouncement)
  22. if err != nil {
  23. return nil, err
  24. }
  25. sf.listener = listener
  26. return sf, nil
  27. }
  28. func (s *StreamFinder) Start(ctx context.Context) error {
  29. return s.listener.Start(ctx)
  30. }
  31. func (s *StreamFinder) Stop() error {
  32. return s.listener.Stop()
  33. }
  34. func (s *StreamFinder) handleAnnouncement(a SAPAnnouncement) {
  35. name := a.ParsedSDP.SessionName
  36. if name == "" {
  37. return
  38. }
  39. s.mu.Lock()
  40. defer s.mu.Unlock()
  41. if a.Delete {
  42. delete(s.sessions, name)
  43. return
  44. }
  45. s.sessions[name] = a
  46. if waiters := s.waiters[name]; len(waiters) > 0 {
  47. delete(s.waiters, name)
  48. for _, ch := range waiters {
  49. select {
  50. case ch <- a:
  51. default:
  52. }
  53. close(ch)
  54. }
  55. }
  56. }
  57. func (s *StreamFinder) FindByStreamName(name string) (SAPAnnouncement, bool) {
  58. s.mu.Lock()
  59. defer s.mu.Unlock()
  60. a, ok := s.sessions[name]
  61. return a, ok
  62. }
  63. func (s *StreamFinder) WaitForStreamName(ctx context.Context, name string) (SAPAnnouncement, error) {
  64. if name == "" {
  65. return SAPAnnouncement{}, fmt.Errorf("stream name must not be empty")
  66. }
  67. s.mu.Lock()
  68. if a, ok := s.sessions[name]; ok {
  69. s.mu.Unlock()
  70. return a, nil
  71. }
  72. ch := make(chan SAPAnnouncement, 1)
  73. s.waiters[name] = append(s.waiters[name], ch)
  74. s.mu.Unlock()
  75. select {
  76. case <-ctx.Done():
  77. s.mu.Lock()
  78. waiters := s.waiters[name]
  79. kept := waiters[:0]
  80. for _, w := range waiters {
  81. if w != ch {
  82. kept = append(kept, w)
  83. }
  84. }
  85. if len(kept) == 0 {
  86. delete(s.waiters, name)
  87. } else {
  88. s.waiters[name] = kept
  89. }
  90. s.mu.Unlock()
  91. return SAPAnnouncement{}, ctx.Err()
  92. case a := <-ch:
  93. return a, nil
  94. }
  95. }
  96. func (s *StreamFinder) WaitConfigByStreamName(ctx context.Context, base Config, name string) (Config, SAPAnnouncement, error) {
  97. a, err := s.WaitForStreamName(ctx, name)
  98. if err != nil {
  99. return Config{}, SAPAnnouncement{}, err
  100. }
  101. cfg, err := ConfigFromSDP(base, a.ParsedSDP)
  102. if err != nil {
  103. return Config{}, SAPAnnouncement{}, err
  104. }
  105. return cfg, a, nil
  106. }
  107. func (s *StreamFinder) Snapshot() []SAPAnnouncement {
  108. s.mu.Lock()
  109. defer s.mu.Unlock()
  110. out := make([]SAPAnnouncement, 0, len(s.sessions))
  111. for _, v := range s.sessions {
  112. out = append(out, v)
  113. }
  114. return out
  115. }
  116. func (s *StreamFinder) WaitForStreamNameTimeout(name string, timeout time.Duration) (SAPAnnouncement, error) {
  117. ctx, cancel := context.WithTimeout(context.Background(), timeout)
  118. defer cancel()
  119. return s.WaitForStreamName(ctx, name)
  120. }