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  }