合肥vs南宁夜景视频直播,用Go语言写一个实时对比的双城记

为什么突然想用Go写这个?昨晚刷手机,刷到合肥和南宁的夜景直播对比,合肥的天鹅湖灯光秀,南宁的邕江两岸霓虹,俩城市隔着一千多公里,但...

为什么突然想用Go写这个?

昨晚刷手机,刷到合肥和南宁的夜景直播对比,合肥的天鹅湖灯光秀,南宁的邕江两岸霓虹,俩城市隔着一千多公里,但在直播窗口里,它们就这么被摆在一起,我突然想——要是能用Go语言写个程序,自动抓取两边的直播流,做一个24小时不间断的对比小工具,那多有意思?

说实话,我不专业做视频的,我就是个写Go的码农,平时写写后端API、处理点数据流,但夜景直播这个事儿,它不复杂——我们需要的是:两个直播源、一个对比窗口、一段能跑的逻辑,Go的并发模型正好对着“同时处理两路流”这种需求,goroutine加channel,天然适合干这个

第一步:先搞清需求,别一上来就写代码

我打开南宁的夜景直播,画面是邕江大桥到南宁大桥那一段,灯光打在江面上,碎成一片一片的,合肥那边,天鹅湖周边的写字楼灯光秀,节奏感很强,颜色变换像呼吸一样,两个画面放一起,你得让用户能同时看,还能切视角、看评论、甚至对比亮灯率。

所以需求大概是这样:

  • 双流同步播放:左右分屏,合肥左、南宁右(或者反过来)。
  • 数据层对比:比如亮度、点赞数、评论热度(如果API开放的话)。
  • 轻量化部署:别搞太重的框架,用Go做个HTTP服务,前端用HTML5视频标签就行。
  • 能跑24小时的那种:稳定性要够,断流能自动重连。

你看,我没一开始就提goroutine,因为需求得先理清。写代码最怕就是“我觉得能这样做”就开始敲键盘,后边全是坑。

第二步:用Go搭个最基本的双流直播服务器

Go标准库的net/http就够了,我写了个最简单的版本,就俩handler,一个负责返回合肥直播源的地址,一个返回南宁的,直播源从哪来?有些公开的直播平台提供m3u8或者flv流地址,比如合肥的可以从一些城市的官方慢直播里拿,南宁的也是。合法来源哈,别爬非公开的

func hefeiStream(w http.ResponseWriter, r *http.Request) {
    // 这里其实应该是动态获取最新直播地址,为了演示我写死一个示例
    streamURL := "https://example.com/hefei-night.m3u8"
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{"url": streamURL})
}
func nanningStream(w http.ResponseWriter, r *http.Request) {
    streamURL := "https://example.com/nanning-night.m3u8"
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{"url": streamURL})
}

你看,这段代码简单到有点傻,但它能跑。一个能跑的东西,比一个完美的设计图有价值,我用mux路由,注册了两个端点,前端用JavaScript定时去请求,拿到流地址后塞进两个<video>标签里。

不过这时候有个问题:如果直播流断了怎么办? 城市夜景直播经常因为设备问题断流,合肥那边偶尔会黑屏几分钟,南宁这边也会,我一开始没处理,结果半夜起来看,两个窗口都是黑的。

第三步:用goroutine做健康检查和自动重连

这就是Go的强项了,我写了个HealthChecker,每隔30秒对两个直播源发一次请求,看能不能拿到视频流的第一帧数据,如果连续三次失败,就触发重连逻辑。

func healthCheck(ctx context.Context, streamURL string) bool {
    // 模拟检查,实际会用ffprobe或者直接尝试拉流
    select {
    case <-time.After(5 * time.Second):
        return true // 假设检查通过
    case <-ctx.Done():
        return false
    }
}

关键是用两个goroutine分别跑合肥和南宁的检查,互不干扰:

go func() {
    for {
        ok := healthCheck(ctx, hefeiURL)
        if !ok {
            hefeiURL = refreshStream("hefei") // 重新获取直播源
        }
        time.Sleep(30 * time.Second)
    }
}()

这个for循环加sleep的模式,看着不优雅,但在这种场景下它管用,不要为了用channel而用channel,这里一个独立的goroutine做定期的健康检查,逻辑清晰,好调试。

南宁那边也一样,这样两边独立运行,合肥断了不影响南宁,反过来也一样。

第四步:加入对比数据——亮度分析和弹幕热度

纯看画面多没意思,加点对比数据才好玩,我一开始想用Go调OpenCV算平均亮度,后来发现太重了,杀鸡不用牛刀,那就简单点:从直播流的HTTP响应头里拿内容长度变化,或者从视频帧的元数据里提取亮度信息(前提是流带这个)。

