github.com/0chain/gosdk@v1.17.11/wasmsdk/player_stream.go (about) 1 //go:build js && wasm 2 // +build js,wasm 3 4 package main 5 6 import ( 7 "context" 8 "fmt" 9 "path/filepath" 10 "strings" 11 "sync" 12 "time" 13 14 "github.com/0chain/gosdk/core/sys" 15 "github.com/0chain/gosdk/wasmsdk/jsbridge" 16 "github.com/0chain/gosdk/zboxcore/marker" 17 "github.com/0chain/gosdk/zboxcore/sdk" 18 ) 19 20 type StreamPlayer struct { 21 sync.RWMutex 22 allocationID string 23 remotePath string 24 authTicket string 25 lookupHash string 26 27 isViewer bool 28 allocationObj *sdk.Allocation 29 authTicketObj *marker.AuthTicket 30 31 waitingToDownloadFiles chan sdk.PlaylistFile 32 latestWaitingToDownloadFile sdk.PlaylistFile 33 34 downloadedFiles chan []byte 35 ctx context.Context 36 cancel context.CancelFunc 37 prefetchQty int 38 39 timer jsbridge.Timer 40 } 41 42 func (p *StreamPlayer) Start() error { 43 if p.cancel != nil { 44 p.cancel() 45 } 46 47 p.ctx, p.cancel = context.WithCancel(context.TODO()) 48 p.waitingToDownloadFiles = make(chan sdk.PlaylistFile, p.prefetchQty) 49 p.downloadedFiles = make(chan []byte, p.prefetchQty) 50 51 p.timer = *jsbridge.NewTimer(5*time.Second, p.reloadList) 52 p.timer.Start() 53 54 go p.reloadList() 55 go p.startDownload() 56 57 return nil 58 } 59 60 func (p *StreamPlayer) Stop() { 61 if p.cancel != nil { 62 p.timer.Stop() 63 p.cancel() 64 p.cancel = nil 65 } 66 67 close(p.downloadedFiles) 68 } 69 70 func (p *StreamPlayer) download(it sdk.PlaylistFile) { 71 wg := &sync.WaitGroup{} 72 statusBar := &StatusBar{wg: wg, totalBytesMap: make(map[string]int)} 73 wg.Add(1) 74 75 fileName := it.Name 76 localPath := filepath.Join(p.allocationID, fileName) 77 78 fs, _ := sys.Files.Open(localPath) 79 mf, _ := fs.(*sys.MemFile) 80 81 downloader, err := sdk.CreateDownloader(p.allocationID, localPath, it.Path, 82 sdk.WithAllocation(p.allocationObj), 83 sdk.WithAuthticket(p.authTicket, p.lookupHash), 84 sdk.WithFileHandler(mf)) 85 86 if err != nil { 87 PrintError(err.Error()) 88 return 89 } 90 91 defer sys.Files.Remove(localPath) //nolint 92 93 PrintInfo("playlist: downloading [", it.Path, "]") 94 err = downloader.Start(statusBar, true) 95 96 if err == nil { 97 wg.Wait() 98 } else { 99 PrintError("playlist: download failed.", err.Error()) 100 return 101 } 102 if !statusBar.success { 103 PrintError("playlist: download failed: unknown error") 104 return 105 } 106 107 PrintInfo("playlist: downloaded [", it.Path, "]") 108 109 withRecover(func() { 110 if p.downloadedFiles != nil { 111 p.downloadedFiles <- mf.Buffer 112 } 113 }) 114 } 115 116 func (p *StreamPlayer) startDownload() { 117 for { 118 select { 119 case <-p.ctx.Done(): 120 PrintInfo("playlist: download is cancelled") 121 close(p.waitingToDownloadFiles) 122 return 123 case it, ok := <-p.waitingToDownloadFiles: 124 if ok { 125 if strings.HasSuffix(it.Name, ".ts") { 126 p.download(it) 127 } 128 } 129 } 130 } 131 } 132 133 func (p *StreamPlayer) reloadList() { 134 135 // `waiting to download files` buffer is too less, try to load latest list from remote 136 if len(p.waitingToDownloadFiles) < p.prefetchQty { 137 138 list, err := p.loadList() 139 140 if err != nil { 141 PrintError(err.Error()) 142 return 143 } 144 145 PrintInfo("playlist: ", len(list)) 146 147 for _, it := range list { 148 PrintInfo("playlist: +", it.Path) 149 150 if !withRecover(func() { 151 if p.waitingToDownloadFiles != nil { 152 p.waitingToDownloadFiles <- it 153 } 154 }) { 155 // player is stopped 156 return 157 } 158 159 p.Lock() 160 p.latestWaitingToDownloadFile = it 161 p.Unlock() 162 } 163 } 164 } 165 166 func (p *StreamPlayer) loadList() ([]sdk.PlaylistFile, error) { 167 lookupHash := "" 168 169 p.RLock() 170 if p.latestWaitingToDownloadFile.Name != "" { 171 lookupHash = p.latestWaitingToDownloadFile.LookupHash 172 } 173 p.RUnlock() 174 175 if p.isViewer { 176 //get list from authticket 177 return sdk.GetPlaylistByAuthTicket(p.ctx, p.allocationObj, p.authTicket, p.lookupHash, lookupHash) 178 } 179 180 d, err := p.allocationObj.ListDir(p.remotePath) 181 if err != nil { 182 return nil, err 183 } 184 fmt.Printf("dir: %+v\n", d) 185 return []sdk.PlaylistFile{ 186 sdk.PlaylistFile{ 187 Name: d.Name, 188 Path: d.Path, 189 LookupHash: d.LookupHash, 190 NumBlocks: d.ActualNumBlocks, 191 Size: d.Size, 192 MimeType: d.MimeType, 193 Type: d.Type, 194 }, 195 }, nil 196 197 // return []sdk.PlaylistFile{}, nil 198 //get list from remote allocations's path 199 // return sdk.GetPlaylist(p.ctx, p.allocationObj, p.remotePath, lookupHash) 200 } 201 202 func (p *StreamPlayer) GetNext() []byte { 203 b, ok := <-p.downloadedFiles 204 if ok { 205 return b 206 } 207 208 return nil 209 } 210 211 // createStreamPalyer create player for remotePath 212 func createStreamPalyer(allocationID, remotePath, authTicket, lookupHash string) (*StreamPlayer, error) { 213 214 player := &StreamPlayer{} 215 player.prefetchQty = 3 216 player.remotePath = remotePath 217 player.authTicket = authTicket 218 player.lookupHash = lookupHash 219 220 //player is viewer 221 if len(authTicket) > 0 { 222 //player is viewer via shared authticket 223 at, err := sdk.InitAuthTicket(authTicket).Unmarshall() 224 225 if err != nil { 226 PrintError(err) 227 return nil, err 228 } 229 230 allocationObj, err := sdk.GetAllocationFromAuthTicket(authTicket) 231 if err != nil { 232 PrintError("Error fetching the allocation", err) 233 return nil, err 234 } 235 236 player.isViewer = true 237 player.allocationObj = allocationObj 238 player.authTicketObj = at 239 player.lookupHash = at.FilePathHash 240 241 return player, nil 242 243 } 244 245 if len(allocationID) == 0 { 246 return nil, RequiredArg("allocationID") 247 } 248 249 allocationObj, err := sdk.GetAllocation(allocationID) 250 if err != nil { 251 PrintError("Error fetching the allocation", err) 252 return nil, err 253 } 254 255 player.isViewer = false 256 player.allocationObj = allocationObj 257 258 return player, nil 259 }