You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Go并发下载进程添加单/多个进度条?

多进度条实现方案(基于github.com/cheggaaa/pb/v3)

核心思路

解决动态content-length问题的关键是提前获取每个曲目的真实总下载大小,不管是普通单文件还是HLS分段文件,再为每个曲目创建对应长度的进度条,最后通过进度池统一管理所有进度条的显示。


步骤1:安装依赖

go get github.com/cheggaaa/pb/v3

步骤2:修改播放列表下载逻辑

if soundData.Kind == "playlist" {
    var wg sync.WaitGroup
    plDownloadTracks := getPlaylistDownloadTracks(soundData, clientId)

    // 初始化进度池,统一管理所有进度条
    pool := pb.NewPool()
    pool.SetMaxWidth(80) // 设置进度条显示宽度

    for _, dlT := range plDownloadTracks {
        wg.Add(1)
        go func(dlT []soundcloud.DownloadTrack) {
            defer wg.Done()
            t := getTrack(dlT, true)
            
            // 提前获取当前曲目的总下载大小
            totalSize, err := getTrackTotalSize(t)
            if err != nil {
                fmt.Printf("获取曲目「%s」大小失败: %v\n", t.SoundData.Title, err)
                return
            }

            // 创建对应长度的进度条,设置显示名称
            bar := pb.New64(totalSize).SetDescription(fmt.Sprintf("下载: %s", t.SoundData.Title))
            pool.Add(bar)
            bar.Start() // 启动当前进度条

            fp := soundcloud.Download(t, downloadPath, bar)
            if fp != "" {
                soundcloud.AddMetadata(t, fp)
            }
            bar.Finish() // 结束进度条(无论文件是否已存在)
        }(dlT)
    }

    pool.Start() // 启动进度池
    wg.Wait()    // 等待所有下载完成
    pool.Stop()  // 停止进度池

    fmt.Printf("\n%s 播放列表已保存至: %s\n", theme.Green("[-]"), theme.Magenta(downloadPath))
    return
}

步骤3:添加获取曲目总大小的工具函数

// getTrackTotalSize 获取曲目真实总下载大小,兼容普通文件和HLS分段
func getTrackTotalSize(track soundcloud.DownloadTrack) (int64, error) {
    if track.Quality != "low" {
        // HLS格式:先解析m3u8拿到所有分段URL,再逐个请求获取分段大小
        resp, err := http.Get(track.Url)
        if err != nil {
            return 0, err
        }
        defer resp.Body.Close()

        segments := getSegments(resp.Body)
        var total int64 = 0
        for _, segURI := range segments {
            segResp, err := http.Head(segURI)
            if err != nil {
                continue // 忽略单个分段的错误,继续统计其他分段
            }
            total += segResp.ContentLength
            segResp.Body.Close()
        }
        return total, nil
    } else {
        // 普通格式:直接请求头获取文件大小
        resp, err := http.Head(track.Url)
        if err != nil {
            return 0, err
        }
        defer resp.Body.Close()
        return resp.ContentLength, nil
    }
}

步骤4:修改下载相关函数,适配进度条

更新Download函数

func Download(track soundcloud.DownloadTrack, dlpath string, bar *pb.ProgressBar) string {
    trackName := track.SoundData.Title + "[" + track.Quality + "]." + track.Ext
    path := validateDownload(dlpath, trackName)
    if path == "" {
        return "" // 文件已存在,直接返回
    }

    if track.Quality != "low" {
        resp, err := http.Get(track.Url)
        if err != nil {
            return ""
        }
        defer resp.Body.Close()

        segments := getSegments(resp.Body)
        if err := DownloadM3u8(path, bar, segments); err != nil {
            return ""
        }
        return path
    }

    resp, err := http.Get(track.Url)
    if err != nil {
        return ""
    }
    defer resp.Body.Close()

    f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        return ""
    }
    defer f.Close()

    // 同时写入文件和进度条,自动更新进度
    _, err = io.Copy(io.MultiWriter(f, bar), resp.Body)
    return path
}

更新DownloadM3u8和downloadSeg函数

func downloadSeg(wg *sync.WaitGroup, segmentURI string, file *os.File, bar *pb.ProgressBar) {
    defer wg.Done()
    resp, err := http.Get(segmentURI)
    if err != nil {
        return
    }
    defer resp.Body.Close()

    // 写入文件的同时更新进度条
    if bar != nil {
        _, _ = io.Copy(io.MultiWriter(file, bar), resp.Body)
    } else {
        _, _ = io.Copy(file, resp.Body)
    }
}

func DownloadM3u8(filepath string, bar *pb.ProgressBar, segments []string) error {
    file, err := os.OpenFile(filepath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
    if err != nil {
        return err
    }
    defer file.Close()

    var wg sync.WaitGroup
    for _, segment := range segments {
        wg.Add(1)
        go downloadSeg(&wg, segment, file, bar)
    }
    wg.Wait()
    return nil
}

关键说明

  1. 真实进度计算:通过getTrackTotalSize提前获取每个曲目的总下载大小,避免使用常量值导致进度条失真。
  2. 进度池管理:pb.Pool会自动处理多个进度条的布局和刷新,无需手动控制输出位置。
  3. HLS分段适配:每个分段的下载数据都会同步写入进度条,自动累加完成进度。

内容的提问来源于stack exchange,提问作者AYehia0

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 22:10:42