更实际的方案是:监控直播间的人气值和弹幕密度,如果直播平台开放了API,比如合肥直播间有“在线人数”,南宁这边有“点赞数”,那就用Go的http.Client定时去抓。

type LiveRoomStats struct {
    City    string
    Viewers int
    Likes   int
    Time    time.Time
}
func fetchStats(city string) (*LiveRoomStats, error) {
    // 假设有个公开的JSON接口
    resp, err := http.Get(fmt.Sprintf("https://api.example.com/%s/stats", city))
    // 解析...
}

然后用sync.Mutex保护一个全局的统计数据变量,前端每5秒用WebSocket拉一次。你别想着实时更新,5秒的延迟对夜景直播来说完全够用,又不是股票交易。

我把这些数据渲染成两个小卡片,放在视频画面下方,合肥那边显示“当前在线:1.2万人 点赞:2.3万”,南宁显示“当前在线:9800人 点赞:1.8万”。你别说,数值跳来跳去还挺有看头,尤其是晚上八九点钟,两个数字都在往上蹿。

第五步:前端显示——别写太复杂,用Svelte或者原生JS

Go后端做好了,前端我不强求,我试过用React搭,但为了这么个小工具,打包出来300KB,太了,后来直接写原生HTML+CSS+JS,再套个Svelte的CDN版,轻到飞起

布局就是一个flex容器:左边合肥,右边南宁,每个视频上面覆盖一个半透明的统计条,显示实时对比数据。关键是用CSS的object-fit: cover,让视频填满容器,不出现黑边。

<div style="display: flex; width: 100%; height: 100vh;">
    <div class="stream-container" style="flex: 1;">
        <video id="hefeiVideo" autoplay muted></video>
        <div class="stats-overlay">
            <span>合肥 </span>
            <span id="hefeiViewers">0</span> 人观看
        </div>
    </div>
    <div class="stream-container" style="flex: 1;">
        <video id="nanningVideo" autoplay muted></video>
        <div class="stats-overlay">
            <span>南宁 </span>
            <span id="nanningViewers">0</span> 人观看
        </div>
    </div>
</div>

muted属性别忘了加,不然浏览器会阻止自动播放,夜景直播的背景音其实就那样,风吹草动的声音,我干脆都静音了,想看情绪的可以自己开。

第六步:多路复用——用channel传递控制信号

我后来发现一个问题:用户想切视角,比如只看南宁、或者左右互换,这个得用事件来控制。

于是我用了一个controlChan,类型是struct{Action string},Go后端监听这个channel,收到“swap”事件就互换两边的流地址。

type ControlEvent struct {
    Action string // "swap", "single_nanning", "single_hefei", "dual"
}
func controlLoop(controlChan <-chan ControlEvent) {
    for event := range controlChan {
        switch event.Action {
        case "swap":
            hefeiURL, nanningURL = nanningURL, hefeiURL
        case "single_hefei":
            // 只显示合肥
        }
    }
}

前端点个按钮,发个POST请求到/control,后端就把事件塞进channel。这种设计,在Go里写着很顺手,channel一读一写,比回调函数好理解。

合肥vs南宁夜景视频直播,用Go语言写一个实时对比的双城记

第七步:数据记录——用CSV存一份昼夜对比日志

既然是非正式的实验项目,就别上数据库了。 我用Go的encoding/csv库,每小时把两个城市的直播间数据写进CSV文件:

  • 时间戳
  • 合肥在线人数
  • 南宁在线人数
  • 合肥亮度等级(如果算得出来的话)
  • 南宁亮度等级

文件名按日期来,比如2025-01-05.csv,跑了一个星期,我拿到一份数据:合肥的夜间活跃高峰在20:00-21:30,南宁的稍微晚一点,21:00-23:00是顶点,你仔细想想,这跟城市的作息有关系:合肥更偏北,晚上凉得快,大家回室内早;南宁暖和,夜生活拉得长。

不过这个结论可能不严谨,样本也不够大,但看着数据自己画出来的曲线,就感觉这两个城市在数据里有了性格。

第八步:性能优化——别让同时拉两路流把机器拖死

本地跑的时候,我笔记本风扇呼呼转。两路高清流同时解码,CPU占用直接飙到70%,后来我想了个办法:只在用户打开页面时才拉流,用Go的context来控制超时和取消。

每个用户连接对应一个goroutine,30分钟无操作就自动断开。这个用time.Ticker配合select实现

