Files
icecast-ripper/internal/scheduler/scheduler.go
Dmitry Andreev b5f23a50b7
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
Docker / build (push) Has been cancelled
golangci-lint / lint (push) Has been cancelled
Integrate MP3 duration extraction and silence handling (#6)
* feat: integrate MP3 duration extraction and silence handling

- Added a new dependency on github.com/tcolgate/mp3 for MP3 frame decoding.
- Implemented actual MP3 duration retrieval in the scanRecordings function, falling back to an estimation if retrieval fails.
- Introduced a silent frame asset for generating silence in audio streams.
- Created a silence reader to provide a continuous stream of silent frames.
- Added necessary documentation and licensing for the new MP3 package.
- Updated .gitignore to exclude MP3 files except for the internal data directory.

* feat: update README and improve application configuration and logging

* refactor: remove unused GenerateFileHash function and improve resource cleanup in MP3 and recorder modules
2025-04-13 15:56:22 +03:00

95 lines
2.1 KiB
Go

package scheduler
import (
"context"
"log/slog"
"sync"
"time"
"github.com/kemko/icecast-ripper/internal/recorder"
"github.com/kemko/icecast-ripper/internal/streamchecker"
)
// Scheduler periodically checks if a stream is live and starts recording
type Scheduler struct {
interval time.Duration
checker *streamchecker.Checker
recorder *recorder.Recorder
stopChan chan struct{}
stopOnce sync.Once
wg sync.WaitGroup
parentContext context.Context
}
// New creates a scheduler instance
func New(interval time.Duration, checker *streamchecker.Checker, recorder *recorder.Recorder) *Scheduler {
return &Scheduler{
interval: interval,
checker: checker,
recorder: recorder,
stopChan: make(chan struct{}),
}
}
// Start begins the scheduling process
func (s *Scheduler) Start(ctx context.Context) {
slog.Info("Starting scheduler", "interval", s.interval.String())
s.parentContext = ctx
s.wg.Add(1)
go s.run()
}
// Stop gracefully shuts down the scheduler
func (s *Scheduler) Stop() {
s.stopOnce.Do(func() {
slog.Info("Stopping scheduler...")
close(s.stopChan)
s.wg.Wait()
slog.Info("Scheduler stopped")
})
}
func (s *Scheduler) run() {
defer s.wg.Done()
ticker := time.NewTicker(s.interval)
defer ticker.Stop()
// Initial check immediately on start
s.checkAndRecord()
for {
select {
case <-ticker.C:
s.checkAndRecord()
case <-s.stopChan:
return
case <-s.parentContext.Done():
slog.Info("Parent context cancelled, stopping scheduler")
return
}
}
}
func (s *Scheduler) checkAndRecord() {
if s.recorder.IsRecording() {
slog.Debug("Recording in progress, skipping stream check")
return
}
isLive, err := s.checker.IsLiveWithContext(s.parentContext)
if err != nil {
slog.Warn("Error checking stream status", "error", err)
return
}
if isLive {
slog.Info("Stream is live, starting recording")
if err := s.recorder.StartRecording(s.parentContext, s.checker.GetStreamURL()); err != nil {
slog.Warn("Failed to start recording", "error", err)
}
} else {
slog.Debug("Stream is not live")
}
}