package playback import ( "context" "time" ) // attemptFunc returns whether useful media was received before the attempt ended. type attemptFunc func(context.Context) (becameStable bool, err error) type retryDecider func(error) bool type waitFunc func(context.Context, time.Duration) error func waitForRetry(ctx context.Context, delay time.Duration) error { timer := time.NewTimer(delay) defer timer.Stop() select { case <-timer.C: return nil case <-ctx.Done(): return ctx.Err() } } func runWithRetry( ctx context.Context, policy RetryPolicy, attempt attemptFunc, shouldRetry retryDecider, wait waitFunc, observer retryObserver, ) error { failedAttempts := 0 for { becameStable, err := attempt(ctx) if err == nil { return nil } if ctx.Err() != nil { return ctx.Err() } if becameStable { failedAttempts = 0 } failedAttempts++ willRetry := shouldRetry(err) && policy.canRetry(failedAttempts) if !willRetry { if observer != nil { observer(retryEvent{ FailedAttempts: failedAttempts, Err: err, WillRetry: false, }) } return err } delay := policy.retryDelay(failedAttempts) if observer != nil { observer(retryEvent{ FailedAttempts: failedAttempts, Err: err, RetryIn: delay, WillRetry: true, }) } if err := wait(ctx, delay); err != nil { if ctx.Err() != nil { return ctx.Err() } return err } } } type retryEvent struct { FailedAttempts int Err error RetryIn time.Duration WillRetry bool } type retryObserver func(retryEvent)