golang 流媒体服务器 支持视频快进

650 阅读2分钟

版本1 gin静态托管

适应gin框架实现视频点播功能, 利用gin提供的静态文件托管功能。

注意,有的视频可能不支持视频播放,如果发现视频播放不了,可以使用Handbrake进行转码之后再尝试。

//目录结构
- video
    - hello.mp4
- static
    - index.html
main.go

index.html

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <link href="https://vjs.zencdn.net/7.4.1/video-js.css" rel="stylesheet">
</head>
<body>

<video id="example-video" class="video-js vjs-default-skin" controls="controls" width="900" height="600">
    <source src="/videos/hello.mp4" type="video/mp4"/>
</video>

<script src="https://vjs.zencdn.net/7.4.1/video.min.js"></script>
<script>
    var player = videojs('example-video');
</script>
</body>

</html>

main.go

package main

import (
	"github.com/gin-gonic/gin"
	cors "github.com/itsjamie/gin-cors"
)

func main() {
	s := gin.Default()

	s.Use(cors.Middleware(cors.Config{
		ValidateHeaders: false,
		Origins:         "*",
		RequestHeaders:  "",
		ExposedHeaders:  "",
		Methods:         "",
		MaxAge:          0,
		Credentials:     false,
	}))
	// 开启静态文件托管
	// 第一个参数对应URL路径
        // 第二个参数为本地文件夹
	s.Static("/videos/", "video")
	s.Static("/htmls/", "static")
	_ = s.Run(":8100")
}

访问http://localhost:8100/htmls/会自动找到该目录下的index.html文件。该视频支付快进

版本2

gin手动实现, 这个版本是我参考以前的node.js的代码实现的。但是发现视频并不能播放。先记录下来,有知道办法的可以留言,感激不尽。

package main

/*
   gin框架实现文件下载功能
*/

import (
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"
	cors "github.com/itsjamie/gin-cors"
)

//主函数
func main() {
	r := gin.Default()
	r.Static("/htmls/", "static")
	r.Use(cors.Middleware(cors.Config{
		ValidateHeaders: false,
		Origins:         "*",
		RequestHeaders:  "",
		ExposedHeaders:  "",
		Methods:         "",
		MaxAge:          0,
		Credentials:     false,
	}))

	//Get路由,动态路由
	r.GET("/videos/:name", func(ctx *gin.Context) {
		name := ctx.Param("name")
		var video_range []string = ctx.Request.Header["Range"]
		video_path := path.Join("video", name)
		fmt.Println("video_path is ", video_path)
		stat, file_error := os.Stat(video_path)

		if file_error != nil {
			// 视频不存在
			fmt.Println("视频不存在")
			ctx.JSON(200, gin.H{"msg": "视频不存在", "videoname": video_path})
			return
		}
		var file_size = stat.Size() - 1

		if len(video_range) == 0 {
			//没有range
			fmt.Println("没有range")
			f, f_error := ioutil.ReadFile(video_path)
			if f_error != nil {
				ctx.JSON(200, gin.H{"msg": "错误", "reason": file_error})
				return
			}
			ctx.Header("Content-Range", fmt.Sprintf("bytes %d-%d/%d", 0, file_size, file_size))
			ctx.Header("Accept-Ranges", "bytes")
			ctx.Header("Cache-Control", "no-cache")
			ctx.Header("Content-Length", strconv.Itoa(int(file_size)))
			ctx.Data(200, "video/mp4", f[:1024])
			return
		} else {
			var range_text string = video_range[0]
			fmt.Println("1:", range_text)
			range_text = strings.Replace(range_text, "bytes=", "", -1)
			var byte_range []string = strings.Split(range_text, "-")
			begin, begin_err := strconv.Atoi(byte_range[0])
			if begin_err != nil {
				// 如果没有设置开始位置,则从0开始
				begin = 0
			}

			var end int
			if byte_range[1] == "" {
				end = 1024*10 + begin //10KB
				fmt.Println("end is ", end)
			} else {
				end1, end_err := strconv.Atoi(byte_range[1])
				fmt.Println("end1 is ", end1)
				if end_err != nil {
					end = 1024 * 10 //返回10K
				}
				end = end1
			}
			// 防止一次性读取所有文件
			if end > begin+10240 {
				end = begin + 10240
			}

			code := 206
			if file_size-1 < int64(end) {
				// 超过了范围
				end = int(file_size)
				code = 200
			}

			// 只读取部分进行返回
			file, file_error := os.Open(video_path)
			if file_error != nil {
				ctx.JSON(200, gin.H{"msg": "错误", "reason": file_error})
				return
			}
			seek, seek_error := file.Seek(int64(begin), 0)
			if seek_error != nil {
				fmt.Println("seek_error is ", seek_error)
			} else {
				fmt.Println("seek is ", seek)
			}
			var data [10240]uint8

			fmt.Println("\n begin is ", begin, "end is ", end)
			n_read, n_read_err := file.Read(data[:end-begin])
			fmt.Println("n_read is ", n_read)
			if n_read_err != nil {
				ctx.JSON(200, gin.H{"msg": "错误", "reason": file_error})
				return
			}
			ctx.Header("Content-Range", fmt.Sprintf("bytes %d-%d/%d", begin, begin+n_read, file_size))
			ctx.Header("Accept-Ranges", "bytes")
			ctx.Header("Content-Length", strconv.Itoa(n_read))
			ctx.Header("Cache-Control", "no-cache")
			ctx.Data(code, "video/mp4", data[:n_read])
		}
	})

	//监听端口
	err := r.Run(":8003")
	if err != nil {
		fmt.Println("error")
	}
}

版本3

支持直播功能

rtmp和hls协议

版本4

添加水印

版本5

视频编解码

版本6

封装成一个库文件