Browse Source

Downloader

HLS: retry policy
Alexey Kim 10 months ago
parent
commit
ffe8d3f9bc
1 changed files with 54 additions and 13 deletions
  1. 54 13
      internal/download/hls.go

+ 54 - 13
internal/download/hls.go

@@ -9,32 +9,42 @@ import (
 	"net/http"
 	"net/url"
 	"strings"
+	"sync/atomic"
 	"time"
 )
 
+type HLSOptions struct {
+	Timeout     time.Duration
+	MaxAttempts int
+}
+
 type HLS struct {
-	ctx          context.Context
-	ctxClose     context.CancelFunc
-	runtimeClose chan any
+	ctx      context.Context
+	ctxClose context.CancelFunc
+	exit     chan any
 
+	options HLSOptions
 	client  *http.Client
 	request *http.Request
+
+	retryAttempts atomic.Uint32
 }
 
-func NewHLS(r func() (*http.Request, error), t time.Duration) (*HLS, error) {
+func NewHLS(r func() (*http.Request, error), opts HLSOptions) (*HLS, error) {
 	if r == nil {
 		return nil, errors.New("illegal state: empty requester")
 	}
 
 	var (
 		d = HLS{
+			options: opts,
 			client: &http.Client{
 				Transport: func() *http.Transport {
 					transport := http.DefaultTransport.(*http.Transport)
 					transport.Proxy = http.ProxyFromEnvironment
 					return transport
 				}(),
-				Timeout: t,
+				Timeout: opts.Timeout,
 			},
 		}
 		err error
@@ -55,15 +65,12 @@ func (d *HLS) Close() error {
 		d.ctxClose()
 	}
 
-	if d.runtimeClose != nil {
-		d.runtimeClose <- 0
-	}
-
+	d.exit <- 0
 	return nil
 }
 
 func (d *HLS) Start() chan []byte {
-	d.runtimeClose = make(chan any)
+	d.exit = make(chan any)
 
 	var (
 		c         = make(chan []byte)
@@ -77,12 +84,15 @@ func (d *HLS) Start() chan []byte {
 
 		for {
 			select {
-			case <-d.runtimeClose:
+			case <-d.exit:
 				c <- nil
 				return
 			default:
 				if medialist, err = d.getMediaList(); err != nil {
-					log.Debug().Err(err).Msg("could not retrieve m3u8 playlist")
+					log.Debug().
+						Err(err).
+						Msg("could not retrieve m3u8 playlist")
+
 					c <- nil
 					return
 				}
@@ -160,6 +170,37 @@ func (d *HLS) Start() chan []byte {
 	return c
 }
 
+func (d *HLS) requestMediaList(request *http.Request) (*http.Response, error) {
+	var (
+		response *http.Response
+		err      error
+	)
+
+	response, err = d.client.Do(request)
+	if err != nil && errors.Is(err, http.ErrHandlerTimeout) {
+		if d.retryAttempts.Load() == uint32(d.options.MaxAttempts) {
+			return nil, err
+		}
+
+		select {
+		case <-time.After(time.Second):
+		}
+
+		if d.options.MaxAttempts != -1 {
+			d.retryAttempts.Add(1)
+		}
+
+		return d.requestMediaList(request)
+	}
+
+	if err != nil {
+		return nil, err
+	}
+
+	d.retryAttempts.Store(0)
+	return response, nil
+}
+
 func (d *HLS) getMediaList() (*m3u8.MediaPlaylist, error) {
 	var (
 		request  *http.Request
@@ -171,7 +212,7 @@ func (d *HLS) getMediaList() (*m3u8.MediaPlaylist, error) {
 	)
 
 	request = d.request.Clone(d.ctx)
-	if response, err = d.client.Do(request); err != nil {
+	if response, err = d.requestMediaList(request); err != nil {
 		return nil, err
 	}