版本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
封装成一个库文件