Skip to content

Commit

Permalink
增加content选项用于直接设置页面内容并允许url缺省,修复transitionend未触发问题
Browse files Browse the repository at this point in the history
  • Loading branch information
Vinlic committed Nov 7, 2023
1 parent c649055 commit 662cc0f
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 28 deletions.
6 changes: 6 additions & 0 deletions README.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ wvc.config({
const video = wvc.createSingleVideo({
// URL of the page to be rendered
url: "http://localhost:8080/test.html",
// or can directly set the page content:
// content: "<h1>Hello WebVideoCreator</h1>",
// Video width
width: 1280,
// Video height
Expand Down Expand Up @@ -163,6 +165,8 @@ const video = wvc.createMultiVideo({
chunks: [
{
url: "http://localhost:8080/scene-1.html",
// or can directly set the page content:
// content: "<h1>Hello WebVideoCreator</h1>",
duration: 10000,
// Insert a circular crop transition between the first and second scenes
transition: TRANSITION.CIRCLE_CROP
Expand Down Expand Up @@ -206,6 +210,8 @@ wvc.config({
// Create chunk video 1
const chunk1 = wvc.createChunkVideo({
url: "http://localhost:8080/scene-1.html",
// or can directly set the page content:
// content: "<h1>Hello WebVideoCreator</h1>",
width: 1280,
height: 720,
fps: 30,
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ wvc.config({
const video = wvc.createSingleVideo({
// 需要渲染的页面地址
url: "http://localhost:8080/test.html",
// 或者可以直接设置页面内容
// content: "<h1>Hello WebVideoCreator</h1>",
// 视频宽度
width: 1280,
// 视频高度
Expand Down Expand Up @@ -163,6 +165,8 @@ const video = wvc.createMultiVideo({
chunks: [
{
url: "http://localhost:8080/scene-1.html",
// 或者可以直接设置页面内容
// content: "<h1>Hello WebVideoCreator</h1>",
duration: 10000,
// 在第一和第二幕之间插入转场
transition: TRANSITION.CIRCLE_CROP
Expand Down Expand Up @@ -206,6 +210,8 @@ wvc.config({
// 创建分块视频1
const chunk1 = wvc.createChunkVideo({
url: "http://localhost:8080/scene-1.html",
// 或者可以直接设置页面内容
// content: "<h1>Hello WebVideoCreator</h1>",
width: 1280,
height: 720,
fps: 30,
Expand Down
20 changes: 15 additions & 5 deletions api/ChunkVideo.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export default class ChunkVideo extends VideoChunk {

/** @type {string} - 页面URL */
url;
/** @type {string} - 页面内容 */
content;
/** @type {Font[]} - 注册的字体 */
fonts = [];
/** @type {boolean} - 是否自动启动渲染 */
Expand All @@ -34,7 +36,8 @@ export default class ChunkVideo extends VideoChunk {
* 构造函数
*
* @param {Object} options - 分块视频选项
* @param {string} options.url - 页面URL
* @param {string} [options.url] - 页面URL
* @param {string} [options.content] - 页面内容
* @param {string} options.outputPath - 输出路径
* @param {number} options.width - 视频宽度
* @param {number} options.height - 视频高度
Expand Down Expand Up @@ -63,12 +66,15 @@ export default class ChunkVideo extends VideoChunk {
constructor(options = {}) {
super(options);
assert(_.isObject(options), "options must be Object");
const { url, autostartRender, consoleLog, videoPreprocessLog, pagePrepareFn } = options;
assert(util.isURL(url), `url ${url} is not valid URL`);
const { url, content, autostartRender, consoleLog, videoPreprocessLog, pagePrepareFn } = options;
assert(_.isUndefined(url) || util.isURL(url), `url ${url} is not valid URL`);
assert(_.isUndefined(content) || _.isString(content), "page content must be string");
assert(!_.isUndefined(url) || !_.isUndefined(content), "page url or content must be provide");
assert(_.isUndefined(autostartRender) || _.isBoolean(autostartRender), "autostartRender must be boolean");
assert(_.isUndefined(consoleLog) || _.isBoolean(consoleLog), "consoleLog must be boolean");
assert(_.isUndefined(pagePrepareFn) || _.isFunction(pagePrepareFn), "pagePrepareFn must be Function");
this.url = url;
this.content = content;
this.autostartRender = _.defaultTo(autostartRender, true);
this.consoleLog = _.defaultTo(consoleLog, false);
this.videoPreprocessLog = _.defaultTo(videoPreprocessLog, false);
Expand Down Expand Up @@ -118,7 +124,7 @@ export default class ChunkVideo extends VideoChunk {
async #synthesize() {
const page = await this.#acquirePage();
try {
const { url, width, height, fps, duration } = this;
const { url, content, width, height, fps, duration } = this;
// 监听页面实例发生的某些内部错误
page.on("error", err => this._emitError("Page error:\n" + err.stack));
// 监听页面是否崩溃,当内存不足或过载时可能会崩溃
Expand All @@ -145,7 +151,11 @@ export default class ChunkVideo extends VideoChunk {
height
});
// 跳转到您希望渲染的页面,您可以考虑创建一个本地的Web服务器提供页面以提升加载速度和安全性
await page.goto(url);
if(url)
await page.goto(url);
// 或者设置页面内容
else
await page.setContent(content);
// 存在预处理函数时先执行预处理
this.pagePrepareFn && await this.pagePrepareFn(page);
// 注册字体
Expand Down
20 changes: 15 additions & 5 deletions api/SingleVideo.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export default class SingleVideo extends Synthesizer {

/** @type {string} - 页面URL */
url;
/** @type {string} - 页面内容 */
content;
/** @type {number} - 视频时长 */
duration;
/** @type {Font[]} - 注册的字体列表 */
Expand All @@ -36,7 +38,8 @@ export default class SingleVideo extends Synthesizer {
* 构造函数
*
* @param {Object} options - 单幕视频选项
* @param {string} options.url - 页面URL
* @param {string} [options.url] - 页面URL
* @param {string} [options.content] - 页面内容
* @param {string} options.outputPath - 输出路径
* @param {number} options.width - 视频宽度
* @param {number} options.height - 视频高度
Expand All @@ -63,13 +66,16 @@ export default class SingleVideo extends Synthesizer {
*/
constructor(options = {}) {
super(options);
const { url, duration, autostartRender, consoleLog, videoPreprocessLog, pagePrepareFn } = options;
assert(util.isURL(url), `url ${url} is not valid URL`);
const { url, content, duration, autostartRender, consoleLog, videoPreprocessLog, pagePrepareFn } = options;
assert(_.isUndefined(url) || util.isURL(url), `url ${url} is not valid URL`);
assert(_.isUndefined(content) || _.isString(content), "page content must be string");
assert(!_.isUndefined(url) || !_.isUndefined(content), "page url or content must be provide");
assert(_.isFinite(duration), "duration must be number");
assert(_.isUndefined(autostartRender) || _.isBoolean(autostartRender), "autostartRender must be boolean");
assert(_.isUndefined(consoleLog) || _.isBoolean(consoleLog), "consoleLog must be boolean");
assert(_.isUndefined(pagePrepareFn) || _.isFunction(pagePrepareFn), "pagePrepareFn must be Function");
this.url = url;
this.content = content;
this.duration = duration;
this.autostartRender = _.defaultTo(autostartRender, true);
this.consoleLog = _.defaultTo(consoleLog, false);
Expand Down Expand Up @@ -120,7 +126,7 @@ export default class SingleVideo extends Synthesizer {
async #synthesize() {
const page = await this.#acquirePage();
try {
const { url, width, height, fps, duration } = this;
const { url, content, width, height, fps, duration } = this;
// 监听页面实例发生的某些内部错误
page.on("error", err => this._emitError("Page error:\n" + err.stack));
// 监听页面是否崩溃,当内存不足或过载时可能会崩溃
Expand All @@ -141,7 +147,11 @@ export default class SingleVideo extends Synthesizer {
height
});
// 跳转到您希望渲染的页面,您可以考虑创建一个本地的Web服务器提供页面以提升加载速度和安全性
await page.goto(url);
if(url)
await page.goto(url);
// 或者设置页面内容
else
await page.setContent(content);
// 存在预处理函数时先执行预处理
this.pagePrepareFn && await this.pagePrepareFn(page);
// 注册字体
Expand Down
6 changes: 4 additions & 2 deletions api/WebVideoCreator.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ export default class WebVideoCreator {
* 创建单幕视频
*
* @param {Object} options - 单幕视频选项
* @param {string} options.url - 页面URL
* @param {string} [options.url] - 页面URL
* @param {string} [options.content] - 页面内容
* @param {string} options.outputPath - 输出路径
* @param {number} options.width - 视频宽度
* @param {number} options.height - 视频高度
Expand Down Expand Up @@ -120,7 +121,8 @@ export default class WebVideoCreator {
* 创建分块视频
*
* @param {Object} options - 分块视频选项
* @param {string} options.url - 页面URL
* @param {string} [options.url] - 页面URL
* @param {string} [options.content] - 页面内容
* @param {string} options.outputPath - 输出路径
* @param {number} options.width - 视频宽度
* @param {number} options.height - 视频高度
Expand Down
65 changes: 50 additions & 15 deletions core/Page.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,32 @@ export default class Page extends EventEmitter {
]);
}

/**
* 设置页面内容
*
* @param {string} content 页面内容
*/
async setContent(content) {
assert(this.isReady(), "Page state must be ready");
assert(_.isString(content), "page content must be string");
await this.target.goto("about:blank");
// 清除资源
this.#clearResources();
// 开始CDP会话
await this.#startCDPSession();
// 监听CSS动画
await this.#listenCSSAnimations();
await this.target.setContent(content);
await Promise.all([
// 注入公共样式
this.#injectStyle(COMMON_STYLE_CONTENT),
// 注入MP4Box库
this.#injectLibrary(MP4BOX_LIBRARY_SCRIPT_CONTENT + ";window.____MP4Box = window.MP4Box;window.MP4Box = undefined"),
// 注入Lottie动画库
this.#injectLibrary(LOTTIE_LIBRARY_SCRIPT_CONTENT + ";window.____lottie = window.lottie;window.lottie = undefined")
]);
}

/**
* 注册字体
*
Expand Down Expand Up @@ -467,21 +493,31 @@ export default class Page extends EventEmitter {
async #seekCSSAnimations(currentTime) {
if (this.cssAnimations.length === 0)
return;
const cssAnimationSeekPromises = [];
const dispatchPromises = [];
this.cssAnimations = this.cssAnimations.filter(animation => {
animation.startTime = _.defaultTo(animation.startTime, currentTime);
const animationCurrentTime = currentTime - animation.startTime;
const animationCurrentTime = Math.floor(currentTime - animation.startTime);
if (animationCurrentTime < 0)
return true;
cssAnimationSeekPromises.push(this.#cdpSession.send("Animation.seekAnimations", {
dispatchPromises.push(this.#cdpSession.send("Animation.seekAnimations", {
animations: [animation.id],
currentTime: animationCurrentTime
}));
if (animationCurrentTime >= (animation.duration * (animation.iterations || Infinity)) + animation.delay)
if (animationCurrentTime >= (animation.duration * (animation.iterations || Infinity)) + animation.delay) {
dispatchPromises.push((async () => {
const node = await this.#cdpSession.send("DOM.resolveNode", {
backendNodeId: animation.backendNodeId
});
node && node.object && await this.#cdpSession.send("Runtime.callFunctionOn", {
objectId: node.object.objectId,
functionDeclaration: 'function () { this.dispatchEvent(new TransitionEvent("transitionend")) }'
});
})());
return false;
}
return true;
});
await Promise.all(cssAnimationSeekPromises);
await Promise.all(dispatchPromises);
}

/**
Expand Down Expand Up @@ -580,16 +616,15 @@ export default class Page extends EventEmitter {
// 启用动画通知域
await this.#cdpSession.send("Animation.enable");
// 监听动画开始事件将动画属性添加到调度列表
this.#cdpSession.on("Animation.animationStarted", animation => this.cssAnimations.push({
id: animation.animation.id,
startTime: null,
delay: animation.animation.source.delay,
duration: animation.animation.source.duration,
iterations: animation.animation.source.iterations
}));
// 监听动画取消时间将动画属性从调度列表移除
this.#cdpSession.on("Animation.animationCanceled", animation => {
this.cssAnimations = this.cssAnimations.filter(_animation => _animation.id != animation.id);
this.#cdpSession.on("Animation.animationStarted", animation => {
this.cssAnimations.push({
id: animation.animation.id,
startTime: null,
backendNodeId: animation.animation.source.backendNodeId,
delay: animation.animation.source.delay,
duration: animation.animation.source.duration,
iterations: animation.animation.source.iterations
});
});
}

Expand Down
7 changes: 6 additions & 1 deletion docs/api-reference-high-level.md
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,12 @@
<tr>
<td>url</td>
<td>string</td>
<td>待渲染的页面URL</td>
<td>待渲染的页面URL,与content二选一</td>
</tr>
<tr>
<td>content</td>
<td>string</td>
<td>待渲染的页面内容,与url二选一</td>
</tr>
<tr>
<td>outputPath</td>
Expand Down
4 changes: 4 additions & 0 deletions docs/api-reference-low-level.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,10 @@ const { ... } = core;

本地 URL 或远端 HTTPS URL,受制于浏览器的[安全上下文限制](https://w3c.github.io/webappsec-secure-contexts/),只能访问 localhost / 127.0.0.1 或者使用 HTTPS 协议且证书有效的域

### goto(content: string): Promise

设置待渲染页面内容。

### Page.registerFont(options: Object)

注册字体
Expand Down

0 comments on commit 662cc0f

Please sign in to comment.