func handleConnection(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), 30*time.Minute)
    defer cancel()
    // 启动两个拉流goroutine
    go pullStream(ctx, "hefei")
    go pullStream(ctx, "nanning")
    // 阻塞等待超时或客户端断开
    <-ctx.Done()
}

这样服务器就能抗住更多用户了,不会因为一个用户开着页面不关,就永远占着资源。

第九步:部署——用Docker打包,扔到轻量服务器上

Dockerfile就几行:

FROM golang:1.21-alpine AS build
WORKDIR /app
COPY . .
RUN go build -o live-compare
FROM alpine:latest
COPY --from=build /app/live-compare /app/
EXPOSE 8080
CMD ["/app/live-compare"]

然后docker-compose up -d,配个nginx反代加上SSL证书,域名解析到服务器上。阿里云那种99块钱一年的轻量服务器就够了,2核2G内存,扛个四五十人同时看没问题。

但实际跑起来我发现,并发用户数一上20,拉流带宽就不够了,所以后来我加了简单的限流:每个IP只允许一个连接,用sync.Map做连接计数。

第十步:一些坑和意外发现

第一个坑:HLS流的延迟,m3u8格式的直播延迟通常在10-30秒,合肥那边快一些,南宁慢一些,两边画面差好几秒,用户会觉得“不同步”,我试过用WebRTC替换,但太复杂了,最后选择了在UI上显示时间戳差异,告诉用户“当前两边延迟约12秒”,大家理解就好了。

第二个坑:直播源防盗链,有些城市的官方直播加了referer限制,必须从特定域名访问,我这边的解决办法是:通过Go发起一个中间请求,带上合法的referer,拿到流数据后再转发给前端,用io.Copy把响应体原样转发:

func proxyStream(w http.ResponseWriter, r *http.Request) {
    targetURL := getStreamURL(r.URL.Query().Get("city"))
    req, _ := http.NewRequest("GET", targetURL, nil)
    req.Header.Set("Referer", "https://example.com")
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        http.Error(w, "stream unavailable", 503)
        return
    }
    defer resp.Body.Close()
    w.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
    io.Copy(w, resp.Body)
}

第三个意外发现:南宁的夜景直播评论区特别活跃,很多人刷“南宁我爱你”“邕江最美”;合肥的评论区相对安静,更多是问路的或者感叹“天鹅湖房价多少”。这本身就是一种城市气质的对比,我后来把这个功能加上了——在视频旁边展示实时评论(关键信息脱敏),用户可以看到两个城市的人在说什么。

其实写到最后,我更像是在观察两个城市

这个用Go写的合肥vs南宁夜景直播工具,它不太完美。偶尔会卡顿,流地址隔段时间就失效需要手动更新,对比数据也是粗略的,但它让我在一个窗口里同时看到了长江中下游城市和岭南城市的夜晚。

合肥的夜景是整齐的、有规划的,天鹅湖的灯光秀像程序写好的,一片一片亮起来;南宁的夜景是随性的、流淌的,邕江两岸的灯光跟着水波晃动,像个活物。

Go语言的goroutine在后台运行着健康检查,前端两个视频标签播放着完全不同的夜色,这大概就是代码和生活的交集吧——你写的东西不一定非要改变世界,能让你同时看到了两个城市的夜晚,也挺好。

下次我打算加上成都和重庆,写个三城甚至四城对比。到时候goroutine要开四路了,Go的并发能力刚好派上用场

本文来自作者[kyadmin]投稿,不代表中国·AC米兰(Milan)体育官方网站-Official Website立场,如若转载,请注明出处:http://www.f6336.com/jk/569.html

(17)

文章推荐

发表回复

本站作者才能评论

评论列表(4条)

  • kyadmin
    kyadmin 2026-06-30

    我是中国·AC米兰(Milan)体育官方网站-Official Website的签约作者“kyadmin”!

  • kyadmin
    kyadmin 2026-06-30

    希望本篇文章《合肥vs南宁夜景视频直播,用Go语言写一个实时对比的双城记》能对你有所帮助!

  • kyadmin
    kyadmin 2026-06-30

    本站[中国·AC米兰(Milan)体育官方网站-Official Website]内容主要涵盖:AC米兰,ac米兰官网,AC米兰官网

  • kyadmin
    kyadmin 2026-06-30

    本文概览:为什么突然想用Go写这个?昨晚刷手机,刷到合肥和南宁的夜景直播对比,合肥的天鹅湖灯光秀,南宁的邕江两岸霓虹,俩城市隔着一千多公里,但...

    联系我们

    工作时间:周一至周五,9:30-18:30,节假日休息

    关注我们