github.com/0chain/gosdk@v1.17.11/winsdk/stream.go (about) 1 package main 2 3 /* 4 #include <stdlib.h> 5 */ 6 import ( 7 "C" 8 ) 9 10 import ( 11 "fmt" 12 "math" 13 "net/http" 14 "net/http/httptest" 15 "os" 16 "path/filepath" 17 "strconv" 18 "strings" 19 "sync" 20 21 "github.com/0chain/gosdk/zboxcore/sdk" 22 lru "github.com/hashicorp/golang-lru/v2" 23 ) 24 25 var ( 26 numBlocks int = 100 27 sizePerRequest int64 = sdk.CHUNK_SIZE * int64(numBlocks) // 6400K 100 blocks 28 server *httptest.Server 29 streamAllocationID string 30 cachedDownloadedBlocks, _ = lru.New[string, []byte](1000) 31 ) 32 33 // StartStreamServer - start local media stream server 34 // ## Inputs 35 // - allocationID 36 // 37 // ## Outputs 38 // 39 // { 40 // "error":"", 41 // "result":"http://127.0.0.1:4313/", 42 // } 43 // 44 //export StartStreamServer 45 func StartStreamServer(allocationID *C.char) *C.char { 46 allocID := C.GoString(allocationID) 47 48 handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 49 streamingMedia(w, r) 50 }) 51 52 if server != nil { 53 server.Close() 54 } 55 56 server = httptest.NewServer(handler) 57 streamAllocationID = allocID 58 log.Info("win: ", server.URL) 59 return WithJSON(server.URL, nil) 60 } 61 62 func streamingMedia(w http.ResponseWriter, req *http.Request) { 63 64 remotePath := req.URL.Path 65 log.Info("win: start streaming media: ", streamAllocationID, remotePath) 66 67 f, err := getFileMeta(streamAllocationID, remotePath) 68 if err != nil { 69 http.Error(w, err.Error(), 500) 70 return 71 } 72 73 w.Header().Set("Content-Type", f.MimeType) 74 75 rangeHeader := req.Header.Get("Range") 76 77 log.Info("win: range: ", rangeHeader, " mimetype: ", f.MimeType, " numBlocks:", f.NumBlocks, " ActualNumBlocks:", f.ActualNumBlocks, " ActualFileSize:", f.ActualFileSize) 78 // we can simply hint Chrome to send serial range requests for media file by 79 // 80 // if rangeHeader == "" { 81 // w.Header().Set("Accept-Ranges", "bytes") 82 // w.Header().Set("Content-Length", strconv.FormatInt(size, 10)) 83 // w.WriteHeader(200) 84 // fmt.Printf("hint browser to send range requests, total size: %d\n", size) 85 // return 86 // } 87 // 88 // but this not worked for Safari and Firefox 89 if rangeHeader == "" { 90 ra := httpRange{ 91 start: 0, 92 length: sizePerRequest, 93 total: f.ActualFileSize, 94 } 95 w.Header().Set("Accept-Ranges", "bytes") 96 97 w.Header().Set("Content-Range", ra.Header()) 98 99 w.WriteHeader(http.StatusPartialContent) 100 101 if req.Method != "HEAD" { 102 buf, err := downloadBlocks(remotePath, f, ra) 103 if err != nil { 104 http.Error(w, err.Error(), 500) 105 } 106 107 written, err := w.Write(buf) 108 109 if err != nil { 110 http.Error(w, err.Error(), 500) 111 } 112 113 w.Header().Set("Content-Length", strconv.Itoa(written)) 114 115 for k, v := range w.Header() { 116 log.Info("win: response ", k, " = ", v[0]) 117 } 118 119 } else { 120 w.Header().Set("Content-Length", "0") 121 } 122 return 123 } 124 125 ranges, err := parseRange(rangeHeader, f.ActualFileSize) 126 if err != nil { 127 http.Error(w, err.Error(), 400) 128 return 129 } 130 131 // multi-part requests are not supported 132 if len(ranges) > 1 { 133 http.Error(w, "unsupported multi-part", http.StatusRequestedRangeNotSatisfiable) 134 return 135 } 136 137 ra := ranges[0] 138 139 w.Header().Set("Accept-Ranges", "bytes") 140 w.Header().Set("Content-Range", ra.Header()) 141 142 w.WriteHeader(http.StatusPartialContent) 143 144 if req.Method != "HEAD" { 145 146 buf, err := downloadBlocks(remotePath, f, ra) 147 148 if err != nil { 149 http.Error(w, err.Error(), 500) 150 } 151 152 written, err := w.Write(buf) 153 if err != nil { 154 http.Error(w, err.Error(), 500) 155 } 156 w.Header().Set("Content-Length", strconv.Itoa(written)) 157 158 for k, v := range w.Header() { 159 log.Info("win: response ", k, " = ", v[0]) 160 } 161 } else { 162 w.Header().Set("Content-Length", "0") 163 } 164 } 165 166 func downloadBlocks(remotePath string, f *sdk.ConsolidatedFileMeta, ra httpRange) ([]byte, error) { 167 defer func() { 168 if r := recover(); r != nil { 169 log.Error("win: ", r) 170 } 171 }() 172 alloc, err := getAllocation(streamAllocationID) 173 174 if err != nil { 175 return nil, err 176 } 177 var startBlock int64 178 179 if ra.start == 0 { 180 startBlock = 1 181 } else { 182 startBlock = int64(math.Floor(float64(ra.start)/float64(sdk.CHUNK_SIZE)/float64(alloc.DataShards))) + 1 183 } 184 185 if startBlock > f.NumBlocks { 186 startBlock = f.NumBlocks 187 } 188 189 blocks := int(math.Ceil(float64(ra.length) / float64(sdk.CHUNK_SIZE) / float64(alloc.DataShards))) 190 191 endBlock := startBlock + int64(blocks) 192 193 if endBlock > f.NumBlocks { 194 endBlock = f.NumBlocks 195 } 196 197 if startBlock == endBlock { 198 endBlock = 0 199 } 200 201 offset := 0 202 blockStart := (startBlock - 1) * sdk.CHUNK_SIZE * int64(alloc.DataShards) 203 if ra.start > blockStart { 204 offset = int(ra.start) - int(blockStart) 205 } 206 207 lookupHash := getLookupHash(streamAllocationID, remotePath) 208 key := lookupHash + fmt.Sprintf(":%v-%v", startBlock, endBlock) 209 log.Info("win: start download blocks ", startBlock, " - ", endBlock, "/", f.NumBlocks, "(", f.ActualNumBlocks, ") for ", remotePath) 210 buf, ok := cachedDownloadedBlocks.Get(key) 211 if ok { 212 return buf, nil 213 } 214 215 mf := filepath.Join(os.TempDir(), strings.ReplaceAll(remotePath, "/", "_")+fmt.Sprintf("_%v_%v", startBlock, endBlock)) 216 defer os.Remove(mf) 217 218 // _, err = os.Stat(mf) 219 220 // if os.IsNotExist(err) { 221 statusBar := NewStatusBar(statusDownload, key) 222 status := statusBar.getStatus(key) 223 status.wg = &sync.WaitGroup{} 224 status.wg.Add(1) 225 log.Info("win: download blocks to ", mf) 226 err = alloc.DownloadFileByBlock(mf, remotePath, startBlock, endBlock, numBlocks, false, statusBar, true) 227 //err = alloc.DownloadFile(mf, remotePath, true, statusBar, true) 228 if err != nil { 229 return nil, err 230 } 231 232 log.Info("win: waiting for download to done") 233 status.wg.Wait() 234 // } 235 236 buf, err = os.ReadFile(mf) 237 if err != nil { 238 return nil, err 239 } 240 241 log.Info("win: downloaded blocks ", len(buf), " start:", ra.start, " blockStart:", blockStart, " offset:", offset, " len:", ra.length) 242 if len(buf) > 0 { 243 if len(buf) > int(ra.length) { 244 b := buf[offset : offset+int(ra.length)] 245 cachedDownloadedBlocks.Add(key, b) 246 return b, nil 247 } 248 cachedDownloadedBlocks.Add(key, buf) 249 return buf[offset:], nil 250 } 251 252 // os.Remove(mf) 253 return nil, nil 254 }