package bilibili import ( "context" "errors" "fmt" "github.com/keuin/slbr/common" "io" "net/http" "os" "strings" ) // CopyLiveStream read data from a livestream video stream, copy them to a writer. func (b Bilibili) CopyLiveStream( ctx context.Context, roomId common.RoomId, stream StreamingUrlInfo, out *os.File, bufSize int64, ) (err error) { url := stream.URL if !strings.HasPrefix(url, "https://") && !strings.HasPrefix(url, "http://") { return fmt.Errorf("invalid URL: %v", url) } r, err := b.newGet(url) if err != nil { b.logger.Error("Cannot create HTTP GET instance on %v: %v", url, err) return err } r.Header.Set("Referer", fmt.Sprintf("https://live.bilibili.com/blanc/%d?liteVersion=true", roomId)) resp, err := b.Do(r) if err != nil { b.logger.Error("Cannot make HTTP GET request on %v: %v\n", url, err) return } // 404 when not streaming if resp.StatusCode == http.StatusNotFound { return fmt.Errorf("live is not started or the room does not exist") } err = validateHttpStatus(resp) if err != nil { return } defer func() { _ = resp.Body.Close() }() b.logger.Info("Copying live stream...") var n int64 // blocking copy copyLoop: for err == nil { select { case <-ctx.Done(): // cancelled err = ctx.Err() break copyLoop default: var sz int64 sz, err = io.CopyN(out, resp.Body, bufSize) n += sz } } if errors.Is(err, context.Canceled) { b.logger.Info("Stop copying...") } else if errors.Is(err, io.EOF) { b.logger.Info("The live is ended. (room %v)", roomId) } else { b.logger.Error("Stream copying was interrupted unexpectedly: %v", err) } b.logger.Info("Total downloaded: %v", common.PrettyBytes(uint64(n))) return err }