feat: remove unused style file and integrate TensorFlow body segmentation for danmuku mask functionality

This commit is contained in:
Harvey Zack committed 2024-10-07 23:49:33 +08:00
1 parent d9bdc9bdeb
commit ef56482911
5 files changed
+78463 -40

No files matched your search

@@ -20,5 +20,12 @@
"html5",
"video",
"player"
]
}
],
"dependencies": {
"@mediapipe/selfie_segmentation": "^0.1.1675465747",
"@tensorflow-models/body-segmentation": "^1.0.2",
"@tensorflow/tfjs-backend-webgl": "^4.21.0",
"@tensorflow/tfjs-converter": "^4.21.0",
"@tensorflow/tfjs-core": "^4.21.0"
}
}
@@ -1,26 +1,125 @@
import style from 'bundle-text:./style.less';
import * as tf from '@tensorflow/tfjs-core';
import '@tensorflow/tfjs-backend-webgl';
import * as bodySegmentation from '@tensorflow-models/body-segmentation';
export default function artplayerPluginDanmukuMask(option) {
export default function artplayerPluginDanmukuMask(option = {}) {
return (art) => {
const {
template: { $player, $video, $danmuku },
} = art;
let segmenter = null;
let canvas = null;
let ctx = null;
let animationFrameId = null;
async function initSegmenter() {
await tf.setBackend('webgl');
const model = bodySegmentation.SupportedModels.MediaPipeSelfieSegmentation;
const segmenterConfig = {
runtime: 'mediapipe', // or 'tfjs'
solutionPath: 'https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation',
modelType: 'general',
};
segmenter = await bodySegmentation.createSegmenter(model, segmenterConfig);
}
function createCanvas() {
canvas = document.createElement('canvas');
ctx = canvas.getContext('2d');
canvas.style.position = 'absolute';
canvas.style.top = '0';
canvas.style.left = '0';
canvas.style.width = '100%';
canvas.style.height = '100%';
canvas.style.pointerEvents = 'none';
$player.appendChild(canvas);
}
async function segmentBody() {
if ($video.paused || $video.ended) {
animationFrameId = requestAnimationFrame(segmentBody);
return;
}
try {
canvas.width = $video.videoWidth;
canvas.height = $video.videoHeight;
const segmentation = await segmenter.segmentPeople($video);
if (!segmentation) {
animationFrameId = requestAnimationFrame(segmentBody);
return;
}
const foregroundColor = { r: 0, g: 0, b: 0, a: 0 };
const backgroundColor = { r: 0, g: 0, b: 0, a: 255 };
const drawContour = false;
const foregroundThreshold = 0.6;
const mask = await bodySegmentation.toBinaryMask(
segmentation,
foregroundColor,
backgroundColor,
drawContour,
foregroundThreshold,
);
const opacity = 1;
const maskBlurAmount = 3;
await bodySegmentation.drawMask(canvas, $video, mask, opacity, maskBlurAmount);
$danmuku.style.webkitMaskImage = `url(${canvas.toDataURL()})`;
$danmuku.style.maskImage = `url(${canvas.toDataURL()})`;
} catch (error) {
console.error('Error in segmentBody:', error);
}
animationFrameId = requestAnimationFrame(segmentBody);
}
async function startSegmentation() {
if (!segmenter) {
await initSegmenter();
}
if (!canvas) {
createCanvas();
}
// Wait for the video element to be ready
await new Promise((resolve) => {
if ($video.readyState >= 2) {
resolve();
} else {
$video.onloadeddata = resolve;
}
});
segmentBody();
}
function stopSegmentation() {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
}
art.on('ready', startSegmentation);
art.on('destroy', stopSegmentation);
return {
name: 'artplayerPluginDanmukuMask',
start: startSegmentation,
stop: stopSegmentation,
};
};
}
if (typeof document !== 'undefined') {
const id = 'artplayer-plugin-danmuku-mask';
const $style = document.getElementById(id);
if ($style) {
$style.textContent = style;
} else {
const $style = document.createElement('style');
$style.id = id;
$style.textContent = style;
document.head.appendChild($style);
}
}
if (typeof window !== 'undefined') {
window['artplayerPluginDanmukuMask'] = artplayerPluginDanmukuMask;
}
@@ -1,3 +0,0 @@
.artplayer-plugin-danmuku-mask {
//
}