hyapp-h5/common/effect-player.js
2026-06-11 23:09:39 +08:00

1028 lines
36 KiB
JavaScript

(function () {
var DEFAULT_SVGA_SCRIPT =
'https://cdn.jsdelivr.net/npm/svgaplayerweb@2.3.2/build/svga.min.js';
var DEFAULT_PAG_BASE = 'https://cdn.jsdelivr.net/npm/libpag@4.5.68/lib/';
var scriptCache = {};
var pagInitCache = {};
var videoLayoutCache = {};
var svgaVideoItemCache = {};
var MIN_SPLIT_MATCH_FRAMES = 3;
var MIN_USEFUL_FRAME_BRIGHTNESS = 0.035;
var MIN_SPLIT_SATURATION_GAP = 0.08;
var MAX_SPLIT_MASK_SATURATION = 0.28;
var MAX_SPLIT_MASK_GRAY_ERROR = 0.06;
function extend(target, source) {
var output = target || {};
source = source || {};
Object.keys(source).forEach(function (key) {
if (source[key] !== undefined) output[key] = source[key];
});
return output;
}
function resolveElement(target) {
if (!target) return null;
if (typeof target === 'string') return document.querySelector(target);
return target;
}
function loadScriptOnce(url) {
if (scriptCache[url]) return scriptCache[url];
scriptCache[url] = new Promise(function (resolve, reject) {
var script = document.createElement('script');
script.src = url;
script.async = true;
script.onload = resolve;
script.onerror = function () {
reject(new Error('script_load_failed'));
};
document.head.appendChild(script);
});
return scriptCache[url];
}
function loadSVGAItemOnce(sourceURL, canvas, options) {
if (svgaVideoItemCache[sourceURL]) return svgaVideoItemCache[sourceURL];
svgaVideoItemCache[sourceURL] = loadScriptOnce(options.svgaScript)
.then(function () {
if (!window.SVGA) throw new Error('svga_unavailable');
return new Promise(function (resolve, reject) {
var parser = new window.SVGA.Parser(canvas);
parser.load(sourceURL, resolve, reject);
});
})
.catch(function (error) {
delete svgaVideoItemCache[sourceURL];
throw error;
});
return svgaVideoItemCache[sourceURL];
}
function waitForWindowLoad() {
if (document.readyState === 'complete') return Promise.resolve();
return new Promise(function (resolve) {
window.addEventListener('load', resolve, { once: true });
});
}
function extensionFromURL(url) {
var clean = String(url || '')
.split('?')[0]
.split('#')[0];
var match = clean.match(/\.([a-z0-9]+)$/i);
return match ? match[1].toLowerCase() : '';
}
function inferType(url, explicitType) {
var type = String(explicitType || '').toLowerCase();
if (type) return type;
var ext = extensionFromURL(url);
if (ext === 'mp4' || ext === 'webm' || ext === 'mov') return 'video';
if (ext === 'svga') return 'svga';
if (ext === 'pag') return 'pag';
return '';
}
function sameOrigin(url) {
try {
return (
new URL(url, window.location.href).origin ===
window.location.origin
);
} catch (_) {
return true;
}
}
function proxyBinaryURL(url, type, options) {
if (options.proxy === false) return url;
if (type !== 'svga' && type !== 'pag') return url;
if (sameOrigin(url)) return url;
if (typeof options.proxyURL === 'function') {
return options.proxyURL(url, type) || url;
}
if (typeof options.proxyURL === 'string' && options.proxyURL) {
var base = options.proxyURL;
var separator = base.indexOf('?') >= 0 ? '&' : '?';
return base + separator + 'url=' + encodeURIComponent(url);
}
if (window.HyAppAPI && typeof window.HyAppAPI.buildURL === 'function') {
return window.HyAppAPI.buildURL('/api/v1/media/effects/proxy', {
url: url,
});
}
return url;
}
function mediaSize(container, options) {
return {
width:
Number(options.width) ||
(container && container.clientWidth) ||
300,
height:
Number(options.height) ||
(container && container.clientHeight) ||
300,
};
}
function styleNode(node, options) {
node.style.display = 'block';
node.style.width = '100%';
node.style.height = '100%';
if (node.tagName === 'VIDEO' || node.tagName === 'CANVAS') {
node.style.objectFit = options.fit || 'contain';
}
}
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
function median(values) {
if (!values.length) return 0;
var sorted = values.slice().sort(function (a, b) {
return a - b;
});
return sorted[Math.floor(sorted.length / 2)];
}
function fullFrameLayout(video, kind, hasNativeAlpha) {
var width = Math.max(1, video.videoWidth || 1);
var height = Math.max(1, video.videoHeight || 1);
var rect = { x: 0, y: 0, width: 1, height: 1 };
return {
kind: kind || 'normal',
hasAlphaMask: false,
hasNativeAlpha: !!hasNativeAlpha,
contentAspectRatio: width / height,
colorRect: rect,
alphaRect: rect,
};
}
function waitForMediaEvent(target, eventName, timeoutMS) {
return new Promise(function (resolve, reject) {
var timer = 0;
function cleanup() {
window.clearTimeout(timer);
target.removeEventListener(eventName, handleEvent);
target.removeEventListener('error', handleError);
}
function handleEvent() {
cleanup();
resolve();
}
function handleError() {
cleanup();
reject(new Error('video_' + eventName + '_failed'));
}
target.addEventListener(eventName, handleEvent, { once: true });
target.addEventListener('error', handleError, { once: true });
timer = window.setTimeout(function () {
cleanup();
reject(new Error('video_' + eventName + '_timeout'));
}, timeoutMS || 4000);
});
}
function loadVideoMetadata(video) {
if (video.readyState >= 1 && video.videoWidth && video.videoHeight) {
return Promise.resolve();
}
return waitForMediaEvent(video, 'loadedmetadata', 8000);
}
function seekVideo(video, time) {
var duration = Number(video.duration || 0);
var targetTime = duration
? clamp(time, 0, Math.max(0, duration - 0.05))
: Math.max(0, time);
if (Math.abs(video.currentTime - targetTime) < 0.04) {
return Promise.resolve();
}
video.currentTime = targetTime;
return waitForMediaEvent(video, 'seeked', 2500).catch(function () {});
}
function videoSampleTimes(duration, mode) {
var base =
mode === 'packed'
? [0, 0.5, 1, 2, 3]
: [0.3, 0.6, 0.9, 1.2, 1.5, 1.8, 2.1, 2.4, 2.7, 3];
var filtered = base.filter(function (time) {
return !duration || !Number.isFinite(duration) || time <= duration;
});
return filtered.length ? filtered : [duration ? duration / 2 : 0];
}
function uniqueTimes(times) {
var seen = {};
return times
.slice()
.sort(function (a, b) {
return a - b;
})
.filter(function (time) {
var key = time.toFixed(2);
if (seen[key]) return false;
seen[key] = true;
return true;
});
}
function readVideoFrame(video, maxWidth) {
var sourceWidth = Math.max(1, video.videoWidth || 1);
var sourceHeight = Math.max(1, video.videoHeight || 1);
var sampleWidth = Math.min(sourceWidth, Number(maxWidth) || 480);
var sampleHeight = Math.max(
1,
Math.round((sampleWidth * sourceHeight) / sourceWidth)
);
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = sampleWidth;
canvas.height = sampleHeight;
context.clearRect(0, 0, sampleWidth, sampleHeight);
context.drawImage(video, 0, 0, sampleWidth, sampleHeight);
return {
width: sampleWidth,
height: sampleHeight,
data: context.getImageData(0, 0, sampleWidth, sampleHeight).data,
};
}
function pixelAt(frame, x, y) {
var safeX = clamp(Math.round(x), 0, frame.width - 1);
var safeY = clamp(Math.round(y), 0, frame.height - 1);
var index = (safeY * frame.width + safeX) * 4;
return {
r: frame.data[index],
g: frame.data[index + 1],
b: frame.data[index + 2],
a: frame.data[index + 3],
};
}
function hasNativeAlpha(frames) {
for (var index = 0; index < frames.length; index += 1) {
var data = frames[index].data;
var transparent = 0;
var sampled = 0;
var step = Math.max(4, Math.floor(data.length / (4 * 6000)) * 4);
for (var offset = 3; offset < data.length; offset += step) {
sampled += 1;
if (data[offset] < 245) transparent += 1;
}
if (sampled && transparent / sampled > 0.003) return true;
}
return false;
}
function isLikelyAlphaMaskPixel(pixel) {
var maxChannel = Math.max(pixel.r, pixel.g, pixel.b);
if (maxChannel < 22) return false;
var minChannel = Math.min(pixel.r, pixel.g, pixel.b);
var saturation = maxChannel
? (maxChannel - minChannel) / maxChannel
: 0;
var grayError =
(Math.abs(pixel.r - pixel.g) +
Math.abs(pixel.g - pixel.b) +
Math.abs(pixel.b - pixel.r)) /
(3 * 255);
return saturation < 0.16 && grayError < 0.055;
}
function detectPackedAlphaTopRightInFrame(frame) {
var width = frame.width;
var height = frame.height;
if (width < 12 || height < 12) return null;
var searchStart = clamp(Math.floor(width * 0.54), 0, width - 1);
var sampleHeight = clamp(Math.floor(height * 0.58), 1, height);
var stepY = Math.max(1, Math.floor(sampleHeight / 220));
var rowSamples = Math.max(1, Math.ceil(sampleHeight / stepY));
var threshold = Math.max(8, Math.floor(rowSamples * 0.32));
var grayColumns = [];
for (var x = searchStart; x < width; x += 1) {
var grayCount = 0;
for (var y = 0; y < sampleHeight; y += stepY) {
if (isLikelyAlphaMaskPixel(pixelAt(frame, x, y))) {
grayCount += 1;
}
}
grayColumns[x] = grayCount >= threshold;
}
var minRangeWidth = Math.max(24, Math.floor(width * 0.12));
var bestStart = -1;
var bestEnd = -1;
var cursor = searchStart;
while (cursor < width) {
while (cursor < width && !grayColumns[cursor]) cursor += 1;
if (cursor >= width) break;
var start = cursor;
while (cursor < width && grayColumns[cursor]) cursor += 1;
var end = cursor - 1;
if (end - start + 1 >= minRangeWidth && end > bestEnd) {
bestStart = start;
bestEnd = end;
}
}
if (bestStart < 0 || bestEnd < 0) return null;
return {
frameWidth: width,
frameHeight: height,
alphaStartX: bestStart,
alphaEndX: bestEnd,
};
}
function detectPackedAlphaTopRight(frames) {
var candidates = frames
.map(detectPackedAlphaTopRightInFrame)
.filter(Boolean);
if (!candidates.length) return null;
var frameWidth = median(
candidates.map(function (item) {
return item.frameWidth;
})
);
var frameHeight = median(
candidates.map(function (item) {
return item.frameHeight;
})
);
var alphaStartX = median(
candidates.map(function (item) {
return item.alphaStartX;
})
);
var alphaEndX = median(
candidates.map(function (item) {
return item.alphaEndX;
})
);
var alphaWidth = alphaEndX - alphaStartX + 1;
var alphaStartRatio = alphaStartX / frameWidth;
var alphaWidthRatio = alphaWidth / frameWidth;
var colorToAlphaRatio = alphaStartX / alphaWidth;
if (
alphaStartRatio < 0.58 ||
alphaStartRatio > 0.78 ||
alphaWidthRatio < 0.18 ||
alphaWidthRatio > 0.42 ||
colorToAlphaRatio < 1.45 ||
colorToAlphaRatio > 2.65 ||
alphaEndX < frameWidth * 0.82
) {
return null;
}
var colorWidth = clamp(alphaStartX, 1, frameWidth - 1);
var safeAlphaEndX = clamp(alphaEndX, colorWidth, frameWidth - 1);
var safeAlphaWidth = Math.max(1, safeAlphaEndX - colorWidth + 1);
var alphaHeight = clamp(
Math.round((frameHeight * safeAlphaWidth) / colorWidth),
1,
frameHeight
);
return {
kind: 'packedAlphaTopRight',
hasAlphaMask: true,
hasNativeAlpha: false,
contentAspectRatio: colorWidth / frameHeight,
colorRect: {
x: 0,
y: 0,
width: colorWidth / frameWidth,
height: 1,
},
alphaRect: {
x: colorWidth / frameWidth,
y: 0,
width: safeAlphaWidth / frameWidth,
height: alphaHeight / frameHeight,
},
};
}
function statsForRect(frame, startX, startY, endX, endY) {
var width = Math.max(1, endX - startX);
var height = Math.max(1, endY - startY);
var stepX = Math.max(1, Math.floor(width / 80));
var stepY = Math.max(1, Math.floor(height / 120));
var saturationSum = 0;
var grayErrorSum = 0;
var brightnessSum = 0;
var count = 0;
for (var y = startY; y < endY; y += stepY) {
for (var x = startX; x < endX; x += stepX) {
var pixel = pixelAt(frame, x, y);
var red = pixel.r / 255;
var green = pixel.g / 255;
var blue = pixel.b / 255;
var maxChannel = Math.max(red, green, blue);
var minChannel = Math.min(red, green, blue);
saturationSum += maxChannel
? (maxChannel - minChannel) / maxChannel
: 0;
grayErrorSum +=
(Math.abs(red - green) +
Math.abs(green - blue) +
Math.abs(blue - red)) /
3;
brightnessSum += red * 0.299 + green * 0.587 + blue * 0.114;
count += 1;
}
}
if (!count) {
return { saturation: 0, grayError: 0, brightness: 0 };
}
return {
saturation: saturationSum / count,
grayError: grayErrorSum / count,
brightness: brightnessSum / count,
};
}
function statsByHorizontalHalves(frame) {
var half = Math.floor(frame.width / 2);
return [
statsForRect(frame, 0, 0, half, frame.height),
statsForRect(frame, half, 0, frame.width, frame.height),
];
}
function statsByVerticalHalves(frame) {
var half = Math.floor(frame.height / 2);
return [
statsForRect(frame, 0, 0, frame.width, half),
statsForRect(frame, 0, half, frame.width, frame.height),
];
}
function addSplitScore(score, first, second) {
if (
Math.max(first.brightness, second.brightness) <
MIN_USEFUL_FRAME_BRIGHTNESS
) {
return;
}
var alphaStats = first.saturation < second.saturation ? first : second;
var saturationGap = Math.abs(second.saturation - first.saturation);
if (
saturationGap < MIN_SPLIT_SATURATION_GAP ||
alphaStats.saturation > MAX_SPLIT_MASK_SATURATION ||
alphaStats.grayError > MAX_SPLIT_MASK_GRAY_ERROR
) {
return;
}
score.direction += second.saturation - first.saturation;
score.saturationGap += saturationGap;
score.maskSaturation += alphaStats.saturation;
score.maskGrayError += alphaStats.grayError;
score.frames += 1;
}
function splitCandidateFromScore(orientation, score) {
if (score.frames < MIN_SPLIT_MATCH_FRAMES) return null;
var averageSaturationGap = score.saturationGap / score.frames;
var averageMaskSaturation = score.maskSaturation / score.frames;
var averageMaskGrayError = score.maskGrayError / score.frames;
if (
averageSaturationGap < MIN_SPLIT_SATURATION_GAP ||
averageMaskSaturation > MAX_SPLIT_MASK_SATURATION ||
averageMaskGrayError > MAX_SPLIT_MASK_GRAY_ERROR
) {
return null;
}
return {
candidate: {
orientation: orientation,
alphaFirst: score.direction > 0,
},
score: averageSaturationGap,
};
}
function splitSourceLayout(candidate, video) {
var left = { x: 0, y: 0, width: 0.5, height: 1 };
var right = { x: 0.5, y: 0, width: 0.5, height: 1 };
var top = { x: 0, y: 0, width: 1, height: 0.5 };
var bottom = { x: 0, y: 0.5, width: 1, height: 0.5 };
var isHorizontal = candidate.orientation === 'horizontal';
var first = isHorizontal ? left : top;
var second = isHorizontal ? right : bottom;
var width = Math.max(1, video.videoWidth || 1);
var height = Math.max(1, video.videoHeight || 1);
return {
kind: isHorizontal ? 'splitAlphaHorizontal' : 'splitAlphaVertical',
hasAlphaMask: true,
hasNativeAlpha: false,
contentAspectRatio: isHorizontal
? width / 2 / height
: width / (height / 2),
colorRect: candidate.alphaFirst ? second : first,
alphaRect: candidate.alphaFirst ? first : second,
};
}
function detectSplitAlpha(frames, video) {
var horizontalScore = {
direction: 0,
saturationGap: 0,
maskSaturation: 0,
maskGrayError: 0,
frames: 0,
};
var verticalScore = {
direction: 0,
saturationGap: 0,
maskSaturation: 0,
maskGrayError: 0,
frames: 0,
};
frames.forEach(function (frame) {
var horizontal = statsByHorizontalHalves(frame);
var vertical = statsByVerticalHalves(frame);
addSplitScore(horizontalScore, horizontal[0], horizontal[1]);
addSplitScore(verticalScore, vertical[0], vertical[1]);
});
var candidates = [
splitCandidateFromScore('horizontal', horizontalScore),
splitCandidateFromScore('vertical', verticalScore),
].filter(Boolean);
if (!candidates.length) return null;
candidates.sort(function (a, b) {
return b.score - a.score;
});
return splitSourceLayout(candidates[0].candidate, video);
}
function sampleVideoFrames(video, mode, maxWidth) {
var times = uniqueTimes(videoSampleTimes(video.duration || 0, mode));
var frames = [];
var chain = Promise.resolve();
times.forEach(function (time) {
chain = chain
.then(function () {
return seekVideo(video, time);
})
.then(function () {
frames.push(readVideoFrame(video, maxWidth));
});
});
return chain.then(function () {
return frames;
});
}
function detectVideoLayout(video, url, options) {
var cacheKey = String(url || '');
if (videoLayoutCache[cacheKey]) {
return Promise.resolve(videoLayoutCache[cacheKey]);
}
var maxWidth = Number(options.videoDetectionSampleWidth) || 480;
return loadVideoMetadata(video)
.then(function () {
return sampleVideoFrames(video, 'packed', maxWidth);
})
.then(function (packedFrames) {
if (hasNativeAlpha(packedFrames)) {
return fullFrameLayout(video, 'nativeAlpha', true);
}
return (
detectPackedAlphaTopRight(packedFrames) ||
sampleVideoFrames(video, 'split', maxWidth).then(
function (splitFrames) {
return (
detectSplitAlpha(splitFrames, video) ||
fullFrameLayout(video, 'normal', false)
);
}
)
);
})
.then(function (layout) {
videoLayoutCache[cacheKey] = layout;
return layout;
});
}
function rectToPixels(rect, width, height) {
var x = clamp(Math.round(rect.x * width), 0, width - 1);
var y = clamp(Math.round(rect.y * height), 0, height - 1);
var right = clamp(
Math.round((rect.x + rect.width) * width),
x + 1,
width
);
var bottom = clamp(
Math.round((rect.y + rect.height) * height),
y + 1,
height
);
return {
x: x,
y: y,
width: Math.max(1, right - x),
height: Math.max(1, bottom - y),
};
}
function layoutCanvasSize(video, layout, options) {
var videoWidth = Math.max(1, video.videoWidth || 1);
var videoHeight = Math.max(1, video.videoHeight || 1);
var rect = layout.hasAlphaMask
? rectToPixels(layout.colorRect, videoWidth, videoHeight)
: { width: videoWidth, height: videoHeight };
var maxDimension = Number(options.maxVideoCanvasDimension) || 1200;
var scale = Math.min(
1,
maxDimension / Math.max(rect.width, rect.height)
);
return {
width: Math.max(1, Math.round(rect.width * scale)),
height: Math.max(1, Math.round(rect.height * scale)),
};
}
function renderMaskedVideoFrame(video, canvas, layout, scratch) {
var videoWidth = Math.max(1, video.videoWidth || 1);
var videoHeight = Math.max(1, video.videoHeight || 1);
var outWidth = canvas.width;
var outHeight = canvas.height;
var context = scratch.outputContext;
context.clearRect(0, 0, outWidth, outHeight);
if (!layout.hasAlphaMask) {
context.drawImage(video, 0, 0, outWidth, outHeight);
return;
}
var colorRect = rectToPixels(layout.colorRect, videoWidth, videoHeight);
var alphaRect = rectToPixels(layout.alphaRect, videoWidth, videoHeight);
scratch.colorCanvas.width = outWidth;
scratch.colorCanvas.height = outHeight;
scratch.alphaCanvas.width = outWidth;
scratch.alphaCanvas.height = outHeight;
scratch.colorContext.clearRect(0, 0, outWidth, outHeight);
scratch.alphaContext.clearRect(0, 0, outWidth, outHeight);
scratch.colorContext.drawImage(
video,
colorRect.x,
colorRect.y,
colorRect.width,
colorRect.height,
0,
0,
outWidth,
outHeight
);
scratch.alphaContext.drawImage(
video,
alphaRect.x,
alphaRect.y,
alphaRect.width,
alphaRect.height,
0,
0,
outWidth,
outHeight
);
var colorData = scratch.colorContext.getImageData(
0,
0,
outWidth,
outHeight
);
var alphaData = scratch.alphaContext.getImageData(
0,
0,
outWidth,
outHeight
);
for (var offset = 0; offset < colorData.data.length; offset += 4) {
colorData.data[offset + 3] =
alphaData.data[offset] * 0.299 +
alphaData.data[offset + 1] * 0.587 +
alphaData.data[offset + 2] * 0.114;
}
context.putImageData(colorData, 0, 0);
}
function initPAG(libBase) {
var base = libBase || DEFAULT_PAG_BASE;
if (pagInitCache[base]) return pagInitCache[base];
pagInitCache[base] = loadScriptOnce(base + 'libpag.umd.js').then(
function () {
if (!window.libpag) throw new Error('pag_unavailable');
return window.libpag.PAGInit({
locateFile: function (file) {
return base + file;
},
});
}
);
return pagInitCache[base];
}
function EffectPlayer(target, options) {
this.container = resolveElement(target);
this.options = extend(
{
activeClass: 'is-active',
fit: 'contain',
loop: true,
muted: true,
waitForWindowLoad: true,
svgaScript: DEFAULT_SVGA_SCRIPT,
pagBase: DEFAULT_PAG_BASE,
},
options || {}
);
this.url = '';
this.active = false;
this.cleanup = null;
this.current = null;
this.sequence = 0;
this.destroyed = false;
}
EffectPlayer.prototype._teardown = function () {
if (this.cleanup) {
try {
this.cleanup();
} catch (_) {}
}
this.cleanup = null;
this.active = false;
this.current = null;
if (this.container) {
this.container.classList.remove(this.options.activeClass);
this.container.innerHTML = '';
}
};
EffectPlayer.prototype._mount = function (node) {
if (!this.container) throw new Error('container_unavailable');
this.container.innerHTML = '';
if (node) this.container.appendChild(node);
this.container.classList.add(this.options.activeClass);
this.active = true;
};
EffectPlayer.prototype._isCurrent = function (url) {
return !this.destroyed && this.url === url;
};
EffectPlayer.prototype._createVideoElement = function (
url,
options,
useCORS
) {
var video = document.createElement('video');
if (useCORS) video.crossOrigin = 'anonymous';
video.src = url;
video.autoplay = true;
video.loop = options.loop !== false;
video.muted = options.muted !== false;
video.playsInline = true;
video.setAttribute('webkit-playsinline', 'true');
video.preload = options.preload || 'auto';
return video;
};
EffectPlayer.prototype._playPlainVideo = function (url, options, video) {
if (!this._isCurrent(url)) return Promise.resolve();
video = video || this._createVideoElement(url, options, false);
styleNode(video, options);
this._mount(video);
this.cleanup = function () {
video.pause();
video.removeAttribute('src');
video.load();
};
return video.play().catch(function () {});
};
EffectPlayer.prototype._playCanvasVideo = function (
url,
options,
video,
layout
) {
var self = this;
if (!this._isCurrent(url)) return Promise.resolve();
var size = layoutCanvasSize(video, layout, options);
var canvas = document.createElement('canvas');
canvas.width = size.width;
canvas.height = size.height;
styleNode(canvas, options);
var scratch = {
outputContext: canvas.getContext('2d', {
alpha: true,
willReadFrequently: !!layout.hasAlphaMask,
}),
colorCanvas: document.createElement('canvas'),
alphaCanvas: document.createElement('canvas'),
};
scratch.colorContext = scratch.colorCanvas.getContext('2d', {
willReadFrequently: true,
});
scratch.alphaContext = scratch.alphaCanvas.getContext('2d', {
willReadFrequently: true,
});
var raf = 0;
function draw() {
if (!self._isCurrent(url)) return;
if (video.readyState >= 2) {
renderMaskedVideoFrame(video, canvas, layout, scratch);
}
raf = window.requestAnimationFrame(draw);
}
this._mount(canvas);
this.cleanup = function () {
window.cancelAnimationFrame(raf);
video.pause();
video.removeAttribute('src');
video.load();
};
draw();
return seekVideo(video, 0).then(function () {
return video.play().catch(function () {});
});
};
EffectPlayer.prototype._playVideo = function (url, options) {
var self = this;
if (!this._isCurrent(url)) return Promise.resolve();
if (options.detectTransparentVideo === false) {
return this._playPlainVideo(url, options);
}
var video = this._createVideoElement(url, options, true);
return detectVideoLayout(video, url, options)
.then(function (layout) {
if (!self._isCurrent(url)) return undefined;
if (!layout.hasAlphaMask && !layout.hasNativeAlpha) {
return self._playPlainVideo(url, options, video);
}
return self._playCanvasVideo(url, options, video, layout);
})
.catch(function () {
if (!self._isCurrent(url)) return undefined;
return self._playPlainVideo(url, options);
});
};
EffectPlayer.prototype._playSVGA = function (url, options) {
var self = this;
var sourceURL = proxyBinaryURL(url, 'svga', options);
var size = mediaSize(self.container, options);
var canvas = document.createElement('canvas');
canvas.width = size.width;
canvas.height = size.height;
styleNode(canvas, options);
self._mount(canvas);
return loadSVGAItemOnce(sourceURL, canvas, options).then(
function (videoItem) {
if (!self._isCurrent(url)) return undefined;
if (!window.SVGA) throw new Error('svga_unavailable');
var player = new window.SVGA.Player(canvas);
self.cleanup = function () {
if (player.stopAnimation) player.stopAnimation();
if (player.clear) player.clear();
};
player.setVideoItem(videoItem);
player.startAnimation();
return undefined;
}
);
};
EffectPlayer.prototype._playPAG = function (url, options) {
var self = this;
var sourceURL = proxyBinaryURL(url, 'pag', options);
return initPAG(options.pagBase)
.then(function (PAG) {
if (!self._isCurrent(url)) return undefined;
return fetch(sourceURL).then(function (response) {
if (!response.ok) throw new Error('pag_fetch_failed');
return response.arrayBuffer().then(function (buffer) {
return { PAG: PAG, buffer: buffer };
});
});
})
.then(function (payload) {
if (!payload || !self._isCurrent(url)) return undefined;
var size = mediaSize(self.container, options);
var canvas = document.createElement('canvas');
canvas.width = size.width;
canvas.height = size.height;
styleNode(canvas, options);
self._mount(canvas);
return payload.PAG.PAGFile.load(payload.buffer).then(
function (pagFile) {
return payload.PAG.PAGView.init(pagFile, canvas, {
useScale: false,
});
}
);
})
.then(function (pagView) {
if (!self._isCurrent(url)) return undefined;
if (!pagView) throw new Error('pag_view_unavailable');
self.cleanup = function () {
if (pagView.destroy) pagView.destroy();
};
return pagView.play();
});
};
EffectPlayer.prototype._playNow = function (url, options) {
var type = inferType(url, options.type);
if (type === 'video' || type === 'mp4') {
return this._playVideo(url, options);
}
if (type === 'svga') return this._playSVGA(url, options);
if (type === 'pag') return this._playPAG(url, options);
return Promise.reject(new Error('unsupported_effect_type'));
};
EffectPlayer.prototype.play = function (url, options) {
var self = this;
var nextURL = String(url || '').trim();
var nextOptions = extend(extend({}, this.options), options || {});
if (!nextURL) {
this.stop();
return Promise.resolve();
}
if (this.url === nextURL && this.current) return this.current;
this.stop();
this.url = nextURL;
var sequence = this.sequence;
var run = function () {
if (self.destroyed || sequence !== self.sequence) {
return Promise.resolve();
}
self.current = Promise.resolve()
.then(function () {
return self._playNow(nextURL, nextOptions);
})
.catch(function (error) {
if (sequence === self.sequence) self.stop();
throw error;
});
return self.current;
};
if (nextOptions.waitForWindowLoad === false) return run();
this.current = waitForWindowLoad().then(run);
return this.current;
};
EffectPlayer.prototype.stop = function () {
this.sequence += 1;
this.url = '';
this._teardown();
};
EffectPlayer.prototype.destroy = function () {
this.destroyed = true;
this.stop();
this.container = null;
};
window.HyAppEffectPlayer = {
create: function (target, options) {
return new EffectPlayer(target, options);
},
preload: function (url, options) {
var normalized = extend(
{
svgaScript: DEFAULT_SVGA_SCRIPT,
proxy: true,
},
options || {}
);
var type = inferType(url, normalized.type);
if (type !== 'svga') return Promise.resolve();
var canvas = document.createElement('canvas');
var sourceURL = proxyBinaryURL(url, 'svga', normalized);
return loadSVGAItemOnce(sourceURL, canvas, normalized);
},
play: function (target, url, options) {
var player = new EffectPlayer(target, options);
return player.play(url).then(function () {
return player;
});
},
inferType: inferType,
loadScriptOnce: loadScriptOnce,
};
})();