Skip to content

Commit d6b3f8c

Browse files
authored
Merge pull request #31 from devsapp/add-panoramic-page-recorder
add panoramic-page-recorder template
2 parents 17d8dec + bfc63f6 commit d6b3f8c

19 files changed

Lines changed: 4706 additions & 0 deletions

File tree

panoramic-page-recorder/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
tmp
2+
.s
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
async function preInit(inputObj) {
2+
console.log(`\n _______ _______ __ __ _______ _______ _______
3+
| || || |_| || || || |
4+
| ___|| ___|| || _ || ___|| ___|
5+
| |___ | |___ | || |_| || |___ | | __
6+
| ___|| ___|| || ___|| ___|| || |
7+
| | | | | ||_|| || | | |___ | |_| |
8+
|___| |___| |_| |_||___| |_______||_______|
9+
`)
10+
}
11+
12+
async function postInit(inputObj) {
13+
console.log(`\n Welcome to the HttpPanoramicPageRecording ...`)
14+
}
15+
16+
module.exports = {
17+
postInit,
18+
preInit
19+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
Edition: 3.0.0
2+
Type: Project
3+
Name: HttpPanoramicPageRecording-v3
4+
Version: 0.0.1
5+
Provider:
6+
- 阿里云
7+
Description: 快速部署一个Http触发的全景web页面录制-高级版应用到阿里云函数计算
8+
HomePage: https://github.com/devsapp/start-ffmpeg/tree/dipper/panoramic-page-recorder
9+
Organization: 阿里云函数计算(FC)
10+
Effective: Public
11+
Tags:
12+
- 全景录制
13+
Category: 音视频处理
14+
Service:
15+
函数计算:
16+
Authorities:
17+
- AliyunFCFullAccess
18+
日志服务:
19+
Authorities:
20+
- AliyunFCServerlessDevsRolePolicy
21+
Parameters:
22+
type: object
23+
additionalProperties: false # 不允许增加其他属性
24+
required: # 必填项
25+
- region
26+
- functionNamePrefix
27+
- roleArn
28+
- ossBucket
29+
- timeZone
30+
properties:
31+
region:
32+
title: 部署地域
33+
type: string
34+
default: cn-hangzhou
35+
description: 创建应用所在的地区
36+
enum:
37+
- cn-beijing
38+
- cn-hangzhou
39+
- cn-shanghai
40+
- cn-shenzhen
41+
functionNamePrefix:
42+
title: 函数名称前缀
43+
type: string
44+
default: panoramic-page-${default-suffix}
45+
pattern: '^[a-zA-Z_][a-zA-Z0-9-_]{0,31}$'
46+
description: 函数名称前缀,只能包含字母、数字、下划线和中划线。不能以数字、中划线开头。长度在 1-32 之间
47+
roleArn:
48+
title: 函数服务角色
49+
type: string
50+
default: ''
51+
pattern: '^acs:ram::[0-9]*:role/.*$'
52+
description: 函数计算服务配置依赖的角色
53+
required: true
54+
x-role:
55+
name: aliyunfcdefaultrole
56+
service: fc
57+
authorities:
58+
- AliyunFCDefaultRolePolicy
59+
ossBucket:
60+
title: OSS 存储桶名
61+
type: string
62+
default: ''
63+
description: OSS 存储桶名
64+
x-bucket:
65+
dependency:
66+
- region
67+
timeZone:
68+
title: 时区
69+
type: string
70+
default: Asia/Shanghai
71+
description: 创建的应用函数执行时候所在实例的时区, 详情参考 https://docs.oracle.com/middleware/12211/wcs/tag-ref/MISC/TimeZones.html

panoramic-page-recorder/readme.md

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
2+
> 注:当前项目为 Serverless Devs 应用,由于应用中会存在需要初始化才可运行的变量(例如应用部署地区、函数名等等),所以**不推荐**直接 Clone 本仓库到本地进行部署或直接复制 s.yaml 使用,**强烈推荐**通过 `s init ${模版名称}` 的方法或应用中心进行初始化,详情可参考[部署 & 体验](#部署--体验)
3+
4+
# HttpPanoramicPageRecording-v3 帮助文档
5+
6+
<description>
7+
8+
快速部署一个Http触发的全景web页面录制-高级版应用到阿里云函数计算
9+
10+
</description>
11+
12+
13+
## 资源准备
14+
15+
使用该项目,您需要有开通以下服务并拥有对应权限:
16+
17+
<service>
18+
19+
20+
21+
| 服务/业务 | 权限 | 相关文档 |
22+
| --- | --- | --- |
23+
| 函数计算 | AliyunFCFullAccess,AliyunContainerRegistryFullAccess | [帮助文档](https://help.aliyun.com/product/2508973.html) [计费文档](https://help.aliyun.com/document_detail/2512928.html) |
24+
25+
</service>
26+
27+
<remark>
28+
29+
30+
31+
</remark>
32+
33+
<disclaimers>
34+
35+
36+
37+
</disclaimers>
38+
39+
## 部署 & 体验
40+
41+
<appcenter>
42+
43+
- :fire: 通过 [云原生应用开发平台 CAP](https://cap.console.aliyun.com/template-detail?template=HttpPanoramicPageRecording-v3)[![Deploy with Severless Devs](https://img.alicdn.com/imgextra/i1/O1CN01w5RFbX1v45s8TIXPz_!!6000000006118-55-tps-95-28.svg)](https://cap.console.aliyun.com/template-detail?template=HttpPanoramicPageRecording-v3) 该应用。
44+
45+
</appcenter>
46+
<deploy>
47+
48+
49+
</deploy>
50+
51+
## 案例介绍
52+
53+
<appdetail id="flushContent">
54+
55+
当前市场上复杂的录制需求,如多人参与、界面动态调整、实时音视频捕获、直播编码及流媒体播放,连同辅助功能如电子白板、消息弹幕和视频展示等内容的同步录制,都对传统的录制解决方案构成了挑战。传统方法通过增加额外的录制流并利用信号同步来重现场景,并常伴随着插入SEI时间戳等技术手段来同步各类媒体元素。这不仅对设备的硬件和网络连接提出了更高要求,而且增加了开发难度和成本,由于涉及到后期复杂的内容合成,也不能即时产出录制文件,从而影响业务的快速迭代和发展。
56+
57+
本方案采取 Chrome 进行页面渲染录制+ffmepg 转码实现全景录制:
58+
59+
Chrome 渲染到虚拟 X-server,并通过 FFmpeg 抓取系统桌⾯,通过启动 xvfb 启动虚拟 X-server,Chrome 进⾏全屏显示渲染到到虚拟 X-server 上,并通过 FFmpeg 抓取系统屏幕以及采集系统声⾳并进⾏编码写⽂件。这种⽅式的适配性⾮常好, 不仅可以录制 Chrome,理论上也可以录制其他的应⽤。缺点是占⽤的内存和 CPU 较多。
60+
61+
62+
</appdetail>
63+
64+
## 架构图
65+
66+
<framework id="flushContent">
67+
68+
![](https://img.alicdn.com/imgextra/i2/O1CN01v6nXa41JYMw8pHIqe_!!6000000001040-0-tps-1604-854.jpg)
69+
70+
</framework>
71+
72+
## 部署流程
73+
74+
<usedetail id="flushContent">
75+
76+
部署完毕以后, 您可以获取recorder 函数的 http url, 比如为 `https://xxx.cn-hangzhou.fcapp.run`
77+
78+
### 同步调用
79+
80+
调用直到录制完成,返回调用结果
81+
82+
``` bash
83+
$ curl -H "Content-Type: application/json" -X POST -d '{"record_time":"60","video_url":"https://tv.cctv.com/live/cctv1/","output_file":"record/test.mp4", "width":"1280", "height":"720", "scale": 0.75, "frame_rate":25,"bit_rate":"2000k"}' https://xxx.cn-hangzhou.fcapp.run
84+
```
85+
86+
### 异步调用
87+
88+
发送录制请求,理解返回,录制任务完成函数自动退出,中间过程可以查询函数运行状态
89+
90+
``` bash
91+
$ curl -v -H "Content-Type: application/json" -H "X-Fc-Invocation-Type: Async" -X POST -d '{"record_time":"60","video_url":"https://tv.cctv.com/live/cctv1/","output_file":"record/test.mp4", "width":"1280", "height":"720", "scale": 0.75, "frame_rate":25,"bit_rate":"2000k"}' https://xxx.cn-hangzhou.fcapp.run
92+
```
93+
94+
调用成功后, 会在对应的 bucket 下, 产生 record/test.mp4 大约 60 秒 1280x720 的全景录制视频。
95+
96+
### 调用参数说明
97+
98+
**1.record_time:** 录制时长
99+
100+
**2.video_url:** 录制视频的 url
101+
102+
**3.width:** 录制视频的宽度
103+
104+
**4.height:** 录制视频的高度
105+
106+
**5.scale:** 浏览器缩放比例
107+
108+
**6.output_file:** 最后录制视频保存的 OSS 目录
109+
110+
**7.frame_rate:** 录制视频的帧率(可不传递,默认帧率为30fps)
111+
112+
**8.bit_rate:** 录制视频的码率(可不传递,默认码率为1500k)
113+
114+
**9.output_stream:** 推流地址(可选参数,eg: rtmp://demo.aliyundoc.com/app/stream?xxxx)
115+
116+
其中 scale 是对浏览器进行 75% 的缩放,使视频能录制更多的网页内容
117+
118+
**注意:** 如果您录制的视频存在一些卡顿或者快进, 可能是因为您录制的视频分辨率大并且复杂, 消耗的 CPU 很大, 您可以通过调大函数的规格, 提高 CPU 的能力。
119+
120+
比如上面的示例参数得到下图:
121+
122+
![](https://img.alicdn.com/imgextra/i3/O1CN01fbUSSP1umgrF0cfFr_!!6000000006080-2-tps-3048-1706.png)
123+
124+
125+
### 停止录制任务
126+
127+
您可以获取controller 函数的 http 触发器的 url, 比如为 `https://yyy.cn-hangzhou.fcapp.run`
128+
129+
130+
``` bash
131+
# 其中 xxxx 表示发起异步调用 recorder 任务时候的 task id
132+
$ curl -H "Content-Type: application/json" -X POST -d '{"invocationId":"xxxx"}' https://yyy.cn-hangzhou.fcapp.run
133+
```
134+
135+
136+
</usedetail>
137+
138+
## 二次开发指南
139+
140+
<development id="flushContent">
141+
142+
### 如何本地调试
143+
144+
直接本地运行, 命令执行完毕后, 会在当前目录生成一个 test.mp4 的视频
145+
146+
```bash
147+
# 直接本地执行docker命令, 会在当前目录生成一个 test.mp4 的视频
148+
$ docker run --rm --entrypoint="" -v $(pwd):/var/output my-panoramic-page-recording /recorder/record.sh 60 https://tv.cctv.com/live/cctv1 1280x720x24 1280,720 1280x720 1 25 2000k
149+
```
150+
151+
调试
152+
153+
```bash
154+
# 如果有镜像有代码更新, 重新build 镜像
155+
$ docker build -t my-panoramic-page-recording -f ./recorder/Dockerfile ./recorder
156+
# 测试全屏录制核心脚本 record.sh, 执行完毕后, 会在当前目录有一个 test.mp4 的视频
157+
$ docker run --rm --entrypoint="" -v $(pwd):/var/output my-panoramic-page-recording /recorder/record.sh 60 https://tv.cctv.com/live/cctv1 1280x720x24 1280,720 1280x720 1 25 2000k
158+
```
159+
160+
> 其中 record.sh 的参数意义:
161+
>
162+
> 1. 录制时长
163+
> 2. 视频 url
164+
> 3. $widthx$heightx24
165+
> 4. $width,$height
166+
> 5. $widthx$height
167+
> 6. chrome 浏览器缩放比例
168+
> 7. 帧率
169+
> 8. 码率
170+
> 9. 推流地址
171+
172+
173+
**`server.js`**
174+
175+
custom container http server 逻辑
176+
177+
**`record.sh`**
178+
179+
核心录屏逻辑, 启动 xvfb, 在虚拟 X-server 中使用 `record.js` 中的 puppeteer 启动浏览器, 最后 FFmpeg 完成 X-server 屏幕的视频和音频抓取工作, 生成全屏录制后的视频
180+
181+
182+
调试完毕, 您可以将镜像 tag 成您的 ACR 镜像, docker push 上去, 然后使用这个新的镜像更新您的 recorder 函数中 customContainerConfig 中的 image 即可。
183+
184+
185+
### 更多
186+
如果您想将生成的视频直接预热的 CDN, 以阿里云 CDN 为例, 只需要在 server.js 上传完 OSS bucket 后的逻辑中增加如下代码:
187+
188+
[PushObjectCache](https://next.api.aliyun.com/api/Cdn/2018-05-10/PushObjectCache?lang=NODEJS&sdkStyle=old&params={})
189+
190+
> Tips 前提需要配置好 CDN
191+
192+
</development>
193+
194+
195+
196+
197+
198+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/headless-ffmpeg
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict';
2+
3+
const apiClient = require('@alicloud/openapi-client');
4+
const FC20230330 = require('@alicloud/fc20230330');
5+
6+
exports.handler = (event, context, callback) => {
7+
const eventObj = JSON.parse(event);
8+
console.log(`receive event: ${JSON.stringify(eventObj)}`);
9+
10+
let body = '';
11+
// get http request body
12+
if ("body" in eventObj) {
13+
body = eventObj.body;
14+
if (eventObj.isBase64Encoded) {
15+
body = Buffer.from(body, 'base64').toString('utf-8');
16+
}
17+
}
18+
console.log(`receive http body: ${body}`);
19+
const bodyObj = JSON.parse(body);
20+
console.log(`receive http bodyObj: ${JSON.stringify(bodyObj)}`);
21+
const invocationId = bodyObj['invocationId'];
22+
console.log(`receive invocationId: ${invocationId}`);
23+
24+
const config = new apiClient.Config({
25+
accessKeyId: context.credentials.accessKeyId,
26+
accessKeySecret: context.credentials.accessKeySecret,
27+
securityToken: context.credentials.securityToken,
28+
endpoint: `${context.accountId}.${context.region }.fc.aliyuncs.com`
29+
});
30+
31+
let client = new FC20230330.default(config);
32+
let stopAsyncTaskRequest = new FC20230330.StopAsyncTaskRequest({});
33+
const recorder = process.env.RECORDER_FUNCTION_NAME
34+
client.stopAsyncTask(recorder,invocationId, stopAsyncTaskRequest).then(function (res) {
35+
console.log(res);
36+
callback(null, {
37+
'statusCode': 200,
38+
'body': res
39+
});
40+
}).catch(function (err) {
41+
console.error(err);
42+
callback(null, {
43+
'statusCode': 500,
44+
'body': err.message
45+
});
46+
});
47+
}

0 commit comments

Comments
 (0)