@eva/plugin-renderer-video 2.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/EVA.plugin.renderer.video.js +474 -0
- package/dist/EVA.plugin.renderer.video.min.js +1 -0
- package/dist/plugin-renderer-video.cjs.js +495 -0
- package/dist/plugin-renderer-video.cjs.prod.js +1 -0
- package/dist/plugin-renderer-video.d.ts +118 -0
- package/dist/plugin-renderer-video.esm.js +490 -0
- package/index.js +7 -0
- package/package.json +27 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
import { Component, decorators, resource, OBSERVER_TYPE } from '@eva/eva.js';
|
|
2
|
+
import { type } from '@eva/inspector-decorator';
|
|
3
|
+
import { Renderer, RendererSystem } from '@eva/plugin-renderer';
|
|
4
|
+
import { Texture, Sprite } from 'pixi.js';
|
|
5
|
+
|
|
6
|
+
/******************************************************************************
|
|
7
|
+
Copyright (c) Microsoft Corporation.
|
|
8
|
+
|
|
9
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
10
|
+
purpose with or without fee is hereby granted.
|
|
11
|
+
|
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
13
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
14
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
15
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
16
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
17
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
18
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
19
|
+
***************************************************************************** */
|
|
20
|
+
|
|
21
|
+
function __decorate(decorators, target, key, desc) {
|
|
22
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
23
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
24
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
25
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function __metadata(metadataKey, metadataValue) {
|
|
29
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
33
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
34
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
35
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
36
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
37
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
38
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
43
|
+
var e = new Error(message);
|
|
44
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Phaser 风格的 Video 渲染组件(Eva.js MVP 实现)。
|
|
49
|
+
*
|
|
50
|
+
* 行为:
|
|
51
|
+
* 1. ADD 时 VideoSystem 会创建一个 detached 的 `<video>` DOM 元素 + PixiJS Texture + Sprite,
|
|
52
|
+
* Sprite 加入到 GameObject 的 PixiJS Container;
|
|
53
|
+
* 2. video 的源 (`<video>.src`) 由 `src` 决定:
|
|
54
|
+
* - 若注册过同名 VIDEO/IMAGE 资源(且 instance.src 可用),取注册的 url;
|
|
55
|
+
* - 否则直接当作 URL 使用。
|
|
56
|
+
* 3. 视频进入 `loadeddata` 后即可绘制;
|
|
57
|
+
* 4. CHANGE prop 时按属性增量更新(loop/muted/volume/playbackRate/src);
|
|
58
|
+
* 5. REMOVE/destroy 时停止播放、销毁 video/texture/sprite。
|
|
59
|
+
*
|
|
60
|
+
* 不支持(明确 SKIP):
|
|
61
|
+
* - HLS/DASH/MSE 高级流;
|
|
62
|
+
* - getUserMedia / 透明视频(alpha video / chroma key);
|
|
63
|
+
* - 作为 shader 纹理(Eva.js 无 GLSL 注入);
|
|
64
|
+
* - Phaser saveTexture 把视频转 sprite atlas 的工作流。
|
|
65
|
+
*/
|
|
66
|
+
class Video extends Component {
|
|
67
|
+
constructor() {
|
|
68
|
+
super(...arguments);
|
|
69
|
+
this.src = '';
|
|
70
|
+
this.loop = false;
|
|
71
|
+
this.autoplay = true;
|
|
72
|
+
this.muted = true;
|
|
73
|
+
this.volume = 1;
|
|
74
|
+
this.playbackRate = 1;
|
|
75
|
+
this.anchorX = 0.5;
|
|
76
|
+
this.anchorY = 0.5;
|
|
77
|
+
this.crossOrigin = 'anonymous';
|
|
78
|
+
this.playsInline = true;
|
|
79
|
+
/** Runtime 标记:System 设置,表示视频已经触发过 `ended`。 */
|
|
80
|
+
this.completed = false;
|
|
81
|
+
}
|
|
82
|
+
init(obj) {
|
|
83
|
+
if (obj)
|
|
84
|
+
Object.assign(this, obj);
|
|
85
|
+
}
|
|
86
|
+
/** 立即播放(若 video 已就绪)。 */
|
|
87
|
+
play() {
|
|
88
|
+
if (!this.videoElement)
|
|
89
|
+
return;
|
|
90
|
+
try {
|
|
91
|
+
const ret = this.videoElement.play();
|
|
92
|
+
if (ret && typeof ret.catch === 'function') {
|
|
93
|
+
return ret.catch((e) => {
|
|
94
|
+
// eslint-disable-next-line no-console
|
|
95
|
+
console.warn('[Video] play() rejected:', e);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
// eslint-disable-next-line no-console
|
|
101
|
+
console.warn('[Video] play() threw:', e);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** 暂停。 */
|
|
105
|
+
pause() {
|
|
106
|
+
if (!this.videoElement)
|
|
107
|
+
return;
|
|
108
|
+
try {
|
|
109
|
+
this.videoElement.pause();
|
|
110
|
+
}
|
|
111
|
+
catch (_a) {
|
|
112
|
+
/* ignore */
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/** 跳转到指定时间(秒)。 */
|
|
116
|
+
setCurrentTime(seconds) {
|
|
117
|
+
if (!this.videoElement)
|
|
118
|
+
return;
|
|
119
|
+
try {
|
|
120
|
+
this.videoElement.currentTime = Math.max(0, seconds);
|
|
121
|
+
}
|
|
122
|
+
catch (_a) {
|
|
123
|
+
/* ignore */
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** 把当前帧画到一个独立 canvas 上并返回,用于 snapshot(失败时返回 null)。 */
|
|
127
|
+
snapshot(area) {
|
|
128
|
+
var _a, _b, _c, _d;
|
|
129
|
+
const v = this.videoElement;
|
|
130
|
+
if (!v || v.readyState < 2)
|
|
131
|
+
return null;
|
|
132
|
+
const w = (_a = area === null || area === void 0 ? void 0 : area.width) !== null && _a !== void 0 ? _a : v.videoWidth;
|
|
133
|
+
const h = (_b = area === null || area === void 0 ? void 0 : area.height) !== null && _b !== void 0 ? _b : v.videoHeight;
|
|
134
|
+
if (w <= 0 || h <= 0)
|
|
135
|
+
return null;
|
|
136
|
+
try {
|
|
137
|
+
const canvas = document.createElement('canvas');
|
|
138
|
+
canvas.width = w;
|
|
139
|
+
canvas.height = h;
|
|
140
|
+
const ctx = canvas.getContext('2d');
|
|
141
|
+
if (!ctx)
|
|
142
|
+
return null;
|
|
143
|
+
ctx.drawImage(v, (_c = area === null || area === void 0 ? void 0 : area.x) !== null && _c !== void 0 ? _c : 0, (_d = area === null || area === void 0 ? void 0 : area.y) !== null && _d !== void 0 ? _d : 0, w, h, 0, 0, w, h);
|
|
144
|
+
return canvas;
|
|
145
|
+
}
|
|
146
|
+
catch (_e) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
Video.componentName = 'Video';
|
|
152
|
+
__decorate([
|
|
153
|
+
type('string'),
|
|
154
|
+
__metadata("design:type", String)
|
|
155
|
+
], Video.prototype, "src", void 0);
|
|
156
|
+
__decorate([
|
|
157
|
+
type('boolean'),
|
|
158
|
+
__metadata("design:type", Boolean)
|
|
159
|
+
], Video.prototype, "loop", void 0);
|
|
160
|
+
__decorate([
|
|
161
|
+
type('boolean'),
|
|
162
|
+
__metadata("design:type", Boolean)
|
|
163
|
+
], Video.prototype, "autoplay", void 0);
|
|
164
|
+
__decorate([
|
|
165
|
+
type('boolean'),
|
|
166
|
+
__metadata("design:type", Boolean)
|
|
167
|
+
], Video.prototype, "muted", void 0);
|
|
168
|
+
__decorate([
|
|
169
|
+
type('number'),
|
|
170
|
+
__metadata("design:type", Number)
|
|
171
|
+
], Video.prototype, "volume", void 0);
|
|
172
|
+
__decorate([
|
|
173
|
+
type('number'),
|
|
174
|
+
__metadata("design:type", Number)
|
|
175
|
+
], Video.prototype, "playbackRate", void 0);
|
|
176
|
+
__decorate([
|
|
177
|
+
type('number'),
|
|
178
|
+
__metadata("design:type", Number)
|
|
179
|
+
], Video.prototype, "anchorX", void 0);
|
|
180
|
+
__decorate([
|
|
181
|
+
type('number'),
|
|
182
|
+
__metadata("design:type", Number)
|
|
183
|
+
], Video.prototype, "anchorY", void 0);
|
|
184
|
+
__decorate([
|
|
185
|
+
type('boolean'),
|
|
186
|
+
__metadata("design:type", Boolean)
|
|
187
|
+
], Video.prototype, "playsInline", void 0);
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 把 `src` 解析为可播放 URL。
|
|
191
|
+
* 1. 如果 `src` 是完整 URL(以 http(s):// 或 / 开头,或包含 .mp4/.webm/.ogv 后缀),直接使用。
|
|
192
|
+
* 2. 否则尝试从 resource manager 拿对应资源 base 元数据,优先取 `src.video.url` 或 `src.url`。
|
|
193
|
+
*/
|
|
194
|
+
function resolveSrc(src) {
|
|
195
|
+
if (!src)
|
|
196
|
+
return '';
|
|
197
|
+
if (src.startsWith('http://') || src.startsWith('https://') || src.startsWith('//') || src.startsWith('/')) {
|
|
198
|
+
return src;
|
|
199
|
+
}
|
|
200
|
+
if (/\.(mp4|webm|ogv|m4v|mov)(\?|#|$)/i.test(src)) {
|
|
201
|
+
return src;
|
|
202
|
+
}
|
|
203
|
+
// try resource registry
|
|
204
|
+
try {
|
|
205
|
+
const all = resource.resourcesMap || resource.resources;
|
|
206
|
+
if (all && all[src]) {
|
|
207
|
+
const meta = all[src];
|
|
208
|
+
const sources = meta.src || meta.data || {};
|
|
209
|
+
const candidate = sources.video || sources.audio || sources.image || sources;
|
|
210
|
+
if (candidate === null || candidate === void 0 ? void 0 : candidate.url)
|
|
211
|
+
return candidate.url;
|
|
212
|
+
if (candidate === null || candidate === void 0 ? void 0 : candidate.src)
|
|
213
|
+
return candidate.src;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch (_a) {
|
|
217
|
+
/* ignore */
|
|
218
|
+
}
|
|
219
|
+
return src;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* 透明 1x1 像素的 PNG,用作未就绪时的占位 texture 资源。
|
|
223
|
+
* 不直接用 Texture.EMPTY 是因为 EMPTY 的 source 在 v8 里某些路径上会触发 isValid 校验。
|
|
224
|
+
*/
|
|
225
|
+
const TRANSPARENT_PIXEL_DATA_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
|
|
226
|
+
let VideoSystem = class VideoSystem extends Renderer {
|
|
227
|
+
constructor() {
|
|
228
|
+
super(...arguments);
|
|
229
|
+
this.name = 'Video';
|
|
230
|
+
this.records = {};
|
|
231
|
+
}
|
|
232
|
+
init() {
|
|
233
|
+
this.renderSystem = this.game.getSystem(RendererSystem);
|
|
234
|
+
this.renderSystem.rendererManager.register(this);
|
|
235
|
+
}
|
|
236
|
+
rendererUpdate(gameObject) {
|
|
237
|
+
var _a, _b, _c, _d, _e;
|
|
238
|
+
const record = this.records[gameObject.id];
|
|
239
|
+
if (!record)
|
|
240
|
+
return;
|
|
241
|
+
const c = record.component;
|
|
242
|
+
const v = record.video;
|
|
243
|
+
// 视频可绘制时把帧拷贝到 canvas,再通过 texture.source.update() 推到 GPU。
|
|
244
|
+
if (record.ctx && v.readyState >= 2 && v.videoWidth > 0 && v.videoHeight > 0) {
|
|
245
|
+
// 首次拿到真实尺寸时按 video 自然尺寸调整 canvas 分辨率。
|
|
246
|
+
if (!record.resizedToVideo) {
|
|
247
|
+
record.canvas.width = v.videoWidth;
|
|
248
|
+
record.canvas.height = v.videoHeight;
|
|
249
|
+
record.resizedToVideo = true;
|
|
250
|
+
// 重建 texture 以让 PixiJS 感知新的源尺寸。
|
|
251
|
+
const oldTex = record.texture;
|
|
252
|
+
const newTex = Texture.from(record.canvas);
|
|
253
|
+
record.texture = newTex;
|
|
254
|
+
record.sprite.texture = newTex;
|
|
255
|
+
if (oldTex && oldTex !== newTex) {
|
|
256
|
+
try {
|
|
257
|
+
oldTex.destroy(false);
|
|
258
|
+
}
|
|
259
|
+
catch ( /* ignore */_f) { /* ignore */ }
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
record.ctx.drawImage(v, 0, 0, record.canvas.width, record.canvas.height);
|
|
264
|
+
// PixiJS v8: 通过 texture.source.update() 通知 GPU 上传新帧。
|
|
265
|
+
const src = (_a = record.texture) === null || _a === void 0 ? void 0 : _a.source;
|
|
266
|
+
if (src) {
|
|
267
|
+
if (typeof src.update === 'function')
|
|
268
|
+
src.update();
|
|
269
|
+
else
|
|
270
|
+
src.dirty = true;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch (_g) {
|
|
274
|
+
/* drawImage 在某些跨域场景会抛 SecurityError,忽略让下一帧继续尝试 */
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const w = (_b = c.width) !== null && _b !== void 0 ? _b : (v.videoWidth || 256);
|
|
278
|
+
const h = (_c = c.height) !== null && _c !== void 0 ? _c : (v.videoHeight || 256);
|
|
279
|
+
record.sprite.width = w;
|
|
280
|
+
record.sprite.height = h;
|
|
281
|
+
record.sprite.anchor.set((_d = c.anchorX) !== null && _d !== void 0 ? _d : 0.5, (_e = c.anchorY) !== null && _e !== void 0 ? _e : 0.5);
|
|
282
|
+
}
|
|
283
|
+
componentChanged(changed) {
|
|
284
|
+
var _a, _b, _c, _d, _e;
|
|
285
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
286
|
+
if (changed.componentName !== 'Video')
|
|
287
|
+
return;
|
|
288
|
+
const component = changed.component;
|
|
289
|
+
const gameObjectId = changed.gameObject.id;
|
|
290
|
+
if (changed.type === OBSERVER_TYPE.ADD) {
|
|
291
|
+
const record = this.createRecord(component);
|
|
292
|
+
this.records[gameObjectId] = record;
|
|
293
|
+
const container = this.containerManager.getContainer(gameObjectId);
|
|
294
|
+
if (container)
|
|
295
|
+
container.addChildAt(record.sprite, 0);
|
|
296
|
+
this.attachEndedHandler(record, changed.gameObject);
|
|
297
|
+
this.startLoad(record);
|
|
298
|
+
}
|
|
299
|
+
else if (changed.type === OBSERVER_TYPE.CHANGE) {
|
|
300
|
+
const record = this.records[gameObjectId];
|
|
301
|
+
if (!record)
|
|
302
|
+
return;
|
|
303
|
+
const propKey = (_b = (_a = changed.prop) === null || _a === void 0 ? void 0 : _a.prop) === null || _b === void 0 ? void 0 : _b[0];
|
|
304
|
+
if (propKey === 'src') {
|
|
305
|
+
try {
|
|
306
|
+
record.video.pause();
|
|
307
|
+
}
|
|
308
|
+
catch ( /* ignore */_f) { /* ignore */ }
|
|
309
|
+
const url = resolveSrc(component.src);
|
|
310
|
+
record.video.src = url;
|
|
311
|
+
record.component = component;
|
|
312
|
+
component.videoElement = record.video;
|
|
313
|
+
component.completed = false;
|
|
314
|
+
record.resizedToVideo = false;
|
|
315
|
+
try {
|
|
316
|
+
record.video.load();
|
|
317
|
+
}
|
|
318
|
+
catch ( /* ignore */_g) { /* ignore */ }
|
|
319
|
+
if (component.autoplay !== false)
|
|
320
|
+
this.tryPlay(record.video);
|
|
321
|
+
}
|
|
322
|
+
else if (propKey === 'loop') {
|
|
323
|
+
record.video.loop = !!component.loop;
|
|
324
|
+
}
|
|
325
|
+
else if (propKey === 'muted') {
|
|
326
|
+
record.video.muted = !!component.muted;
|
|
327
|
+
}
|
|
328
|
+
else if (propKey === 'volume') {
|
|
329
|
+
record.video.volume = Math.max(0, Math.min(1, (_c = component.volume) !== null && _c !== void 0 ? _c : 1));
|
|
330
|
+
}
|
|
331
|
+
else if (propKey === 'playbackRate') {
|
|
332
|
+
record.video.playbackRate = (_d = component.playbackRate) !== null && _d !== void 0 ? _d : 1;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
else if (changed.type === OBSERVER_TYPE.REMOVE) {
|
|
336
|
+
const record = this.records[gameObjectId];
|
|
337
|
+
if (!record)
|
|
338
|
+
return;
|
|
339
|
+
this.tearDown(record);
|
|
340
|
+
const container = (_e = this.containerManager) === null || _e === void 0 ? void 0 : _e.getContainer(gameObjectId);
|
|
341
|
+
if (container)
|
|
342
|
+
container.removeChild(record.sprite);
|
|
343
|
+
try {
|
|
344
|
+
record.sprite.destroy({ children: true });
|
|
345
|
+
}
|
|
346
|
+
catch ( /* ignore */_h) { /* ignore */ }
|
|
347
|
+
delete this.records[gameObjectId];
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
createRecord(component) {
|
|
352
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
353
|
+
const video = document.createElement('video');
|
|
354
|
+
video.crossOrigin = (_a = component.crossOrigin) !== null && _a !== void 0 ? _a : 'anonymous';
|
|
355
|
+
video.loop = !!component.loop;
|
|
356
|
+
video.muted = component.muted !== false;
|
|
357
|
+
video.volume = Math.max(0, Math.min(1, (_b = component.volume) !== null && _b !== void 0 ? _b : 1));
|
|
358
|
+
video.playbackRate = (_c = component.playbackRate) !== null && _c !== void 0 ? _c : 1;
|
|
359
|
+
video.playsInline = component.playsInline !== false;
|
|
360
|
+
video['webkit-playsinline'] = '';
|
|
361
|
+
video.preload = 'auto';
|
|
362
|
+
component.videoElement = video;
|
|
363
|
+
// 用一个 canvas 作为帧中转:PixiJS 直接把这个 canvas 当 ImageSource,
|
|
364
|
+
// 完全绕过 PixiJS v8 的 VideoSource(避免 _onPlayStart/_mediaReady 无限递归)。
|
|
365
|
+
const canvas = document.createElement('canvas');
|
|
366
|
+
canvas.width = (_d = component.width) !== null && _d !== void 0 ? _d : 256;
|
|
367
|
+
canvas.height = (_e = component.height) !== null && _e !== void 0 ? _e : 256;
|
|
368
|
+
const ctx = canvas.getContext('2d');
|
|
369
|
+
// 占位 texture:用透明 PNG 兜底,绝不传 EMPTY(v8 在某些 isValid 路径上不友好)。
|
|
370
|
+
const placeholderTex = Texture.from(TRANSPARENT_PIXEL_DATA_URL);
|
|
371
|
+
const sprite = new Sprite(placeholderTex);
|
|
372
|
+
sprite.anchor.set((_f = component.anchorX) !== null && _f !== void 0 ? _f : 0.5, (_g = component.anchorY) !== null && _g !== void 0 ? _g : 0.5);
|
|
373
|
+
return {
|
|
374
|
+
video,
|
|
375
|
+
canvas,
|
|
376
|
+
ctx,
|
|
377
|
+
texture: placeholderTex,
|
|
378
|
+
sprite,
|
|
379
|
+
component,
|
|
380
|
+
endedHandler: null,
|
|
381
|
+
resizedToVideo: false,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
attachEndedHandler(record, _go) {
|
|
385
|
+
const handler = () => {
|
|
386
|
+
record.component.completed = true;
|
|
387
|
+
try {
|
|
388
|
+
record.video.dispatchEvent(new Event('eva-video-complete'));
|
|
389
|
+
}
|
|
390
|
+
catch (_a) {
|
|
391
|
+
/* ignore */
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
record.video.addEventListener('ended', handler);
|
|
395
|
+
record.endedHandler = handler;
|
|
396
|
+
}
|
|
397
|
+
startLoad(record) {
|
|
398
|
+
const url = resolveSrc(record.component.src);
|
|
399
|
+
record.video.src = url;
|
|
400
|
+
try {
|
|
401
|
+
record.video.load();
|
|
402
|
+
}
|
|
403
|
+
catch ( /* ignore */_a) { /* ignore */ }
|
|
404
|
+
if (record.component.autoplay !== false) {
|
|
405
|
+
// 元数据就绪后再 play,这样 videoWidth/videoHeight 已经稳定。
|
|
406
|
+
const onCanPlay = () => {
|
|
407
|
+
record.video.removeEventListener('canplay', onCanPlay);
|
|
408
|
+
record.video.removeEventListener('loadeddata', onCanPlay);
|
|
409
|
+
this.tryPlay(record.video);
|
|
410
|
+
};
|
|
411
|
+
if (record.video.readyState >= 2) {
|
|
412
|
+
this.tryPlay(record.video);
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
record.video.addEventListener('canplay', onCanPlay);
|
|
416
|
+
record.video.addEventListener('loadeddata', onCanPlay);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
tryPlay(video) {
|
|
421
|
+
try {
|
|
422
|
+
const p = video.play();
|
|
423
|
+
if (p && typeof p.catch === 'function') {
|
|
424
|
+
p.catch((err) => {
|
|
425
|
+
// eslint-disable-next-line no-console
|
|
426
|
+
console.warn('[Video] autoplay rejected:', (err === null || err === void 0 ? void 0 : err.message) || err);
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
catch (e) {
|
|
431
|
+
// eslint-disable-next-line no-console
|
|
432
|
+
console.warn('[Video] autoplay threw:', e);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
tearDown(record) {
|
|
436
|
+
try {
|
|
437
|
+
record.video.pause();
|
|
438
|
+
}
|
|
439
|
+
catch ( /* ignore */_a) { /* ignore */ }
|
|
440
|
+
if (record.endedHandler) {
|
|
441
|
+
try {
|
|
442
|
+
record.video.removeEventListener('ended', record.endedHandler);
|
|
443
|
+
}
|
|
444
|
+
catch ( /* ignore */_b) { /* ignore */ }
|
|
445
|
+
}
|
|
446
|
+
try {
|
|
447
|
+
record.video.removeAttribute('src');
|
|
448
|
+
record.video.load();
|
|
449
|
+
}
|
|
450
|
+
catch ( /* ignore */_c) { /* ignore */ }
|
|
451
|
+
if (record.texture) {
|
|
452
|
+
try {
|
|
453
|
+
record.texture.destroy(false);
|
|
454
|
+
}
|
|
455
|
+
catch ( /* ignore */_d) { /* ignore */ }
|
|
456
|
+
record.texture = null;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
destroy() {
|
|
460
|
+
var _a;
|
|
461
|
+
for (const key in this.records) {
|
|
462
|
+
const id = parseInt(key);
|
|
463
|
+
const record = this.records[id];
|
|
464
|
+
this.tearDown(record);
|
|
465
|
+
const container = (_a = this.containerManager) === null || _a === void 0 ? void 0 : _a.getContainer(id);
|
|
466
|
+
if (container)
|
|
467
|
+
container.removeChild(record.sprite);
|
|
468
|
+
try {
|
|
469
|
+
record.sprite.destroy({ children: true });
|
|
470
|
+
}
|
|
471
|
+
catch ( /* ignore */_b) { /* ignore */ }
|
|
472
|
+
delete this.records[id];
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
VideoSystem.systemName = 'Video';
|
|
477
|
+
VideoSystem = __decorate([
|
|
478
|
+
decorators.componentObserver({
|
|
479
|
+
Video: [
|
|
480
|
+
{ prop: ['src'], deep: false },
|
|
481
|
+
{ prop: ['loop'], deep: false },
|
|
482
|
+
{ prop: ['muted'], deep: false },
|
|
483
|
+
{ prop: ['volume'], deep: false },
|
|
484
|
+
{ prop: ['playbackRate'], deep: false },
|
|
485
|
+
],
|
|
486
|
+
})
|
|
487
|
+
], VideoSystem);
|
|
488
|
+
var VideoSystem$1 = VideoSystem;
|
|
489
|
+
|
|
490
|
+
export { Video, VideoSystem$1 as VideoSystem };
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@eva/plugin-renderer-video",
|
|
3
|
+
"version": "2.1.0-beta.1",
|
|
4
|
+
"description": "@eva/plugin-renderer-video",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"module": "dist/plugin-renderer-video.esm.js",
|
|
7
|
+
"bundle": "EVA.plugin.renderer.video",
|
|
8
|
+
"unpkg": "dist/EVA.plugin.renderer.video.min.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"index.js",
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"types": "dist/plugin-renderer-video.d.ts",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"eva.js",
|
|
16
|
+
"eva-plugin-renderer-video"
|
|
17
|
+
],
|
|
18
|
+
"author": "dingyike.dyk <dingyike.dyk@alibaba-inc.com>",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"homepage": "https://eva.js.org",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@eva/inspector-decorator": "^2.0.0-beta.0",
|
|
23
|
+
"@eva/eva.js": "2.1.0-beta.1",
|
|
24
|
+
"@eva/plugin-renderer": "2.1.0-beta.1",
|
|
25
|
+
"pixi.js": "^8.6.3"
|
|
26
|
+
}
|
|
27
|
+
}
|