package main
// import (
// "fmt"
// "os"
// "os/exec"
// "path/filepath"
// )
// func main() {
// inputFile := "./test.mp4" // 输入视频文件
// outputDir := "output" // 输出目录
// segmentDuration := "10" // 每个分片的时长(秒)
// // 创建输出目录
// err := os.MkdirAll(outputDir, os.ModePerm)
// if err != nil {
// fmt.Printf("创建输出目录失败: %v\n", err)
// return
// }
// // 构建FFmpeg命令
// outputPattern := filepath.Join(outputDir, "segment_%03d.ts")
// cmd := exec.Command("ffmpeg",
// "-i", inputFile,
// "-c:v", "libx264",
// "-c:a", "aac",
// "-f", "segment",
// "-segment_time", segmentDuration,
// "-segment_format", "mpegts",
// "-segment_list", filepath.Join(outputDir, "playlist.m3u8"),
// "-segment_list_type", "m3u8",
// outputPattern)
// // 执行FFmpeg命令
// output, err := cmd.CombinedOutput()
// if err != nil {
// fmt.Printf("视频切片失败: %v\n", err)
// fmt.Println(string(output))
// return
// }
// fmt.Println("视频切片完成!")
// }
import (
"fmt"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
)
const (
inputFile = "./extended.mp4"
outputDir = "output"
segmentDuration = "10"
serverPort = ":8999"
)
func main() {
// 确保输出目录存在
err := os.MkdirAll(outputDir, os.ModePerm)
if err != nil {
log.Fatalf("创建输出目录失败: %v\n", err)
}
// 复制原始MP4文件到输出目录
err = copyFile(inputFile, filepath.Join(outputDir, "original.mp4"))
if err != nil {
log.Fatalf("复制原始文件失败: %v\n", err)
}
// 执行视频切片
err = sliceVideo()
if err != nil {
log.Fatalf("视频切片失败: %v\n", err)
}
// 设置静态文件服务
http.Handle("/output/", http.StripPrefix("/output/", http.FileServer(http.Dir(outputDir))))
// 设置首页
http.HandleFunc("/", serveIndex)
fmt.Printf("服务器启动在 http://localhost%s\n", serverPort)
log.Fatal(http.ListenAndServe(serverPort, nil))
}
func copyFile(src, dst string) error {
input, err := os.ReadFile(src)
if err != nil {
return err
}
return os.WriteFile(dst, input, 0644)
}
func sliceVideo() error {
outputPattern := filepath.Join(outputDir, "segment_%03d.ts")
cmd := exec.Command("ffmpeg",
"-i", inputFile,
"-c:v", "libx264",
"-c:a", "aac",
"-f", "segment",
"-segment_time", segmentDuration,
"-segment_format", "mpegts",
"-segment_list", filepath.Join(outputDir, "playlist.m3u8"),
"-segment_list_type", "m3u8",
outputPattern)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("ffmpeg 命令执行失败: %v\n%s", err, output)
}
return nil
}
func serveIndex(w http.ResponseWriter, r *http.Request) {
html := `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>视频测试</title>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
h2 { color: #333; }
video { max-width: 100%; }
</style>
</head>
<body>
<h1>视频测试页面</h1>
<h2>1. 原始 MP4 视频</h2>
<video id="mp4Video" controls>
<source src="/output/original.mp4" type="video/mp4">
您的浏览器不支持 video 标签。
</video>
<h2>2. HLS 流 (m3u8)</h2>
<video id="hlsVideo" controls></video>
<script>
var video = document.getElementById('hlsVideo');
if(Hls.isSupported()) {
var hls = new Hls();
hls.loadSource('/output/playlist.m3u8');
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function() {
video.play();
});
}
else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = '/output/playlist.m3u8';
video.addEventListener('loadedmetadata', function() {
video.play();
});
}
</script>
</body>
</html>
`
fmt.Fprint(w, html)
}
问题:在本地localhost访问网站mp4不同时间点视频跳转比ts快,在卡顿卡死也不重新请求新的ts
解决
// FILEPATH: /usr/local/vid/slice_vid/main.go
package main
import (
"fmt"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
)
const (
inputFile = "./extended.mp4"
outputDir = "output"
segmentDuration = "4" // 减小分片大小到 4 秒
serverPort = ":8999"
)
func main() {
// 确保输出目录存在
err := os.MkdirAll(outputDir, os.ModePerm)
if err != nil {
log.Fatalf("创建输出目录失败: %v\n", err)
}
// 复制原始MP4文件到输出目录
err = copyFile(inputFile, filepath.Join(outputDir, "original.mp4"))
if err != nil {
log.Fatalf("复制原始文件失败: %v\n", err)
}
// 执行视频切片
err = sliceVideo()
if err != nil {
log.Fatalf("视频切片失败: %v\n", err)
}
// 设置静态文件服务
http.Handle("/output/", http.StripPrefix("/output/", http.FileServer(http.Dir(outputDir))))
// 设置首页
http.HandleFunc("/", serveIndex)
fmt.Printf("服务器启动在 http://localhost%s\n", serverPort)
log.Fatal(http.ListenAndServe(serverPort, nil))
}
func copyFile(src, dst string) error {
input, err := os.ReadFile(src)
if err != nil {
return err
}
return os.WriteFile(dst, input, 0644)
}
func sliceVideo() error {
outputPattern := filepath.Join(outputDir, "segment_%03d.ts")
cmd := exec.Command("ffmpeg",
"-i", inputFile,
"-c:v", "libx264",
"-preset", "veryfast",
"-crf", "23",
"-g", "48",
"-keyint_min", "48", // 强制最小关键帧间隔
"-sc_threshold", "0", // 禁用场景切换检测
"-c:a", "aac",
"-b:a", "128k",
"-ac", "2",
"-f", "segment",
"-segment_time", segmentDuration,
"-segment_format", "mpegts",
"-segment_list", filepath.Join(outputDir, "playlist.m3u8"),
"-segment_list_type", "m3u8",
"-force_key_frames", "expr:gte(t,n_forced*4)", // 每 4 秒强制一个关键帧
outputPattern)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("ffmpeg 命令执行失败: %v\n%s", err, output)
}
return nil
}
func serveIndex(w http.ResponseWriter, r *http.Request) {
html := `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HLS 视频播放</title>
<!-- 引入 hls.js 库 -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/hls.js/latest/hls.min.js"></script>
</head>
<body>
<!-- 视频播放容器 -->
<video class="video" id="HLSPlayer" controls>
<!-- 如果浏览器支持 HLS,这里会显示视频 -->
<p>您的浏览器不支持 HLS 视频播放,请升级浏览器或使用支持 HLS 的设备。</p>
</video>
<script>
// 获取视频播放器元素
let video = document.getElementById("HLSPlayer");
// HLS 视频流的 URL(请替换为你的 m3u8 文件路径)
var url = '/output/playlist.m3u8'; // 替换为你的 HLS 流地址
// 检查浏览器是否支持 HLS
if (Hls.isSupported()) {
// 创建 HLS 实例
var hls = new Hls();
hls.loadSource(url); // 加载 HLS 流
hls.attachMedia(video); // 将 HLS 实例绑定到 video 元素
hls.on(Hls.Events.MANIFEST_PARSED, function () {
video.play(); // 当 HLS 流解析完成后自动播放
});
// 错误处理
hls.on(Hls.Events.ERROR, function (event, data) {
if (data.fatal) {
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
console.log("网络错误,尝试重新加载...");
hls.startLoad();
break;
case Hls.ErrorTypes.MEDIA_ERROR:
console.log("媒体错误,尝试恢复...");
hls.recoverMediaError();
break;
default:
console.log("无法恢复的错误,销毁 HLS 实例...");
hls.destroy();
break;
}
}
});
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
// 如果浏览器原生支持 HLS(如 Safari),直接设置 video 的 src
video.src = url;
video.addEventListener('canplay', function () {
video.play();
});
} else {
// 如果浏览器不支持 HLS,提示用户
console.log("您的浏览器不支持 HLS 视频播放。");
}
// 页面关闭时销毁 HLS 实例(避免资源泄漏)
window.addEventListener('beforeunload', function () {
if (hls) {
hls.destroy();
}
});
</script>
</body>
</html>
`
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, html)
}
前端实现倍速功能
package main
import (
"fmt"
"log"
"net/http"
)
const (
inputFile = "./extended.mp4"
outputDir = "output"
segmentDuration = "4" // 减小分片大小到 4 秒
serverPort = ":8999"
)
func main() {
// 设置静态文件服务
http.Handle("/output/", http.StripPrefix("/output/", http.FileServer(http.Dir(outputDir))))
// 设置首页
http.HandleFunc("/", serveIndex)
fmt.Printf("服务器启动在 http://localhost%s\n", serverPort)
log.Fatal(http.ListenAndServe(serverPort, nil))
}
func serveIndex(w http.ResponseWriter, r *http.Request) {
html := `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HLS 视频播放</title>
<!-- 引入 hls.js 库 -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/hls.js/latest/hls.min.js"></script>
</head>
<body>
<!-- 视频播放容器 -->
<video class="video" id="HLSPlayer" controls>
<!-- 如果浏览器支持 HLS,这里会显示视频 -->
<p>您的浏览器不支持 HLS 视频播放,请升级浏览器或使用支持 HLS 的设备。</p>
</video>
<!-- 倍速选择控件 -->
<div style="margin-top: 10px;">
<label for="speedControl">播放速度:</label>
<select id="speedControl">
<option value="0.5">0.5x</option>
<option value="1.0" selected>1.0x</option>
<option value="1.5">1.5x</option>
<option value="2.0">2.0x</option>
</select>
</div>
<script>
// 获取视频播放器元素
let video = document.getElementById("HLSPlayer");
// HLS 视频流的 URL(请替换为你的 m3u8 文件路径)
var url = '/output/playlist.m3u8'; // 替换为你的 HLS 流地址
// 检查浏览器是否支持 HLS
if (Hls.isSupported()) {
// 创建 HLS 实例
var hls = new Hls();
hls.loadSource(url); // 加载 HLS 流
hls.attachMedia(video); // 将 HLS 实例绑定到 video 元素
hls.on(Hls.Events.MANIFEST_PARSED, function () {
video.play(); // 当 HLS 流解析完成后自动播放
});
// 错误处理
hls.on(Hls.Events.ERROR, function (event, data) {
if (data.fatal) {
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
console.log("网络错误,尝试重新加载...");
hls.startLoad();
break;
case Hls.ErrorTypes.MEDIA_ERROR:
console.log("媒体错误,尝试恢复...");
hls.recoverMediaError();
break;
default:
console.log("无法恢复的错误,销毁 HLS 实例...");
hls.destroy();
break;
}
}
});
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
// 如果浏览器原生支持 HLS(如 Safari),直接设置 video 的 src
video.src = url;
video.addEventListener('canplay', function () {
video.play();
});
} else {
// 如果浏览器不支持 HLS,提示用户
console.log("您的浏览器不支持 HLS 视频播放。");
}
// 倍速控制
document.getElementById("speedControl").addEventListener("change", function () {
const selectedSpeed = parseFloat(this.value);
video.playbackRate = selectedSpeed;
});
// 页面关闭时销毁 HLS 实例(避免资源泄漏)
window.addEventListener('beforeunload', function () {
if (hls) {
hls.destroy();
}
});
</script>
</body>
</html>
`
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, html)
}
加截图功能
package main
import (
"fmt"
"log"
"net/http"
)
const (
inputFile = "./extended.mp4"
outputDir = "output"
segmentDuration = "4" // 减小分片大小到 4 秒
serverPort = ":8999"
)
func main() {
// 设置静态文件服务
http.Handle("/output/", http.StripPrefix("/output/", http.FileServer(http.Dir(outputDir))))
// 设置首页
http.HandleFunc("/", serveIndex)
fmt.Printf("服务器启动在 http://localhost%s\n", serverPort)
log.Fatal(http.ListenAndServe(serverPort, nil))
}
func serveIndex(w http.ResponseWriter, r *http.Request) {
html := `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HLS 视频播放与截图功能</title>
<!-- 引入 hls.js 库 -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/hls.js/latest/hls.min.js"></script>
</head>
<body>
<!-- 视频播放容器 -->
<video class="video" id="HLSPlayer" controls>
<p>您的浏览器不支持 HLS 视频播放,请升级浏览器或使用支持 HLS 的设备。</p>
</video>
<!-- 倍速选择控件 -->
<div style="margin-top: 10px;">
<label for="speedControl">播放速度:</label>
<select id="speedControl">
<option value="0.5">0.5x</option>
<option value="1.0" selected>1.0x</option>
<option value="1.5">1.5x</option>
<option value="2.0">2.0x</option>
</select>
</div>
<!-- 截图按钮 -->
<button id="screenshotButton" style="margin-top: 10px;">截图并保存</button>
<!-- 显示截图的区域 -->
<div id="screenshotPreview" style="margin-top: 10px;">
<h4>截图预览:</h4>
</div>
<script>
// 获取视频播放器元素
let video = document.getElementById("HLSPlayer");
// HLS 视频流的 URL(请替换为你的 m3u8 文件路径)
var url = '/output/playlist.m3u8'; // 替换为你的 HLS 流地址
// 检查浏览器是否支持 HLS
if (Hls.isSupported()) {
// 创建 HLS 实例
var hls = new Hls();
hls.loadSource(url); // 加载 HLS 流
hls.attachMedia(video); // 将 HLS 实例绑定到 video 元素
hls.on(Hls.Events.MANIFEST_PARSED, function () {
video.play(); // 当 HLS 流解析完成后自动播放
});
// 错误处理
hls.on(Hls.Events.ERROR, function (event, data) {
if (data.fatal) {
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
console.log("网络错误,尝试重新加载...");
hls.startLoad();
break;
case Hls.ErrorTypes.MEDIA_ERROR:
console.log("媒体错误,尝试恢复...");
hls.recoverMediaError();
break;
default:
console.log("无法恢复的错误,销毁 HLS 实例...");
hls.destroy();
break;
}
}
});
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
// 如果浏览器原生支持 HLS(如 Safari),直接设置 video 的 src
video.src = url;
video.addEventListener('canplay', function () {
video.play();
});
} else {
// 如果浏览器不支持 HLS,提示用户
console.log("您的浏览器不支持 HLS 视频播放。");
}
// 倍速控制
document.getElementById("speedControl").addEventListener("change", function () {
const selectedSpeed = parseFloat(this.value);
video.playbackRate = selectedSpeed;
});
// 截图功能
document.getElementById("screenshotButton").addEventListener("click", function () {
// 创建一个 canvas 元素
const canvas = document.createElement("canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const context = canvas.getContext("2d");
// 绘制当前视频帧到 canvas
context.drawImage(video, 0, 0, canvas.width, canvas.height);
// 将 canvas 内容转换为图片(base64格式)
const screenshot = canvas.toDataURL("image/png");
// 显示截图
const screenshotPreview = document.getElementById("screenshotPreview");
screenshotPreview.innerHTML = '<img src="' + screenshot + '" alt="截图" style="max-width: 100%;">';
// 创建下载链接并触发下载
const downloadLink = document.createElement("a");
downloadLink.href = screenshot;
downloadLink.download = "screenshot.png"; // 设置下载文件名
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
});
// 页面关闭时销毁 HLS 实例(避免资源泄漏)
window.addEventListener('beforeunload', function () {
if (hls) {
hls.destroy();
}
});
</script>
</body>
</html>
`
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, html)
}
截图加水印
package main
import (
"fmt"
"log"
"net/http"
"os"
"os/exec"
)
const (
inputFile = "./extended.mp4" // 输入的视频文件
outputDir = "output" // 输出目录
segmentDuration = "4" // 分片时长
serverPort = ":8999" // 服务器端口
)
func main() {
// 确保输出目录存在
if err := os.MkdirAll(outputDir, os.ModePerm); err != nil {
log.Fatalf("创建输出目录失败: %v", err)
}
// 设置静态文件服务
http.Handle("/output/", http.StripPrefix("/output/", http.FileServer(http.Dir(outputDir))))
// 设置首页
http.HandleFunc("/", serveIndex)
// 设置截取视频片段的接口
http.HandleFunc("/cut-video", cutVideoHandler)
// 设置截图并添加水印的接口
http.HandleFunc("/screenshot", screenshotHandler)
fmt.Printf("服务器启动在 http://localhost%s\n", serverPort)
log.Fatal(http.ListenAndServe(serverPort, nil))
}
func serveIndex(w http.ResponseWriter, r *http.Request) {
html := `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HLS 视频播放与截图功能</title>
<script type="text/javascript" src="https://cdn.jsdelivr.net/hls.js/latest/hls.min.js"></script>
</head>
<body>
<video class="video" id="HLSPlayer" controls>
<p>您的浏览器不支持 HLS 视频播放,请升级浏览器或使用支持 HLS 的设备。</p>
</video>
<div style="margin-top: 10px;">
<label for="speedControl">播放速度:</label>
<select id="speedControl">
<option value="0.5">0.5x</option>
<option value="1.0" selected>1.0x</option>
<option value="1.5">1.5x</option>
<option value="2.0">2.0x</option>
</select>
</div>
<button id="screenshotButton" style="margin-top: 10px;">截图并保存</button>
<button id="recordButton" style="margin-top: 10px;">开始录制</button>
<div id="screenshotPreview" style="margin-top: 10px;">
<h4>截图预览:</h4>
</div>
<script>
let video = document.getElementById("HLSPlayer");
const url = '/output/playlist.m3u8'; // 替换为你的 HLS 流地址
let recording = false;
let startTime = 0;
let endTime = 0;
if (Hls.isSupported()) {
var hls = new Hls();
hls.loadSource(url);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function () {
video.play();
});
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = url;
video.addEventListener('canplay', function () {
video.play();
});
} else {
console.log("您的浏览器不支持 HLS 视频播放。");
}
document.getElementById("speedControl").addEventListener("change", function () {
video.playbackRate = parseFloat(this.value);
});
document.getElementById("screenshotButton").addEventListener("click", function () {
const currentTime = video.currentTime;
fetch('/screenshot?time=' + currentTime)
.then(response => response.text())
.then(data => {
alert(data);
})
.catch(error => {
console.error("截图失败:", error);
alert("截图失败,请检查控制台日志。");
});
});
document.getElementById("recordButton").addEventListener("click", function () {
if (!recording) {
startTime = video.currentTime;
recording = true;
this.textContent = "结束录制";
} else {
endTime = video.currentTime;
recording = false;
this.textContent = "开始录制";
fetch('/cut-video?start=' + startTime + '&end=' + endTime)
.then(response => response.text())
.then(data => {
alert(data);
})
.catch(error => {
console.error("截取视频失败:", error);
alert("截取视频失败,请检查控制台日志。");
});
}
});
</script>
</body>
</html>
`
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, html)
}
func cutVideoHandler(w http.ResponseWriter, r *http.Request) {
start := r.URL.Query().Get("start")
end := r.URL.Query().Get("end")
if start == "" || end == "" {
http.Error(w, "缺少开始或结束时间参数", http.StatusBadRequest)
return
}
outputFile := fmt.Sprintf("%s/clip_%s_to_%s.mp4", outputDir, start, end)
cmd := exec.Command("ffmpeg", "-i", inputFile, "-ss", start, "-to", end, "-c", "copy", outputFile)
err := cmd.Run()
if err != nil {
http.Error(w, fmt.Sprintf("截取视频失败: %v", err), http.StatusInternalServerError)
return
}
w.Write([]byte(fmt.Sprintf("视频片段已成功截取并保存到 %s", outputFile)))
}
func screenshotHandler(w http.ResponseWriter, r *http.Request) {
time := r.URL.Query().Get("time")
if time == "" {
http.Error(w, "缺少时间参数", http.StatusBadRequest)
return
}
// 截图并添加水印
outputFile := fmt.Sprintf("%s/screenshot_%s.png", outputDir, time)
cmd := exec.Command("ffmpeg", "-i", inputFile, "-ss", time, "-vframes", "1", "-vf", "drawtext=text='Watermark':x=10:y=10:fontsize=24:fontcolor=white", outputFile)
err := cmd.Run()
if err != nil {
http.Error(w, fmt.Sprintf("截图失败: %v", err), http.StatusInternalServerError)
return
}
w.Write([]byte(fmt.Sprintf("截图已成功保存到 %s", outputFile)))
}
截取录制视频
package main
import (
"fmt"
"log"
"net/http"
"os"
"os/exec"
)
const (
inputFile = "./extended.mp4" // 输入的视频文件
outputDir = "output" // 输出目录
segmentDuration = "4" // 分片时长
serverPort = ":8999" // 服务器端口
)
func main() {
// 确保输出目录存在
if err := os.MkdirAll(outputDir, os.ModePerm); err != nil {
log.Fatalf("创建输出目录失败: %v", err)
}
// 设置静态文件服务
http.Handle("/output/", http.StripPrefix("/output/", http.FileServer(http.Dir(outputDir))))
// 设置首页
http.HandleFunc("/", serveIndex)
// 设置截取视频片段的接口
http.HandleFunc("/cut-video", cutVideoHandler)
fmt.Printf("服务器启动在 http://localhost%s\n", serverPort)
log.Fatal(http.ListenAndServe(serverPort, nil))
}
func serveIndex(w http.ResponseWriter, r *http.Request) {
html := `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HLS 视频播放与截图功能</title>
<script type="text/javascript" src="https://cdn.jsdelivr.net/hls.js/latest/hls.min.js"></script>
</head>
<body>
<video class="video" id="HLSPlayer" controls>
<p>您的浏览器不支持 HLS 视频播放,请升级浏览器或使用支持 HLS 的设备。</p>
</video>
<div style="margin-top: 10px;">
<label for="speedControl">播放速度:</label>
<select id="speedControl">
<option value="0.5">0.5x</option>
<option value="1.0" selected>1.0x</option>
<option value="1.5">1.5x</option>
<option value="2.0">2.0x</option>
</select>
</div>
<button id="screenshotButton" style="margin-top: 10px;">截图并保存</button>
<div style="margin-top: 10px;">
<label for="startRange">开始时间(秒):</label>
<input type="number" id="startRange" min="0" value="0">
<label for="endRange">结束时间(秒):</label>
<input type="number" id="endRange" min="0" value="10">
<button id="cutVideoButton">截取视频片段</button>
</div>
<div id="screenshotPreview" style="margin-top: 10px;">
<h4>截图预览:</h4>
</div>
<script>
let video = document.getElementById("HLSPlayer");
const url = '/output/playlist.m3u8'; // 替换为你的 HLS 流地址
if (Hls.isSupported()) {
var hls = new Hls();
hls.loadSource(url);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function () {
video.play();
});
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = url;
video.addEventListener('canplay', function () {
video.play();
});
} else {
console.log("您的浏览器不支持 HLS 视频播放。");
}
document.getElementById("speedControl").addEventListener("change", function () {
video.playbackRate = parseFloat(this.value);
});
document.getElementById("screenshotButton").addEventListener("click", function () {
const canvas = document.createElement("canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const context = canvas.getContext("2d");
context.drawImage(video, 0, 0, canvas.width, canvas.height);
const screenshot = canvas.toDataURL("image/png");
const screenshotPreview = document.getElementById("screenshotPreview");
screenshotPreview.innerHTML = '<img src="' + screenshot + '" alt="截图" style="max-width: 100%;">';
const downloadLink = document.createElement("a");
downloadLink.href = screenshot;
downloadLink.download = "screenshot.png";
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
});
document.getElementById("cutVideoButton").addEventListener("click", function () {
const start = parseFloat(document.getElementById("startRange").value);
const end = parseFloat(document.getElementById("endRange").value);
if (isNaN(start) || isNaN(end) || start >= end) {
alert("请输入有效的时间范围!");
return;
}
fetch(` + "`" + `/cut-video?start=${start}&end=${end}` + "`" + `)
.then(response => response.text())
.then(data => {
alert(data);
})
.catch(error => {
console.error("截取视频失败:", error);
alert("截取视频失败,请检查控制台日志。");
});
});
</script>
</body>
</html>
`
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, html)
}
func cutVideoHandler(w http.ResponseWriter, r *http.Request) {
start := r.URL.Query().Get("start")
end := r.URL.Query().Get("end")
if start == "" || end == "" {
http.Error(w, "缺少开始或结束时间参数", http.StatusBadRequest)
return
}
outputFile := fmt.Sprintf("%s/clip_%s_to_%s.mp4", outputDir, start, end)
cmd := exec.Command("ffmpeg", "-i", inputFile, "-ss", start, "-to", end, "-c", "copy", outputFile)
err := cmd.Run()
if err != nil {
http.Error(w, fmt.Sprintf("截取视频失败: %v", err), http.StatusInternalServerError)
return
}
w.Write([]byte(fmt.Sprintf("视频片段已成功截取并保存到 %s", outputFile)))
}
视频加水印
package main
import (
"fmt"
"log"
"net/http"
"os"
"os/exec"
)
const (
inputFile = "./extended.mp4" // 输入的视频文件
outputDir = "output" // 输出目录
segmentDuration = "4" // 分片时长
serverPort = ":8999" // 服务器端口
watermarkText = "Watermark" // 水印文本
)
func main() {
// 确保输出目录存在
if err := os.MkdirAll(outputDir, os.ModePerm); err != nil {
log.Fatalf("创建输出目录失败: %v", err)
}
// 设置静态文件服务
http.Handle("/output/", http.StripPrefix("/output/", http.FileServer(http.Dir(outputDir))))
// 设置首页
http.HandleFunc("/", serveIndex)
// 设置截取视频片段的接口
http.HandleFunc("/cut-video", cutVideoHandler)
// 设置截图并添加水印的接口
http.HandleFunc("/screenshot", screenshotHandler)
// 设置下载列表页面
http.HandleFunc("/downloads", downloadsHandler)
fmt.Printf("服务器启动在 http://localhost%s\n", serverPort)
log.Fatal(http.ListenAndServe(serverPort, nil))
}
func serveIndex(w http.ResponseWriter, r *http.Request) {
html := `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HLS 视频播放与截图功能</title>
<script type="text/javascript" src="https://cdn.jsdelivr.net/hls.js/latest/hls.min.js"></script>
</head>
<body>
<video class="video" id="HLSPlayer" controls>
<p>您的浏览器不支持 HLS 视频播放,请升级浏览器或使用支持 HLS 的设备。</p>
</video>
<div style="margin-top: 10px;">
<label for="speedControl">播放速度:</label>
<select id="speedControl">
<option value="0.5">0.5x</option>
<option value="1.0" selected>1.0x</option>
<option value="1.5">1.5x</option>
<option value="2.0">2.0x</option>
</select>
</div>
<button id="screenshotButton" style="margin-top: 10px;">截图并保存</button>
<button id="recordButton" style="margin-top: 10px;">开始录制</button>
<div id="screenshotPreview" style="margin-top: 10px;">
<h4>截图预览:</h4>
</div>
<script>
let video = document.getElementById("HLSPlayer");
const url = '/output/playlist.m3u8'; // 替换为你的 HLS 流地址
let recording = false;
let startTime = 0;
let endTime = 0;
if (Hls.isSupported()) {
var hls = new Hls();
hls.loadSource(url);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function () {
video.play();
});
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = url;
video.addEventListener('canplay', function () {
video.play();
});
} else {
console.log("您的浏览器不支持 HLS 视频播放。");
}
document.getElementById("speedControl").addEventListener("change", function () {
video.playbackRate = parseFloat(this.value);
});
document.getElementById("screenshotButton").addEventListener("click", function () {
const currentTime = video.currentTime;
fetch('/screenshot?time=' + currentTime)
.then(response => response.text())
.then(data => {
alert(data);
})
.catch(error => {
console.error("截图失败:", error);
alert("截图失败,请检查控制台日志。");
});
});
document.getElementById("recordButton").addEventListener("click", function () {
if (!recording) {
startTime = video.currentTime;
recording = true;
this.textContent = "结束录制";
} else {
endTime = video.currentTime;
recording = false;
this.textContent = "开始录制";
fetch('/cut-video?start=' + startTime + '&end=' + endTime)
.then(response => response.text())
.then(data => {
alert(data);
})
.catch(error => {
console.error("截取视频失败:", error);
alert("截取视频失败,请检查控制台日志。");
});
}
});
</script>
</body>
</html>
`
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, html)
}
func cutVideoHandler(w http.ResponseWriter, r *http.Request) {
start := r.URL.Query().Get("start")
end := r.URL.Query().Get("end")
if start == "" || end == "" {
http.Error(w, "缺少开始或结束时间参数", http.StatusBadRequest)
return
}
// 生成带水印的视频文件名
outputFile := fmt.Sprintf("%s/clip_%s_to_%s.mp4", outputDir, start, end)
tempFile := fmt.Sprintf("%s/temp_clip_%s_to_%s.mp4", outputDir, start, end)
// 第一步:截取视频片段
cmd1 := exec.Command("ffmpeg", "-i", inputFile, "-ss", start, "-to", end, "-c", "copy", tempFile)
err := cmd1.Run()
if err != nil {
http.Error(w, fmt.Sprintf("截取视频失败: %v", err), http.StatusInternalServerError)
return
}
// 第二步:给视频片段添加水印
cmd2 := exec.Command("ffmpeg", "-i", tempFile, "-vf", fmt.Sprintf("drawtext=text='%s':x=10:y=10:fontsize=24:fontcolor=white", watermarkText), "-c:a", "copy", outputFile)
err = cmd2.Run()
if err != nil {
http.Error(w, fmt.Sprintf("添加水印失败: %v", err), http.StatusInternalServerError)
return
}
// 删除临时文件
os.Remove(tempFile)
w.Write([]byte(fmt.Sprintf("视频片段已成功截取并添加水印,保存到 %s", outputFile)))
}
func screenshotHandler(w http.ResponseWriter, r *http.Request) {
time := r.URL.Query().Get("time")
if time == "" {
http.Error(w, "缺少时间参数", http.StatusBadRequest)
return
}
outputFile := fmt.Sprintf("%s/screenshot_%s.png", outputDir, time)
cmd := exec.Command("ffmpeg", "-i", inputFile, "-ss", time, "-vframes", "1", "-vf", fmt.Sprintf("drawtext=text='%s':x=10:y=10:fontsize=24:fontcolor=white", watermarkText), outputFile)
err := cmd.Run()
if err != nil {
http.Error(w, fmt.Sprintf("截图失败: %v", err), http.StatusInternalServerError)
return
}
w.Write([]byte(fmt.Sprintf("截图已成功保存到 %s", outputFile)))
}
func downloadsHandler(w http.ResponseWriter, r *http.Request) {
files, err := os.ReadDir(outputDir)
if err != nil {
http.Error(w, "无法读取下载目录", http.StatusInternalServerError)
return
}
html := `<html><body><h1>下载列表</h1><ul>`
for _, file := range files {
fileName := file.Name()
// filePath := filepath.Join(outputDir, fileName)
html += fmt.Sprintf(`<li><a href="/output/%s" download>%s</a></li>`, fileName, fileName)
}
html += `</ul></body></html>`
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, html)
}
开始录制结束录制控件
package main
import (
"fmt"
"log"
"net/http"
"os"
"os/exec"
)
const (
inputFile = "./extended.mp4" // 输入的视频文件
outputDir = "output" // 输出目录
segmentDuration = "4" // 分片时长
serverPort = ":8999" // 服务器端口
)
func main() {
// 确保输出目录存在
if err := os.MkdirAll(outputDir, os.ModePerm); err != nil {
log.Fatalf("创建输出目录失败: %v", err)
}
// 设置静态文件服务
http.Handle("/output/", http.StripPrefix("/output/", http.FileServer(http.Dir(outputDir))))
// 设置首页
http.HandleFunc("/", serveIndex)
// 设置截取视频片段的接口
http.HandleFunc("/cut-video", cutVideoHandler)
fmt.Printf("服务器启动在 http://localhost%s\n", serverPort)
log.Fatal(http.ListenAndServe(serverPort, nil))
}
func serveIndex(w http.ResponseWriter, r *http.Request) {
html := `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HLS 视频播放与截图功能</title>
<script type="text/javascript" src="https://cdn.jsdelivr.net/hls.js/latest/hls.min.js"></script>
</head>
<body>
<video class="video" id="HLSPlayer" controls>
<p>您的浏览器不支持 HLS 视频播放,请升级浏览器或使用支持 HLS 的设备。</p>
</video>
<div style="margin-top: 10px;">
<label for="speedControl">播放速度:</label>
<select id="speedControl">
<option value="0.5">0.5x</option>
<option value="1.0" selected>1.0x</option>
<option value="1.5">1.5x</option>
<option value="2.0">2.0x</option>
</select>
</div>
<button id="screenshotButton" style="margin-top: 10px;">截图并保存</button>
<button id="recordButton" style="margin-top: 10px;">开始录制</button>
<div id="screenshotPreview" style="margin-top: 10px;">
<h4>截图预览:</h4>
</div>
<script>
let video = document.getElementById("HLSPlayer");
const url = '/output/playlist.m3u8'; // 替换为你的 HLS 流地址
let recording = false;
let startTime = 0;
let endTime = 0;
if (Hls.isSupported()) {
var hls = new Hls();
hls.loadSource(url);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function () {
video.play();
});
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = url;
video.addEventListener('canplay', function () {
video.play();
});
} else {
console.log("您的浏览器不支持 HLS 视频播放。");
}
document.getElementById("speedControl").addEventListener("change", function () {
video.playbackRate = parseFloat(this.value);
});
document.getElementById("screenshotButton").addEventListener("click", function () {
const canvas = document.createElement("canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const context = canvas.getContext("2d");
context.drawImage(video, 0, 0, canvas.width, canvas.height);
const screenshot = canvas.toDataURL("image/png");
const screenshotPreview = document.getElementById("screenshotPreview");
screenshotPreview.innerHTML = '<img src="' + screenshot + '" alt="截图" style="max-width: 100%;">';
const downloadLink = document.createElement("a");
downloadLink.href = screenshot;
downloadLink.download = "screenshot.png";
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
});
document.getElementById("recordButton").addEventListener("click", function () {
if (!recording) {
// 开始录制
startTime = video.currentTime;
recording = true;
this.textContent = "结束录制";
} else {
// 结束录制
endTime = video.currentTime;
recording = false;
this.textContent = "开始录制";
// 调用后端接口截取视频片段
fetch(` + "`" + `/cut-video?start=${startTime}&end=${endTime}` + "`" + `)
.then(response => response.text())
.then(data => {
alert(data);
})
.catch(error => {
console.error("截取视频失败:", error);
alert("截取视频失败,请检查控制台日志。");
});
}
});
</script>
</body>
</html>
`
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, html)
}
func cutVideoHandler(w http.ResponseWriter, r *http.Request) {
start := r.URL.Query().Get("start")
end := r.URL.Query().Get("end")
if start == "" || end == "" {
http.Error(w, "缺少开始或结束时间参数", http.StatusBadRequest)
return
}
outputFile := fmt.Sprintf("%s/clip_%s_to_%s.mp4", outputDir, start, end)
cmd := exec.Command("ffmpeg", "-i", inputFile, "-ss", start, "-to", end, "-c", "copy", outputFile)
err := cmd.Run()
if err != nil {
http.Error(w, fmt.Sprintf("截取视频失败: %v", err), http.StatusInternalServerError)
return
}
w.Write([]byte(fmt.Sprintf("视频片段已成功截取并保存到 %s", outputFile)))
}
结合富文本编辑器,实现边看视频变做笔记
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
)
const (
inputFile = "./extended.mp4" // 输入的视频文件
outputDir = "output" // 输出目录
segmentDuration = "4" // 分片时长
serverPort = ":8999" // 服务器端口
contentFile = "content.json" // 保存笔记的文件
)
func main() {
// 确保输出目录存在
if err := os.MkdirAll(outputDir, os.ModePerm); err != nil {
log.Fatalf("创建输出目录失败: %v", err)
}
// 设置静态文件服务
http.Handle("/output/", http.StripPrefix("/output/", http.FileServer(http.Dir(outputDir))))
// 设置首页
http.HandleFunc("/", serveIndex)
// 设置截取视频片段的接口
http.HandleFunc("/cut-video", cutVideoHandler)
// 设置保存笔记的接口
http.HandleFunc("/save", handleSave)
// 设置加载笔记的接口
http.HandleFunc("/load", handleLoad)
fmt.Printf("服务器启动在 http://localhost%s\n", serverPort)
log.Fatal(http.ListenAndServe(serverPort, nil))
}
func serveIndex(w http.ResponseWriter, r *http.Request) {
html := `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>视频播放与笔记功能</title>
<script src="https://cdn.tiny.cloud/1/你的key/tinymce/6/tinymce.min.js" referrerpolicy="origin"></script>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
</head>
<body>
<h1>视频播放与笔记功能</h1>
<video class="video" id="HLSPlayer" controls>
<p>您的浏览器不支持 HLS 视频播放,请升级浏览器或使用支持 HLS 的设备。</p>
</video>
<div style="margin-top: 10px;">
<label for="speedControl">播放速度:</label>
<select id="speedControl">
<option value="0.5">0.5x</option>
<option value="1.0" selected>1.0x</option>
<option value="1.5">1.5x</option>
<option value="2.0">2.0x</option>
</select>
</div>
<button id="screenshotButton" style="margin-top: 10px;">截图并保存</button>
<button id="recordButton" style="margin-top: 10px;">开始录制</button>
<div id="screenshotPreview" style="margin-top: 10px;">
<h4>截图预览:</h4>
</div>
<textarea id="editor">在这里记录笔记...</textarea>
<br>
<button onclick="saveContent()">保存笔记</button>
<button onclick="loadContent()">加载笔记</button>
<script>
let video = document.getElementById("HLSPlayer");
const url = '/output/playlist.m3u8'; // 替换为你的 HLS 流地址
let recording = false;
let startTime = 0;
let endTime = 0;
if (Hls.isSupported()) {
var hls = new Hls();
hls.loadSource(url);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function () {
video.play();
});
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = url;
video.addEventListener('canplay', function () {
video.play();
});
} else {
console.log("您的浏览器不支持 HLS 视频播放。");
}
document.getElementById("speedControl").addEventListener("change", function () {
video.playbackRate = parseFloat(this.value);
});
document.getElementById("screenshotButton").addEventListener("click", function () {
const canvas = document.createElement("canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const context = canvas.getContext("2d");
context.drawImage(video, 0, 0, canvas.width, canvas.height);
const screenshot = canvas.toDataURL("image/png");
const screenshotPreview = document.getElementById("screenshotPreview");
screenshotPreview.innerHTML = '<img src="' + screenshot + '" alt="截图" style="max-width: 100%;">';
// 将截图插入到编辑器中
const editor = tinymce.get('editor');
editor.insertContent('<img src="' + screenshot + '" alt="截图">');
});
document.getElementById("recordButton").addEventListener("click", function () {
if (!recording) {
startTime = video.currentTime;
recording = true;
this.textContent = "结束录制";
} else {
endTime = video.currentTime;
recording = false;
this.textContent = "开始录制";
fetch('/cut-video?start=' + startTime + '&end=' + endTime)
.then(response => response.text())
.then(data => {
alert(data);
})
.catch(error => {
console.error("截取视频失败:", error);
alert("截取视频失败,请检查控制台日志。");
});
}
});
tinymce.init({
selector: '#editor',
plugins: 'anchor autolink charmap codesample emoticons image link lists media searchreplace table visualblocks wordcount',
toolbar: 'undo redo | blocks fontfamily fontsize | bold italic underline strikethrough | link image media table | align lineheight | numlist bullist indent outdent | emoticons charmap | removeformat',
});
function saveContent() {
const content = tinymce.get('editor').getContent();
fetch('/save', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ text: content }),
})
.then(response => {
if (response.ok) {
alert('笔记保存成功!');
} else {
alert('保存笔记失败。');
}
});
}
function loadContent() {
fetch('/load')
.then(response => {
if (!response.ok) {
throw new Error('加载失败');
}
return response.json();
})
.then(data => {
tinymce.get('editor').setContent(data.text);
console.log('笔记加载成功');
})
.catch(error => {
console.error('加载笔记失败:', error);
alert('加载笔记失败: ' + error.message);
});
}
</script>
</body>
</html>
`
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, html)
}
func cutVideoHandler(w http.ResponseWriter, r *http.Request) {
start := r.URL.Query().Get("start")
end := r.URL.Query().Get("end")
if start == "" || end == "" {
http.Error(w, "缺少开始或结束时间参数", http.StatusBadRequest)
return
}
outputFile := fmt.Sprintf("%s/clip_%s_to_%s.mp4", outputDir, start, end)
cmd := exec.Command("ffmpeg", "-i", inputFile, "-ss", start, "-to", end, "-c", "copy", outputFile)
err := cmd.Run()
if err != nil {
http.Error(w, fmt.Sprintf("截取视频失败: %v", err), http.StatusInternalServerError)
return
}
w.Write([]byte(fmt.Sprintf("视频片段已成功截取并保存到 %s", outputFile)))
}
// Content 结构体用于保存和加载笔记内容
type Content struct {
Text string `json:"text"`
}
// handleSave 处理保存笔记的请求
func handleSave(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var content Content
err := json.NewDecoder(r.Body).Decode(&content)
if err != nil {
http.Error(w, "Invalid request body: "+err.Error(), http.StatusBadRequest)
return
}
err = ioutil.WriteFile(contentFile, []byte(content.Text), 0644)
if err != nil {
http.Error(w, "Failed to save content: "+err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("Content saved successfully"))
}
// handleLoad 处理加载笔记的请求
func handleLoad(w http.ResponseWriter, r *http.Request) {
if _, err := os.Stat(contentFile); os.IsNotExist(err) {
http.Error(w, "No saved content found", http.StatusNotFound)
return
}
data, err := ioutil.ReadFile(contentFile)
if err != nil {
http.Error(w, "Failed to read content: "+err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Content{Text: string(data)})
}