github.com/puellanivis/breton@v0.2.16/lib/net/dash/mpd.go (about)

     1  package dash
     2  
     3  import (
     4  	"context"
     5  	"encoding/xml"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/puellanivis/breton/lib/files"
    10  	"github.com/puellanivis/breton/lib/net/dash/mpd"
    11  )
    12  
    13  func readMPD(ctx context.Context, manifest string) (*mpd.MPD, error) {
    14  	b, err := files.Read(ctx, manifest)
    15  	if err != nil {
    16  		return nil, err
    17  	}
    18  
    19  	m := new(mpd.MPD)
    20  	if err := xml.Unmarshal(b, m); err != nil {
    21  		return nil, err
    22  	}
    23  
    24  	return m, nil
    25  }
    26  
    27  type cachedMPD struct {
    28  	sync.RWMutex
    29  
    30  	expTime time.Duration
    31  	expired <-chan struct{}
    32  	err     error
    33  
    34  	manifest string
    35  	*mpd.MPD
    36  }
    37  
    38  func newMPD(manifest string, minimumUpdatePeriod time.Duration) *cachedMPD {
    39  	expire := make(chan struct{})
    40  	close(expire)
    41  
    42  	return &cachedMPD{
    43  		expTime: minimumUpdatePeriod,
    44  		expired: expire,
    45  
    46  		manifest: manifest,
    47  		MPD:      new(mpd.MPD),
    48  	}
    49  }
    50  
    51  func (m *cachedMPD) refresh(ctx context.Context) (*mpd.MPD, error) {
    52  	m.Lock()
    53  	defer m.Unlock()
    54  
    55  	// we weren’t locked before, so we have to check again
    56  	select {
    57  	case <-m.expired:
    58  		// still expired, continuing on to refresh
    59  
    60  	case <-ctx.Done():
    61  		return nil, ctx.Err()
    62  
    63  	// someone else refreshed the channel while we were waiting…
    64  	// so, we get the return values gratis
    65  	default:
    66  		return m.MPD, m.err
    67  	}
    68  
    69  	m.MPD, m.err = readMPD(ctx, m.manifest)
    70  	if m.err != nil {
    71  		return nil, m.err
    72  	}
    73  
    74  	expire := make(chan struct{})
    75  	m.expired = expire
    76  
    77  	go func() {
    78  		// using closure, no one else can close this channel
    79  		defer close(expire)
    80  
    81  		select {
    82  		case <-time.After(m.expTime):
    83  		case <-ctx.Done():
    84  		}
    85  	}()
    86  
    87  	return m.MPD, m.err
    88  }
    89  
    90  func (m *cachedMPD) get(ctx context.Context) (*mpd.MPD, error) {
    91  	select {
    92  	case <-m.expired:
    93  		return m.refresh(ctx)
    94  	case <-ctx.Done():
    95  		return nil, ctx.Err()
    96  	default:
    97  	}
    98  
    99  	m.RLock()
   100  	defer m.RUnlock()
   101  
   102  	return m.MPD, m.err
   103  }