From 3a1bbc8a7143c26862275fe9c031fc7dee830780 Mon Sep 17 00:00:00 2001
From: panda <542638787@qq.com>
Date: Sat, 7 Oct 2023 10:50:48 +0800
Subject: [PATCH 1/7] vlive support stream function
---
platform/utils.go | 3 +
platform/virtual-live-stream.go | 80 ++++++++++++++++----
ui/src/pages/ScenarioVLive.js | 130 +++++++++++++++++++++++++++++++-
ui/src/resources/locale.json | 4 +-
4 files changed, 199 insertions(+), 18 deletions(-)
diff --git a/platform/utils.go b/platform/utils.go
index a676d1fb..bc84bab3 100644
--- a/platform/utils.go
+++ b/platform/utils.go
@@ -295,6 +295,9 @@ const (
SRS_BEIAN = "SRS_BEIAN"
SRS_HTTPS = "SRS_HTTPS"
SRS_HTTPS_DOMAIN = "SRS_HTTPS_DOMAIN"
+
+ SRS_SOURCE_TYPE_FILE = "file"
+ SRS_SOURCE_TYPE_STREAM = "stream"
)
// Tencent cloud consts.
diff --git a/platform/virtual-live-stream.go b/platform/virtual-live-stream.go
index 106ebb37..ec7b7261 100644
--- a/platform/virtual-live-stream.go
+++ b/platform/virtual-live-stream.go
@@ -11,6 +11,7 @@ import (
"fmt"
"io"
"net/http"
+ "net/url"
"os"
"os/exec"
"path"
@@ -216,6 +217,45 @@ func (v *VLiveWorker) Handle(ctx context.Context, handler *http.ServeMux) error
}
})
+ ep = "/terraform/v1/ffmpeg/vlive/streamUrl/"
+ logger.Tf(ctx, "Handle %v", ep)
+ handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) {
+ if err := func() error {
+ q := r.URL.Query()
+ qUrl := q.Get("url")
+ u, err := url.Parse(qUrl)
+ if err != nil {
+ return errors.Wrapf(err, "parse %v", qUrl)
+ }
+ // check url if valid rtmp or http-flv or https-flv or hls live url
+ if u.Scheme != "rtmp" && u.Scheme != "http" && u.Scheme != "https" {
+ return errors.Errorf("invalid url scheme %v", u.Scheme)
+ }
+ if u.Scheme == "http" || u.Scheme == "https" {
+ if u.Path == "" {
+ return errors.Errorf("url path %v empty", u.Path)
+ }
+ if !strings.HasSuffix(u.Path, ".flv") && !strings.HasSuffix(u.Path, ".m3u8") {
+ return errors.Errorf("invalid url path suffix %v", u.Path)
+ }
+ }
+ targetUUID := uuid.NewString()
+ ohttp.WriteData(ctx, w, r, &struct {
+ Name string `json:"name"`
+ UUID string `json:"uuid"`
+ Target string `json:"target"`
+ }{
+ Name: path.Base(u.Path),
+ UUID: targetUUID,
+ Target: qUrl,
+ })
+ logger.Tf(ctx, "vLive stream url ok, url=%v, uuid=%v", qUrl, targetUUID)
+ return nil
+ }(); err != nil {
+ ohttp.WriteError(ctx, w, r, err)
+ }
+ })
+
ep = "/terraform/v1/ffmpeg/vlive/server/"
logger.Tf(ctx, "Handle %v", ep)
handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) {
@@ -355,6 +395,7 @@ func (v *VLiveWorker) Handle(ctx context.Context, handler *http.ServeMux) error
Size int64 `json:"size"`
UUID string `json:"uuid"`
Target string `json:"target"`
+ Type string `json:"type"`
}
var token, platform string
@@ -381,7 +422,9 @@ func (v *VLiveWorker) Handle(ctx context.Context, handler *http.ServeMux) error
// Always cleanup the files in upload.
var tempFiles []string
for _, f := range files {
- tempFiles = append(tempFiles, f.Target)
+ if f.Type != SRS_SOURCE_TYPE_STREAM {
+ tempFiles = append(tempFiles, f.Target)
+ }
}
defer func() {
for _, tempFile := range tempFiles {
@@ -397,11 +440,13 @@ func (v *VLiveWorker) Handle(ctx context.Context, handler *http.ServeMux) error
if f.Target == "" {
return errors.New("no target")
}
- if _, err := os.Stat(f.Target); err != nil {
- return errors.Wrapf(err, "no file %v", f.Target)
- }
- if !strings.HasPrefix(f.Target, dirUploadPath) {
- return errors.Errorf("invalid target %v", f.Target)
+ if f.Type != SRS_SOURCE_TYPE_STREAM {
+ if _, err := os.Stat(f.Target); err != nil {
+ return errors.Wrapf(err, "no file %v", f.Target)
+ }
+ if !strings.HasPrefix(f.Target, dirUploadPath) {
+ return errors.Errorf("invalid target %v", f.Target)
+ }
}
}
@@ -467,11 +512,15 @@ func (v *VLiveWorker) Handle(ctx context.Context, handler *http.ServeMux) error
parsedFile := &VLiveSourceFile{
Name: file.Name, Size: uint64(file.Size), UUID: file.UUID,
- Target: path.Join(dirVLivePath, fmt.Sprintf("%v%v", file.UUID, path.Ext(file.Target))),
+ Target: file.Target,
+ Type: file.Type,
Format: &format.Format, Video: matchVideo, Audio: matchAudio,
}
- if err = os.Rename(file.Target, parsedFile.Target); err != nil {
- return errors.Wrapf(err, "rename %v to %v", file.Target, parsedFile.Target)
+ if file.Type != SRS_SOURCE_TYPE_STREAM {
+ parsedFile.Target = path.Join(dirVLivePath, fmt.Sprintf("%v%v", file.UUID, path.Ext(file.Target)))
+ if err = os.Rename(file.Target, parsedFile.Target); err != nil {
+ return errors.Wrapf(err, "rename %v to %v", file.Target, parsedFile.Target)
+ }
}
parsedFiles = append(parsedFiles, parsedFile)
@@ -490,8 +539,10 @@ func (v *VLiveWorker) Handle(ctx context.Context, handler *http.ServeMux) error
// Remove old files.
for _, f := range confObj.Files {
- if _, err := os.Stat(f.Target); err == nil {
- os.Remove(f.Target)
+ if f.Type != SRS_SOURCE_TYPE_STREAM {
+ if _, err := os.Stat(f.Target); err == nil {
+ os.Remove(f.Target)
+ }
}
}
confObj.Files = parsedFiles
@@ -696,6 +747,7 @@ type VLiveSourceFile struct {
Size uint64 `json:"size"`
UUID string `json:"uuid"`
Target string `json:"target"`
+ Type string `json:"type"`
Format *VLiveFileFormat `json:"format"`
Video *VLiveFileVideo `json:"video"`
Audio *VLiveFileAudio `json:"audio"`
@@ -936,9 +988,11 @@ func (v *VLiveTask) doVLive(ctx context.Context, input *VLiveSourceFile) error {
outputURL := fmt.Sprintf("%v%v", outputServer, v.config.Secret)
// Start FFmpeg process.
- args := []string{
- "-stream_loop", "-1", "-re", "-i", input.Target, "-c", "copy", "-f", "flv", outputURL,
+ args := []string{}
+ if input.Type != SRS_SOURCE_TYPE_STREAM {
+ args = append(args, "-stream_loop", "-1")
}
+ args = append(args, "-re", "-i", input.Target, "-c", "copy", "-f", "flv", outputURL)
cmd := exec.CommandContext(ctx, "ffmpeg", args...)
stderr, err := cmd.StderrPipe()
diff --git a/ui/src/pages/ScenarioVLive.js b/ui/src/pages/ScenarioVLive.js
index 5ee88a58..79a541be 100644
--- a/ui/src/pages/ScenarioVLive.js
+++ b/ui/src/pages/ScenarioVLive.js
@@ -642,10 +642,15 @@ function VLiveFileList({files, onChangeFiles}) {
function ChooseVideoSourceCn({platform, vLiveFiles, setVLiveFiles}) {
const [checkType, setCheckType] = React.useState('upload');
+ React.useEffect(() => {
+ if (vLiveFiles?.length && vLiveFiles[0].type === 'stream') {
+ setCheckType('stream');
+ }
+ }, [vLiveFiles]);
return (<>
视频源
- * 虚拟直播就是将视频源(文件)转换成直播流
+ * 虚拟直播就是将视频源(文件/流)转换成直播流
setCheckType('upload')}
/>
@@ -668,11 +673,29 @@ function ChooseVideoSourceCn({platform, vLiveFiles, setVLiveFiles}) {
}
+
+
+ setCheckType('stream')}
+ />
+ * 流必须是 rtmp/http-flv/hls 格式
+
+ {checkType === 'stream' &&
+
+
+
+ }
+
>);
}
function ChooseVideoSourceEn({platform, vLiveFiles, setVLiveFiles}) {
const [checkType, setCheckType] = React.useState('upload');
+ React.useEffect(() => {
+ if (vLiveFiles?.length && vLiveFiles[0].type === 'stream') {
+ setCheckType('stream');
+ }
+ }, [vLiveFiles]);
return (<>
Live Stream Source
@@ -699,9 +722,100 @@ function ChooseVideoSourceEn({platform, vLiveFiles, setVLiveFiles}) {
}
+
+
+ setCheckType('stream')}
+ />
+ * The stream must be in rtmp/http-flv/hls format.
+
+ {checkType === 'stream' &&
+
+
+
+ }
+
>);
}
+function VLiveStreamSelectorCn({platform, vLiveFiles, setVLiveFiles}) {
+ const handleError = useErrorHandler();
+ const [inputStream, setInputStream] = React.useState('');
+
+ const checkStreamUrl = function() {
+ if (!inputStream) return alert('请输入流地址');
+ // check stream url if valid. start with rtmp/http-flv/hls.
+ if (!inputStream.startsWith('rtmp://') && !inputStream.startsWith('http://') && !inputStream.startsWith('https://')) return alert('流地址必须是 rtmp/http-flv/hls 格式');
+ const token = Token.load();
+ axios.post(`/terraform/v1/ffmpeg/vlive/streamUrl?url=${inputStream}`).then(res => {
+ console.log(`检查流地址成功,${JSON.stringify(res.data.data)}`);
+ const streamObj = res.data.data;
+ const files = [{name: streamObj.name, size: 0, uuid: streamObj.uuid, target: streamObj.target, type: "stream"}];
+ axios.post('/terraform/v1/ffmpeg/vlive/source', {
+ ...token, platform, files,
+ }).then(res => {
+ console.log(`更新虚拟直播源为流地址成功,${JSON.stringify(res.data.data)}`);
+ setVLiveFiles(res.data.data.files);
+ }).catch(handleError);
+ }).catch(handleError);
+ }
+
+ return (<>
+
+ {!vLiveFiles?.length && <>
+
+
+ setInputStream(e.target.value)} />
+
+
+
+
+
>
+ }
+ {vLiveFiles?.length && setVLiveFiles(null)}/>}
+
+ >);
+}
+
+function VLiveStreamSelectorEn({platform, vLiveFiles, setVLiveFiles}) {
+ const handleError = useErrorHandler();
+ const [inputStream, setInputStream] = React.useState('');
+
+ const checkStreamUrl = function() {
+ if (!inputStream) return alert('Please input stream URL');
+ // check stream url if valid. start with rtmp/http-flv/hls.
+ if (!inputStream.startsWith('rtmp://') && !inputStream.startsWith('http://') && !inputStream.startsWith('https://')) return alert('The stream must be in rtmp/http-flv/hls format.');
+ const token = Token.load();
+ axios.post(`/terraform/v1/ffmpeg/vlive/streamUrl?url=${inputStream}`).then(res => {
+ console.log(`Check stream url ok,${JSON.stringify(res.data.data)}`);
+ const streamObj = res.data.data;
+ const files = [{name: streamObj.name, size: 0, uuid: streamObj.uuid, target: streamObj.target, type: "stream"}];
+ axios.post('/terraform/v1/ffmpeg/vlive/source', {
+ ...token, platform, files,
+ }).then(res => {
+ console.log(`Setup the virtual live stream ok,${JSON.stringify(res.data.data)}`);
+ setVLiveFiles(res.data.data.files);
+ }).catch(handleError);
+ }).catch(handleError);
+ }
+
+ return (<>
+
+ {!vLiveFiles?.length && <>
+
+
+ setInputStream(e.target.value)} />
+
+
+
+
+
>
+ }
+ {vLiveFiles?.length && setVLiveFiles(null)}/>}
+
+ >)
+}
+
function VLiveFileSelectorCn({platform, vLiveFiles, setVLiveFiles}) {
const handleError = useErrorHandler();
const [inputFile, setInputFile] = React.useState('');
@@ -838,8 +952,18 @@ function VLiveFileFormatInfo({file}) {
const f = file;
if (!f?.format) return <>>;
return <>
- {Number(f?.size/1024/1024).toFixed(1)}MB
- {Number(f?.format?.duration).toFixed(0)}s
+ {f?.type !== 'stream' &&
+ <>
+ File
+ {Number(f?.size/1024/1024).toFixed(1)}MB
+ {Number(f?.format?.duration).toFixed(0)}s
+ >
+ }
+ {f?.type === 'stream' &&
+ <>
+ Stream
+ >
+ }
{Number(f?.format?.bit_rate/1000).toFixed(1)}Kbps
>;
}
diff --git a/ui/src/resources/locale.json b/ui/src/resources/locale.json
index b574b317..95349292 100644
--- a/ui/src/resources/locale.json
+++ b/ui/src/resources/locale.json
@@ -207,7 +207,7 @@
"submit": "提交",
"setOk": "设置成功",
"upload": "上传文件",
- "changeFiles": "更换文件"
+ "changeFiles": "更换文件/流"
}
}
},
@@ -419,7 +419,7 @@
"submit": "Submit",
"setOk": "Setup OK",
"upload": "Upload File",
- "changeFiles": "Change File"
+ "changeFiles": "Change File/Stream"
}
}
}
From b3b5a0ad9fd2356b79124dd294e6a3fb564c4237 Mon Sep 17 00:00:00 2001
From: panda <542638787@qq.com>
Date: Sat, 7 Oct 2023 14:52:49 +0800
Subject: [PATCH 2/7] support rtsp, distinguish file type, add timeout for
ffprobe
---
platform/utils.go | 9 +++++---
platform/virtual-live-stream.go | 38 +++++++++++++++++----------------
ui/src/pages/ScenarioVLive.js | 30 +++++++++++++++-----------
3 files changed, 44 insertions(+), 33 deletions(-)
diff --git a/platform/utils.go b/platform/utils.go
index bc84bab3..ed53f997 100644
--- a/platform/utils.go
+++ b/platform/utils.go
@@ -295,9 +295,6 @@ const (
SRS_BEIAN = "SRS_BEIAN"
SRS_HTTPS = "SRS_HTTPS"
SRS_HTTPS_DOMAIN = "SRS_HTTPS_DOMAIN"
-
- SRS_SOURCE_TYPE_FILE = "file"
- SRS_SOURCE_TYPE_STREAM = "stream"
)
// Tencent cloud consts.
@@ -306,6 +303,12 @@ const (
TENCENT_CLOUD_VOD_ENDPOINT = "vod.tencentcloudapi.com"
)
+// SrsVLiveSourceType defines the source type of vLive.
+type SrsVLiveSourceType string
+const SrsVLiveSourceTypeUpload SrsVLiveSourceType = "upload"
+const SrsVLiveSourceTypeFile SrsVLiveSourceType = "file"
+const SrsVLiveSourceTypeStream SrsVLiveSourceType = "stream"
+
// For vLive upload directory.
var dirUploadPath = path.Join(".", "upload")
var dirVLivePath = path.Join(".", "vlive")
diff --git a/platform/virtual-live-stream.go b/platform/virtual-live-stream.go
index ec7b7261..d2bff790 100644
--- a/platform/virtual-live-stream.go
+++ b/platform/virtual-live-stream.go
@@ -227,15 +227,15 @@ func (v *VLiveWorker) Handle(ctx context.Context, handler *http.ServeMux) error
if err != nil {
return errors.Wrapf(err, "parse %v", qUrl)
}
- // check url if valid rtmp or http-flv or https-flv or hls live url
- if u.Scheme != "rtmp" && u.Scheme != "http" && u.Scheme != "https" {
+ // check url if valid rtmp or rtsp or http-flv or https-flv or hls live url
+ if u.Scheme != "rtmp" && u.Scheme != "rtsp" && u.Scheme != "http" && u.Scheme != "https" {
return errors.Errorf("invalid url scheme %v", u.Scheme)
}
if u.Scheme == "http" || u.Scheme == "https" {
if u.Path == "" {
return errors.Errorf("url path %v empty", u.Path)
}
- if !strings.HasSuffix(u.Path, ".flv") && !strings.HasSuffix(u.Path, ".m3u8") {
+ if !strings.HasSuffix(u.Path, ".flv") && !strings.HasSuffix(u.Path, ".m3u8") && !strings.HasSuffix(u.Path, ".ts") {
return errors.Errorf("invalid url path suffix %v", u.Path)
}
}
@@ -395,7 +395,7 @@ func (v *VLiveWorker) Handle(ctx context.Context, handler *http.ServeMux) error
Size int64 `json:"size"`
UUID string `json:"uuid"`
Target string `json:"target"`
- Type string `json:"type"`
+ Type SrsVLiveSourceType `json:"type"`
}
var token, platform string
@@ -422,7 +422,7 @@ func (v *VLiveWorker) Handle(ctx context.Context, handler *http.ServeMux) error
// Always cleanup the files in upload.
var tempFiles []string
for _, f := range files {
- if f.Type != SRS_SOURCE_TYPE_STREAM {
+ if f.Type != SrsVLiveSourceTypeStream {
tempFiles = append(tempFiles, f.Target)
}
}
@@ -440,7 +440,7 @@ func (v *VLiveWorker) Handle(ctx context.Context, handler *http.ServeMux) error
if f.Target == "" {
return errors.New("no target")
}
- if f.Type != SRS_SOURCE_TYPE_STREAM {
+ if f.Type != SrsVLiveSourceTypeStream {
if _, err := os.Stat(f.Target); err != nil {
return errors.Wrapf(err, "no file %v", f.Target)
}
@@ -465,7 +465,9 @@ func (v *VLiveWorker) Handle(ctx context.Context, handler *http.ServeMux) error
// Parse file information and move file from dirUploadPath to dirVLivePath.
for _, file := range files {
// Probe file information.
- stdout, err := exec.CommandContext(ctx, "ffprobe",
+ toCtx, toCancelFunc := context.WithTimeout(ctx, 15*time.Second)
+ defer toCancelFunc()
+ stdout, err := exec.CommandContext(toCtx, "ffprobe",
"-show_error", "-show_private_data", "-v", "quiet", "-find_stream_info", "-print_format", "json",
"-show_format", "-show_streams", file.Target,
).Output()
@@ -516,7 +518,7 @@ func (v *VLiveWorker) Handle(ctx context.Context, handler *http.ServeMux) error
Type: file.Type,
Format: &format.Format, Video: matchVideo, Audio: matchAudio,
}
- if file.Type != SRS_SOURCE_TYPE_STREAM {
+ if file.Type != SrsVLiveSourceTypeStream {
parsedFile.Target = path.Join(dirVLivePath, fmt.Sprintf("%v%v", file.UUID, path.Ext(file.Target)))
if err = os.Rename(file.Target, parsedFile.Target); err != nil {
return errors.Wrapf(err, "rename %v to %v", file.Target, parsedFile.Target)
@@ -539,7 +541,7 @@ func (v *VLiveWorker) Handle(ctx context.Context, handler *http.ServeMux) error
// Remove old files.
for _, f := range confObj.Files {
- if f.Type != SRS_SOURCE_TYPE_STREAM {
+ if f.Type != SrsVLiveSourceTypeStream {
if _, err := os.Stat(f.Target); err == nil {
os.Remove(f.Target)
}
@@ -743,14 +745,14 @@ func (v *VLiveFileAudio) String() string {
}
type VLiveSourceFile struct {
- Name string `json:"name"`
- Size uint64 `json:"size"`
- UUID string `json:"uuid"`
- Target string `json:"target"`
- Type string `json:"type"`
- Format *VLiveFileFormat `json:"format"`
- Video *VLiveFileVideo `json:"video"`
- Audio *VLiveFileAudio `json:"audio"`
+ Name string `json:"name"`
+ Size uint64 `json:"size"`
+ UUID string `json:"uuid"`
+ Target string `json:"target"`
+ Type SrsVLiveSourceType `json:"type"`
+ Format *VLiveFileFormat `json:"format"`
+ Video *VLiveFileVideo `json:"video"`
+ Audio *VLiveFileAudio `json:"audio"`
}
func (v *VLiveSourceFile) String() string {
@@ -989,7 +991,7 @@ func (v *VLiveTask) doVLive(ctx context.Context, input *VLiveSourceFile) error {
// Start FFmpeg process.
args := []string{}
- if input.Type != SRS_SOURCE_TYPE_STREAM {
+ if input.Type != SrsVLiveSourceTypeStream {
args = append(args, "-stream_loop", "-1")
}
args = append(args, "-re", "-i", input.Target, "-c", "copy", "-f", "flv", outputURL)
diff --git a/ui/src/pages/ScenarioVLive.js b/ui/src/pages/ScenarioVLive.js
index 79a541be..ad9739eb 100644
--- a/ui/src/pages/ScenarioVLive.js
+++ b/ui/src/pages/ScenarioVLive.js
@@ -643,8 +643,11 @@ function VLiveFileList({files, onChangeFiles}) {
function ChooseVideoSourceCn({platform, vLiveFiles, setVLiveFiles}) {
const [checkType, setCheckType] = React.useState('upload');
React.useEffect(() => {
- if (vLiveFiles?.length && vLiveFiles[0].type === 'stream') {
- setCheckType('stream');
+ if (vLiveFiles?.length) {
+ const type = vLiveFiles[0].type;
+ if (type === 'upload' || type === 'server' || type === 'stream') {
+ setCheckType(type);
+ }
}
}, [vLiveFiles]);
return (<>
@@ -692,8 +695,11 @@ function ChooseVideoSourceCn({platform, vLiveFiles, setVLiveFiles}) {
function ChooseVideoSourceEn({platform, vLiveFiles, setVLiveFiles}) {
const [checkType, setCheckType] = React.useState('upload');
React.useEffect(() => {
- if (vLiveFiles?.length && vLiveFiles[0].type === 'stream') {
- setCheckType('stream');
+ if (vLiveFiles?.length) {
+ const type = vLiveFiles[0].type;
+ if (type === 'upload' || type === 'server' || type === 'stream') {
+ setCheckType(type);
+ }
}
}, [vLiveFiles]);
return (<>
@@ -744,8 +750,8 @@ function VLiveStreamSelectorCn({platform, vLiveFiles, setVLiveFiles}) {
const checkStreamUrl = function() {
if (!inputStream) return alert('请输入流地址');
- // check stream url if valid. start with rtmp/http-flv/hls.
- if (!inputStream.startsWith('rtmp://') && !inputStream.startsWith('http://') && !inputStream.startsWith('https://')) return alert('流地址必须是 rtmp/http-flv/hls 格式');
+ // check stream url if valid. start with rtmp/rtsp/http-flv/hls.
+ if (!inputStream.startsWith('rtmp://') && !inputStream.startsWith('rtsp://') && !inputStream.startsWith('http://') && !inputStream.startsWith('https://')) return alert('流地址必须是 rtmp/rtsp/http-flv/hls 格式');
const token = Token.load();
axios.post(`/terraform/v1/ffmpeg/vlive/streamUrl?url=${inputStream}`).then(res => {
console.log(`检查流地址成功,${JSON.stringify(res.data.data)}`);
@@ -783,8 +789,8 @@ function VLiveStreamSelectorEn({platform, vLiveFiles, setVLiveFiles}) {
const checkStreamUrl = function() {
if (!inputStream) return alert('Please input stream URL');
- // check stream url if valid. start with rtmp/http-flv/hls.
- if (!inputStream.startsWith('rtmp://') && !inputStream.startsWith('http://') && !inputStream.startsWith('https://')) return alert('The stream must be in rtmp/http-flv/hls format.');
+ // check stream url if valid. start with rtmp/rtsp/http-flv/hls.
+ if (!inputStream.startsWith('rtmp://') && !inputStream.startsWith('rtsp://') && !inputStream.startsWith('http://') && !inputStream.startsWith('https://')) return alert('The stream must be in rtmp/rtsp/http-flv/hls format.');
const token = Token.load();
axios.post(`/terraform/v1/ffmpeg/vlive/streamUrl?url=${inputStream}`).then(res => {
console.log(`Check stream url ok,${JSON.stringify(res.data.data)}`);
@@ -831,7 +837,7 @@ function VLiveFileSelectorCn({platform, vLiveFiles, setVLiveFiles}) {
axios.post(`/terraform/v1/ffmpeg/vlive/server?file=${inputFile}`).then(res => {
console.log(`检查服务器文件成功,${JSON.stringify(res.data.data)}`);
const localFileObj = res.data.data;
- const files = [{name: localFileObj.name, size: localFileObj.size, uuid: localFileObj.uuid, target: localFileObj.target}];
+ const files = [{name: localFileObj.name, size: localFileObj.size, uuid: localFileObj.uuid, target: localFileObj.target, type: "file"}];
axios.post('/terraform/v1/ffmpeg/vlive/source', {
...token, platform, files,
}).then(res => {
@@ -873,7 +879,7 @@ function VLiveFileSelectorEn({platform, vLiveFiles, setVLiveFiles}) {
axios.post(`/terraform/v1/ffmpeg/vlive/server?file=${inputFile}`).then(res => {
console.log(`Check server file ok,${JSON.stringify(res.data.data)}`);
const localFileObj = res.data.data;
- const files = [{name: localFileObj.name, size: localFileObj.size, uuid: localFileObj.uuid, target: localFileObj.target}];
+ const files = [{name: localFileObj.name, size: localFileObj.size, uuid: localFileObj.uuid, target: localFileObj.target, type: "file"}];
axios.post('/terraform/v1/ffmpeg/vlive/source', {
...token, platform, files,
}).then(res => {
@@ -908,7 +914,7 @@ function VLiveFileUploaderCn({platform, vLiveFiles, setVLiveFiles}) {
const token = Token.load();
axios.post('/terraform/v1/ffmpeg/vlive/source', {
...token, platform, files: files.map(f => {
- return {name: f.name, size: f.size, uuid: f.uuid, target: f.target};
+ return {name: f.name, size: f.size, uuid: f.uuid, target: f.target, type: "upload"};
}),
}).then(res => {
console.log(`虚拟直播文件源设置成功, ${JSON.stringify(res.data.data)}`);
@@ -932,7 +938,7 @@ function VLiveFileUploaderEn({platform, vLiveFiles, setVLiveFiles}) {
const token = Token.load();
axios.post('/terraform/v1/ffmpeg/vlive/source', {
...token, platform, files: files.map(f => {
- return {name: f.name, size: f.size, uuid: f.uuid, target: f.target};
+ return {name: f.name, size: f.size, uuid: f.uuid, target: f.target, type: "upload"};
}),
}).then(res => {
console.log(`Set file source ok, ${JSON.stringify(res.data.data)}`);
From e46c32a71f148d71ca15da79019388c6a4144a73 Mon Sep 17 00:00:00 2001
From: winlin
Date: Sat, 7 Oct 2023 15:32:27 +0800
Subject: [PATCH 3/7] Refine UI.
---
ui/src/pages/ScenarioVLive.js | 20 ++++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/ui/src/pages/ScenarioVLive.js b/ui/src/pages/ScenarioVLive.js
index ad9739eb..84479f3e 100644
--- a/ui/src/pages/ScenarioVLive.js
+++ b/ui/src/pages/ScenarioVLive.js
@@ -678,10 +678,10 @@ function ChooseVideoSourceCn({platform, vLiveFiles, setVLiveFiles}) {
- setCheckType('stream')}
/>
- * 流必须是 rtmp/http-flv/hls 格式
+ * 流地址支持 rtmp, http, https, 或 rtsp 等格式
{checkType === 'stream' &&
@@ -730,10 +730,10 @@ function ChooseVideoSourceEn({platform, vLiveFiles, setVLiveFiles}) {
- setCheckType('stream')}
/>
- * The stream must be in rtmp/http-flv/hls format.
+ * The stream URL should start with rtmp, http, https, or rtsp.
{checkType === 'stream' &&
@@ -750,8 +750,10 @@ function VLiveStreamSelectorCn({platform, vLiveFiles, setVLiveFiles}) {
const checkStreamUrl = function() {
if (!inputStream) return alert('请输入流地址');
- // check stream url if valid. start with rtmp/rtsp/http-flv/hls.
- if (!inputStream.startsWith('rtmp://') && !inputStream.startsWith('rtsp://') && !inputStream.startsWith('http://') && !inputStream.startsWith('https://')) return alert('流地址必须是 rtmp/rtsp/http-flv/hls 格式');
+ const isHTTP = inputStream.startsWith('http://') || inputStream.startsWith('https://');
+ if (!inputStream.startsWith('rtmp://') && !inputStream.startsWith('rtsp://') && !isHTTP) return alert('流地址必须是 rtmp/http/https/rtsp 格式');
+ if (isHTTP && inputStream.indexOf('.flv') < 0 && inputStream.indexOf('.m3u8') < 0) return alert('HTTP流必须是 http-flv或hls 格式');
+
const token = Token.load();
axios.post(`/terraform/v1/ffmpeg/vlive/streamUrl?url=${inputStream}`).then(res => {
console.log(`检查流地址成功,${JSON.stringify(res.data.data)}`);
@@ -789,8 +791,10 @@ function VLiveStreamSelectorEn({platform, vLiveFiles, setVLiveFiles}) {
const checkStreamUrl = function() {
if (!inputStream) return alert('Please input stream URL');
- // check stream url if valid. start with rtmp/rtsp/http-flv/hls.
- if (!inputStream.startsWith('rtmp://') && !inputStream.startsWith('rtsp://') && !inputStream.startsWith('http://') && !inputStream.startsWith('https://')) return alert('The stream must be in rtmp/rtsp/http-flv/hls format.');
+ const isHTTP = inputStream.startsWith('http://') || inputStream.startsWith('https://');
+ if (!inputStream.startsWith('rtmp://') && !inputStream.startsWith('rtsp://') && !isHTTP) return alert('The stream must be rtmp/http/https/rtsp');
+ if (isHTTP && inputStream.indexOf('.flv') < 0 && inputStream.indexOf('.m3u8') < 0) return alert('The HTTP stream must be http-flv/hls');
+
const token = Token.load();
axios.post(`/terraform/v1/ffmpeg/vlive/streamUrl?url=${inputStream}`).then(res => {
console.log(`Check stream url ok,${JSON.stringify(res.data.data)}`);
From 003fb8b90eda335e6970abe7dc479982d962d10d Mon Sep 17 00:00:00 2001
From: winlin
Date: Sat, 7 Oct 2023 15:34:30 +0800
Subject: [PATCH 4/7] Refine UI.
---
ui/src/pages/ScenarioVLive.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/ui/src/pages/ScenarioVLive.js b/ui/src/pages/ScenarioVLive.js
index 84479f3e..fc1d2868 100644
--- a/ui/src/pages/ScenarioVLive.js
+++ b/ui/src/pages/ScenarioVLive.js
@@ -651,7 +651,7 @@ function ChooseVideoSourceCn({platform, vLiveFiles, setVLiveFiles}) {
}
}, [vLiveFiles]);
return (<>
-
+
视频源
* 虚拟直播就是将视频源(文件/流)转换成直播流
-
+
Live Stream Source
* Virtual live streaming is the process of converting a video source (file) into a live stream.
Date: Tue, 10 Oct 2023 11:40:06 +0800
Subject: [PATCH 5/7] add test case for vlive stream
---
test/scenario_test.go | 196 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 196 insertions(+)
diff --git a/test/scenario_test.go b/test/scenario_test.go
index ad947014..443d797e 100644
--- a/test/scenario_test.go
+++ b/test/scenario_test.go
@@ -16,6 +16,202 @@ import (
"github.com/ossrs/go-oryx-lib/logger"
)
+func TestApi_PublishVliveStreamUrl(t *testing.T) {
+ ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
+ defer cancel()
+
+ if *noMediaTest {
+ return
+ }
+
+ var r0, r1, r2, r3, r4, r5 error
+ defer func(ctx context.Context) {
+ if err := filterTestError(ctx.Err(), r0, r1, r2, r3, r4, r5); err != nil {
+ t.Errorf("Fail for err %+v", err)
+ } else {
+ logger.Tf(ctx, "test done")
+ }
+ }(ctx)
+
+ var pubSecret string
+ if err := apiRequest(ctx, "/terraform/v1/hooks/srs/secret/query", nil, &struct {
+ Publish *string `json:"publish"`
+ }{
+ Publish: &pubSecret,
+ }); err != nil {
+ r0 = err
+ return
+ }
+
+ var wg sync.WaitGroup
+ defer wg.Wait()
+ // Start FFmpeg to publish stream.
+ streamID := fmt.Sprintf("stream-%v-%v", os.Getpid(), rand.Int())
+ streamURL := fmt.Sprintf("%v/live/%v?secret=%v", *endpointRTMP, streamID, pubSecret)
+ ffmpeg := NewFFmpeg(func(v *ffmpegClient) {
+ v.args = []string{
+ "-re", "-stream_loop", "-1", "-i", *srsInputFile, "-c", "copy",
+ "-f", "flv", streamURL,
+ }
+ })
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ r1 = ffmpeg.Run(ctx, cancel)
+ }()
+
+ defer cancel()
+ select {
+ case <-ctx.Done():
+ return
+ case <-ffmpeg.ReadyCtx().Done():
+ }
+
+ // Use the publish stream url as the vlive input.
+ res := struct {
+ Name string `json:"name"`
+ Target string `json:"target"`
+ UUID string `json:"uuid"`
+ Size int64 `json:"size"`
+ Type string `json:"type"`
+ }{}
+ if err := apiRequest(ctx, "/terraform/v1/ffmpeg/vlive/streamUrl?url="+streamURL, nil, &res); err != nil {
+ r0 = errors.Wrapf(err, "request ffmpeg vlive streamUrl failed")
+ return
+ }
+
+ // Use the publish stream url as the vlive source.
+ res.Size = 0
+ res.Type = "stream"
+ codec := struct {
+ UUID string `json:"uuid"`
+ Audio struct {
+ CodecName string `json:"codec_name"`
+ Channels int `json:"channels"`
+ SampleRate string `json:"sample_rate"`
+ } `json:"audio"`
+ Video struct {
+ CodecName string `json:"codec_name"`
+ Profile string `json:"profile"`
+ Width int `json:"width"`
+ Height int `json:"height"`
+ } `json:"video"`
+ }{}
+ if err := apiRequest(ctx, "/terraform/v1/ffmpeg/vlive/source", &struct {
+ Platform string `json:"platform"`
+ Files []interface{} `json:"files"`
+ }{
+ Platform: "bilibili",
+ Files: []interface{}{res},
+ }, &struct {
+ Files []interface{} `json:"files"`
+ }{
+ Files: []interface{}{&codec},
+ }); err != nil {
+ r0 = errors.Wrapf(err, "request ffmpeg vlive source failed")
+ return
+ }
+
+ if err := func() error {
+ if codec.UUID != res.UUID {
+ return errors.Errorf("invalid codec uuid=%v, %v", codec.UUID, res.UUID)
+ }
+ if codec.Audio.CodecName != "aac" || codec.Audio.Channels != 2 || codec.Audio.SampleRate != "44100" {
+ return errors.Errorf("invalid codec audio=%v", codec.Audio)
+ }
+ if codec.Video.CodecName != "h264" || codec.Video.Profile != "High" || codec.Video.Width != 768 || codec.Video.Height != 320 {
+ return errors.Errorf("invalid codec video=%v", codec.Video)
+ }
+ return nil
+ }(); err != nil {
+ r0 = errors.Wrapf(err, "request ffmpeg vlive source failed")
+ return
+ }
+
+ // Start virtual live streaming.
+ type VLiveConfig struct {
+ Platform string `json:"platform"`
+ Server string `json:"server"`
+ Secret string `json:"secret"`
+ Enabled bool `json:"enabled"`
+ Custom bool `json:"custom"`
+ Label string `json:"label"`
+ Files interface{} `json:"files"`
+ Action string `json:"action"`
+ }
+ conf := make(map[string]*VLiveConfig)
+ if err := apiRequest(ctx, "/terraform/v1/ffmpeg/vlive/secret", nil, &conf); err != nil {
+ r0 = errors.Wrapf(err, "request ffmpeg vlive secret failed")
+ return
+ }
+
+ bilibili, ok := conf["bilibili"]
+ if !ok || bilibili == nil {
+ r0 = errors.Errorf("invalid bilibili secret")
+ return
+ }
+ bilibili.Action = "update"
+ bilibili.Custom = true
+
+ // Restore the state of enabled.
+ backup := *bilibili
+ defer func() {
+ logger.Tf(ctx, "restore config %v", backup)
+
+ if backup.Server == "" {
+ backup.Server = bilibili.Server
+ }
+ if backup.Secret == "" {
+ backup.Secret = bilibili.Secret
+ }
+
+ // The ctx has already been cancelled by test case, which will cause the request failed.
+ ctx := context.Background()
+ apiRequest(ctx, "/terraform/v1/ffmpeg/vlive/secret", backup, nil)
+ }()
+
+ publishStreamID := fmt.Sprintf("publish-stream-%v-%v", os.Getpid(), rand.Int())
+ bilibili.Secret = fmt.Sprintf("%v?secret=%v", publishStreamID, pubSecret)
+ bilibili.Server = "rtmp://localhost/live/"
+ bilibili.Enabled = true
+ if err := apiRequest(ctx, "/terraform/v1/ffmpeg/vlive/secret", &bilibili, nil); err != nil {
+ r0 = errors.Wrapf(err, "request ffmpeg vlive secret failed")
+ return
+ }
+
+ // Start FFprobe to detect and verify stream.
+ duration := time.Duration(*srsFFprobeDuration) * time.Millisecond
+ ffprobe := NewFFprobe(func(v *ffprobeClient) {
+ v.dvrFile = fmt.Sprintf("srs-ffprobe-%v.flv", publishStreamID)
+ v.streamURL = fmt.Sprintf("%v/live/%v.flv", *endpointHTTP, publishStreamID)
+ v.duration, v.timeout = duration, time.Duration(*srsFFprobeTimeout)*time.Millisecond
+ })
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ r2 = ffprobe.Run(ctx, cancel)
+ }()
+
+ // Fast quit for probe done.
+ select {
+ case <-ctx.Done():
+ case <-ffprobe.ProbeDoneCtx().Done():
+ cancel()
+ }
+
+ str, m := ffprobe.Result()
+ if len(m.Streams) != 2 {
+ r3 = errors.Errorf("invalid streams=%v, %v, %v", len(m.Streams), m.String(), str)
+ }
+
+ if ts := 90; m.Format.ProbeScore < ts {
+ r4 = errors.Errorf("low score=%v < %v, %v, %v", m.Format.ProbeScore, ts, m.String(), str)
+ }
+ if dv := m.Duration(); dv < duration/2 {
+ r5 = errors.Errorf("short duration=%v < %v, %v, %v", dv, duration, m.String(), str)
+ }
+}
+
func TestApi_PublishVLivePlayFlv(t *testing.T) {
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
defer cancel()
From 8f5592f9ae0da0aa50174b77e6d6f94ae475793d Mon Sep 17 00:00:00 2001
From: winlin
Date: Tue, 10 Oct 2023 19:56:02 +0800
Subject: [PATCH 6/7] Add pullrequest action without secrets.
---
.github/workflows/pullrequest.yml | 417 ++++++++++++++++++++++++++++++
1 file changed, 417 insertions(+)
create mode 100644 .github/workflows/pullrequest.yml
diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml
new file mode 100644
index 00000000..cd639654
--- /dev/null
+++ b/.github/workflows/pullrequest.yml
@@ -0,0 +1,417 @@
+name: Test Dev Environment
+
+on: [pull_request]
+
+jobs:
+ envs:
+ name: envs
+ steps:
+ ##################################################################################################################
+ # Git checkout
+ - name: Checkout repository
+ uses: actions/checkout@v3
+ # The github.ref is, for example, refs/tags/v5.0.145 or refs/tags/v5.0-r8
+ # Generate variables like:
+ # SRS_TAG=v1.0.52
+ # SRS_MAJOR=1
+ # @see https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable
+ - name: Generate varaiables
+ run: |
+ SRS_TAG=$(bash scripts/version.sh)
+ echo "SRS_TAG=$SRS_TAG" >> $GITHUB_ENV
+ SRS_MAJOR=$(echo $SRS_TAG| awk -F '.' '{print $1}' |sed 's/v//g')
+ echo "SRS_MAJOR=$SRS_MAJOR" >> $GITHUB_ENV
+ echo "SRS_TAG:$SRS_TAG, SRS_MAJOR:$SRS_MAJOR"
+ # Map a step output to a job output, see https://docs.github.com/en/actions/using-jobs/defining-outputs-for-jobs
+ outputs:
+ SRS_TAG: ${{ env.SRS_TAG }}
+ SRS_MAJOR: ${{ env.SRS_MAJOR }}
+ runs-on: ubuntu-20.04
+
+ run-test:
+ name: Run UTest
+ runs-on: ubuntu-20.04
+ needs:
+ - envs
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+ - name: Download test file
+ run: |
+ curl --location --output test/source.200kbps.768x320.flv \
+ https://github.com/ossrs/srs/raw/develop/trunk/doc/source.200kbps.768x320.flv
+ - name: Setup the npm node
+ uses: actions/setup-node@v3
+ with:
+ node-version: '18'
+ cache: 'npm'
+ cache-dependency-path: |
+ ui/package-lock.json
+ - name: Setup the Go
+ uses: actions/setup-go@v3
+ with:
+ go-version: '>=1.16.0'
+ - name: Install tools
+ run: |
+ docker run --rm -v /usr/bin:/g ossrs/srs:tools \
+ cp /usr/local/bin/ffmpeg /usr/local/bin/ffprobe /g/
+ ffmpeg -version
+ - name: Test by jest and Go
+ run: |
+ make -j && make test -j
+
+ build-platform-image:
+ name: Build platform image
+ runs-on: ubuntu-20.04
+ needs:
+ - envs
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+ - name: Build image for platform
+ run: |
+ docker build -t platform:latest -f Dockerfile .
+ docker images
+ docker save -o platform.tar platform:latest
+ - uses: actions/upload-artifact@v3
+ with:
+ name: platform-cache
+ path: platform.tar
+ retention-days: 1
+
+ test-zh-image:
+ name: Test ZH image
+ needs:
+ - envs
+ - build-platform-image
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+ - name: Download test file
+ run: |
+ curl --location --output test/source.200kbps.768x320.flv \
+ https://github.com/ossrs/srs/raw/develop/trunk/doc/source.200kbps.768x320.flv
+ - name: Install tools
+ run: |
+ docker run --rm -v /usr/bin:/g ossrs/srs:tools \
+ cp /usr/local/bin/ffmpeg /usr/local/bin/ffprobe /g/
+ ffmpeg -version
+ - uses: actions/download-artifact@v3
+ with:
+ name: platform-cache
+ - name: Run test for platform image
+ run: |
+ docker load -i platform.tar
+ docker tag platform ossrs/srs-stack:5
+ docker run --rm -d -p 2022:2022 -p 2443:2443 -p 1935:1935 \
+ -p 8080:8080 -p 8000:8000/udp -p 10080:10080/udp --name srs-stack \
+ -v /data:/data -e REACT_APP_LOCALE=zh ossrs/srs-stack:5
+ - name: Check and Test service
+ run: |
+ # We will handle the error by ourselves.
+ set +e
+
+ # Record all logs.
+ docker logs -f srs-stack >docker.log 2>&1 & pid_docker=$!
+
+ echo "Wait for service ready." &&
+ make -j -C test &&
+ bash scripts/tools/secret.sh --output test/.env &&
+ ./test/srs-stack.test -test.v -endpoint http://localhost:2022 \
+ -srs-log=true -wait-ready=true -init-password=true -init-self-signed-cert=true \
+ -check-api-secret=true -test.run TestApi_Empty &&
+
+ echo "Make upload writable." &&
+ sudo chmod 777 /data/upload &&
+
+ echo "Test HTTP service." &&
+ bash scripts/tools/secret.sh --output test/.env &&
+ ./test/srs-stack.test -test.v -endpoint http://localhost:2022 \
+ -srs-log=true -wait-ready=true -init-password=false -init-self-signed-cert=false \
+ -check-api-secret=true -no-media-test &&
+
+ echo "Test HTTPS service." &&
+ bash scripts/tools/secret.sh --output test/.env &&
+ ./test/srs-stack.test -test.v -endpoint https://localhost:2443 \
+ -srs-log=true -wait-ready=true -init-password=false -init-self-signed-cert=false \
+ -check-api-secret=true -no-media-test &&
+
+ echo "Run media test with retry" &&
+ bash scripts/tools/secret.sh --output test/.env &&
+ ./scripts/tools/failed-retry.sh 1 ./test/srs-stack.test -test.v -endpoint http://localhost:2022 \
+ -srs-log=true -wait-ready=true -init-password=false -init-self-signed-cert=false \
+ -check-api-secret=true -test.run TestApi_Publish*
+ ret=$?; echo "Test result: $ret"
+
+ echo "Stop service"
+ docker stop srs-stack
+ kill $pid_docker 2>/dev/null
+ echo "Log of docker.log" && cat docker.log
+
+ exit $ret
+ runs-on: ubuntu-20.04
+
+ test-en-image:
+ name: Test EN image
+ needs:
+ - envs
+ - build-platform-image
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+ - name: Download test file
+ run: |
+ curl --location --output test/source.200kbps.768x320.flv \
+ https://github.com/ossrs/srs/raw/develop/trunk/doc/source.200kbps.768x320.flv
+ - name: Install tools
+ run: |
+ docker run --rm -v /usr/bin:/g ossrs/srs:tools \
+ cp /usr/local/bin/ffmpeg /usr/local/bin/ffprobe /g/
+ ffmpeg -version
+ - uses: actions/download-artifact@v3
+ with:
+ name: platform-cache
+ - name: Run test for platform image
+ run: |
+ docker load -i platform.tar
+ docker tag platform ossrs/srs-stack:5
+ docker run --rm -d -p 2022:2022 -p 2443:2443 -p 1935:1935 \
+ -p 8080:8080 -p 8000:8000/udp -p 10080:10080/udp --name srs-stack \
+ -v /data:/data -e REACT_APP_LOCALE=en ossrs/srs-stack:5
+ - name: Check and Test service
+ run: |
+ # We will handle the error by ourselves.
+ set +e
+
+ # Record all logs.
+ docker logs -f srs-stack >docker.log 2>&1 & pid_docker=$!
+
+ echo "Wait for service ready." &&
+ make -j -C test &&
+ bash scripts/tools/secret.sh --output test/.env &&
+ ./test/srs-stack.test -test.v -endpoint http://localhost:2022 \
+ -srs-log=true -wait-ready=true -init-password=true -init-self-signed-cert=true \
+ -check-api-secret=true -test.run TestApi_Empty &&
+
+ echo "Make upload writable." &&
+ sudo chmod 777 /data/upload &&
+
+ echo "Test HTTP service." &&
+ bash scripts/tools/secret.sh --output test/.env &&
+ ./test/srs-stack.test -test.v -endpoint http://localhost:2022 \
+ -srs-log=true -wait-ready=true -init-password=false -init-self-signed-cert=false \
+ -check-api-secret=true -no-media-test &&
+
+ echo "Test HTTPS service." &&
+ bash scripts/tools/secret.sh --output test/.env &&
+ ./test/srs-stack.test -test.v -endpoint https://localhost:2443 \
+ -srs-log=true -wait-ready=true -init-password=false -init-self-signed-cert=false \
+ -check-api-secret=true -no-media-test &&
+
+ echo "Run media test with retry" &&
+ bash scripts/tools/secret.sh --output test/.env &&
+ ./scripts/tools/failed-retry.sh 1 ./test/srs-stack.test -test.v -endpoint http://localhost:2022 \
+ -srs-log=true -wait-ready=true -init-password=false -init-self-signed-cert=false \
+ -check-api-secret=true -test.run TestApi_Publish*
+ ret=$?; echo "Test result: $ret"
+
+ echo "Stop service"
+ docker stop srs-stack
+ kill $pid_docker 2>/dev/null
+ echo "Log of docker.log" && cat docker.log
+
+ exit $ret
+ runs-on: ubuntu-20.04
+
+ test-zh-installer:
+ name: Test ZH installer
+ runs-on: ubuntu-20.04
+ needs:
+ - envs
+ - build-platform-image
+ steps:
+ - name: Covert output to env
+ run: |
+ echo "SRS_TAG=${{ needs.envs.outputs.SRS_TAG }}" >> $GITHUB_ENV
+ echo "SRS_MAJOR=${{ needs.envs.outputs.SRS_MAJOR }}" >> $GITHUB_ENV
+ - name: Checkout repository
+ uses: actions/checkout@v3
+ - name: Download test file
+ run: |
+ curl --location --output test/source.200kbps.768x320.flv \
+ https://github.com/ossrs/srs/raw/develop/trunk/doc/source.200kbps.768x320.flv
+ - name: Install tools
+ run: |
+ docker run --rm -v /usr/bin:/g ossrs/srs:tools \
+ cp /usr/local/bin/ffmpeg /usr/local/bin/ffprobe /g/
+ ffmpeg -version
+ - name: Start Nginx service
+ run: |
+ sudo systemctl start nginx
+ sudo systemctl status nginx
+ - uses: actions/download-artifact@v3
+ with:
+ name: platform-cache
+ - name: Load platform image
+ run: |
+ docker load -i platform.tar
+ docker tag platform:latest ossrs/srs-stack:$SRS_TAG
+ docker tag platform:latest registry.cn-hangzhou.aliyuncs.com/ossrs/srs-stack:$SRS_TAG
+ docker images
+ - name: Build package
+ run: |
+ bash scripts/setup-ubuntu/build.sh --language zh --version $SRS_TAG \
+ --output $(pwd)/build --extract
+ du -sh $(pwd)/build/*
+ - name: Install package
+ run: |
+ sudo bash build/srs_stack/scripts/setup-ubuntu/install.sh --verbose
+ echo "" && echo "/usr/local/srs-stack/" && du -sh /usr/local/srs-stack/*
+ echo "" && ls -lha /data /data/config
+ - name: Check and Test service
+ run: |
+ # We will handle the error by ourselves.
+ set +e
+
+ # Record all logs.
+ journalctl -u srs-stack -f >journalctl.log 2>&1 & pid_journalctl=$!
+
+ echo "Wait for service ready." &&
+ make -j -C test &&
+ bash scripts/tools/secret.sh --output test/.env &&
+ ./test/srs-stack.test -test.v -endpoint http://localhost:2022 \
+ -srs-log=true -wait-ready=true -init-password=true -init-self-signed-cert=true \
+ -check-api-secret=true -test.run TestApi_Empty &&
+
+ echo "Make upload writable." &&
+ sudo chmod 777 /data/upload &&
+
+ echo "Test HTTP service." &&
+ bash scripts/tools/secret.sh --output test/.env &&
+ ./test/srs-stack.test -test.v -endpoint http://localhost:2022 \
+ -srs-log=true -wait-ready=true -init-password=false -init-self-signed-cert=false \
+ -check-api-secret=true -no-media-test &&
+
+ echo "Test HTTPS service." &&
+ bash scripts/tools/secret.sh --output test/.env &&
+ ./test/srs-stack.test -test.v -endpoint https://localhost:2443 \
+ -srs-log=true -wait-ready=true -init-password=false -init-self-signed-cert=false \
+ -check-api-secret=true -no-media-test &&
+
+ echo "Run media test with retry" &&
+ bash scripts/tools/secret.sh --output test/.env &&
+ ./scripts/tools/failed-retry.sh 1 ./test/srs-stack.test -test.v -endpoint http://localhost:2022 \
+ -srs-log=true -wait-ready=true -init-password=false -init-self-signed-cert=false \
+ -check-api-secret=true -test.run TestApi_Publish*
+ ret=$?; echo "Test result: $ret"
+
+ echo "Stop service"
+ sudo systemctl stop srs-stack
+ kill $pid_journalctl 2>/dev/null
+ echo "Log of journalctl.log" && cat journalctl.log
+
+ exit $ret
+
+ test-en-installer:
+ name: Test EN installer
+ runs-on: ubuntu-20.04
+ needs:
+ - envs
+ - build-platform-image
+ steps:
+ - name: Covert output to env
+ run: |
+ echo "SRS_TAG=${{ needs.envs.outputs.SRS_TAG }}" >> $GITHUB_ENV
+ echo "SRS_MAJOR=${{ needs.envs.outputs.SRS_MAJOR }}" >> $GITHUB_ENV
+ - name: Checkout repository
+ uses: actions/checkout@v3
+ - name: Download test file
+ run: |
+ curl --location --output test/source.200kbps.768x320.flv \
+ https://github.com/ossrs/srs/raw/develop/trunk/doc/source.200kbps.768x320.flv
+ - name: Install tools
+ run: |
+ docker run --rm -v /usr/bin:/g ossrs/srs:tools \
+ cp /usr/local/bin/ffmpeg /usr/local/bin/ffprobe /g/
+ ffmpeg -version
+ - name: Start Nginx service
+ run: |
+ sudo systemctl start nginx
+ sudo systemctl status nginx
+ - uses: actions/download-artifact@v3
+ with:
+ name: platform-cache
+ - name: Load platform image
+ run: |
+ docker load -i platform.tar
+ docker tag platform:latest ossrs/srs-stack:$SRS_TAG
+ docker tag platform:latest registry.cn-hangzhou.aliyuncs.com/ossrs/srs-stack:$SRS_TAG
+ docker images
+ - name: Build package
+ run: |
+ bash scripts/setup-ubuntu/build.sh --language en --version $SRS_TAG \
+ --output $(pwd)/build --extract
+ du -sh $(pwd)/build/*
+ - name: Install package
+ run: |
+ sudo bash build/srs_stack/scripts/setup-ubuntu/install.sh --verbose
+ echo "" && echo "/usr/local/srs-stack/" && du -sh /usr/local/srs-stack/*
+ echo "" && ls -lha /data /data/config
+ - name: Check and Test service
+ run: |
+ # We will handle the error by ourselves.
+ set +e
+
+ # Record all logs.
+ journalctl -u srs-stack -f >journalctl.log 2>&1 & pid_journalctl=$!
+
+ echo "Wait for service ready." &&
+ make -j -C test &&
+ bash scripts/tools/secret.sh --output test/.env &&
+ ./test/srs-stack.test -test.v -endpoint http://localhost:2022 \
+ -srs-log=true -wait-ready=true -init-password=true -init-self-signed-cert=true \
+ -check-api-secret=true -test.run TestApi_Empty &&
+
+ echo "Make upload writable." &&
+ sudo chmod 777 /data/upload &&
+
+ echo "Test HTTP service." &&
+ bash scripts/tools/secret.sh --output test/.env &&
+ ./test/srs-stack.test -test.v -endpoint http://localhost:2022 \
+ -srs-log=true -wait-ready=true -init-password=false -init-self-signed-cert=false \
+ -check-api-secret=true -no-media-test &&
+
+ echo "Test HTTPS service." &&
+ bash scripts/tools/secret.sh --output test/.env &&
+ ./test/srs-stack.test -test.v -endpoint https://localhost:2443 \
+ -srs-log=true -wait-ready=true -init-password=false -init-self-signed-cert=false \
+ -check-api-secret=true -no-media-test &&
+
+ echo "Run media test with retry" &&
+ bash scripts/tools/secret.sh --output test/.env &&
+ ./scripts/tools/failed-retry.sh 1 ./test/srs-stack.test -test.v -endpoint http://localhost:2022 \
+ -srs-log=true -wait-ready=true -init-password=false -init-self-signed-cert=false \
+ -check-api-secret=true -test.run TestApi_Publish*
+ ret=$?; echo "Test result: $ret"
+
+ echo "Stop service"
+ sudo systemctl stop srs-stack
+ kill $pid_journalctl 2>/dev/null
+ echo "Log of journalctl.log" && cat journalctl.log
+
+ exit $ret
+
+ test-final:
+ name: test-final
+ runs-on: ubuntu-20.04
+ needs:
+ - run-test
+ - test-zh-image
+ - test-en-image
+ - test-zh-installer
+ - test-en-installer
+ steps:
+ - uses: geekyeggo/delete-artifact@v2
+ with:
+ name: platform-cache
+ - run: echo OK
From bb7f63bcf944155e5add4854f10d1eec7780dff4 Mon Sep 17 00:00:00 2001
From: winlin
Date: Tue, 10 Oct 2023 19:57:10 +0800
Subject: [PATCH 7/7] Add pullrequest action without secrets.
---
.github/workflows/pullrequest.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml
index cd639654..aaba9b49 100644
--- a/.github/workflows/pullrequest.yml
+++ b/.github/workflows/pullrequest.yml
@@ -1,4 +1,4 @@
-name: Test Dev Environment
+name: Test Dev for PullRequest
on: [pull_request]