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 }