From a4be7b0ee685b65c800eaacd2297f27742d055c9 Mon Sep 17 00:00:00 2001 From: Harvey Zack Date: Sun, 8 Sep 2024 18:28:39 +0800 Subject: [PATCH] feat: Update artplayer-proxy-canvas to support canvas proxy --- .../artplayer-proxy-canvas/index.js | 98 ++++++++++----- .../artplayer-proxy-canvas/index.js.map | 2 +- packages/artplayer-proxy-canvas/src/index.js | 113 +++++++++++++----- 3 files changed, 154 insertions(+), 59 deletions(-) diff --git a/docs/uncompiled/artplayer-proxy-canvas/index.js b/docs/uncompiled/artplayer-proxy-canvas/index.js index 1f96e5018..f4f02cd94 100644 --- a/docs/uncompiled/artplayer-proxy-canvas/index.js +++ b/docs/uncompiled/artplayer-proxy-canvas/index.js @@ -160,6 +160,7 @@ function artplayerProxyCanvas(callback) { ...methods, ...prototypes ]; + // 存储 canvas 的原始属性 const originalCanvasProperties = {}; [ "width", @@ -195,51 +196,90 @@ function artplayerProxyCanvas(callback) { }); } }); - let lastDrawTime = 0; - const drawFrame = (timestamp)=>{ - if (!video.paused && !video.ended) { - if (timestamp - lastDrawTime > 1000 / 30) { - ctx.drawImage(video, 0, 0, canvas.width, canvas.height); - lastDrawTime = timestamp; - } - animationFrame = requestAnimationFrame(drawFrame); + const drawFrame = async ()=>{ + if (!video.videoWidth || !video.videoHeight) { + console.log("Video dimensions not ready yet"); + return; } + try { + if (video.readyState >= 2) { + // HAVE_CURRENT_DATA or higher + // 使用 createImageBitmap 进行预处理 + const bitmap = await createImageBitmap(video); + ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height); + bitmap.close(); + } else console.log("Video not ready for frame extraction"); + } catch (error) { + console.error("Error drawing video frame:", error); + } + }; + // 使用 requestVideoFrameCallback 进行精确同步 + let videoFrameCallback; + if ("requestVideoFrameCallback" in HTMLVideoElement.prototype) videoFrameCallback = ()=>{ + drawFrame(); + video.requestVideoFrameCallback(videoFrameCallback); + }; + else // 降级方案:使用 requestAnimationFrame + videoFrameCallback = ()=>{ + drawFrame(); + animationFrame = requestAnimationFrame(videoFrameCallback); }; const resize = ()=>{ - const player = art.template?.$player; - if (!player || option.autoSize) return; - const aspectRatio = canvas.videoWidth / canvas.videoHeight; - const containerWidth = player.clientWidth; - const containerHeight = player.clientHeight; - const containerRatio = containerWidth / containerHeight; - let paddingLeft = 0; - let paddingTop = 0; - if (containerRatio > aspectRatio) { - const canvasWidth = containerHeight * aspectRatio; - paddingLeft = (containerWidth - canvasWidth) / 2; - } else { - const canvasHeight = containerWidth / aspectRatio; - paddingTop = (containerHeight - canvasHeight) / 2; + try { + const player = art.template?.$player; + if (!player || option.autoSize) return; + const aspectRatio = video.videoWidth / video.videoHeight; + const containerWidth = player.clientWidth; + const containerHeight = player.clientHeight; + const containerRatio = containerWidth / containerHeight; + let canvasWidth, canvasHeight; + if (containerRatio > aspectRatio) { + canvasHeight = containerHeight; + canvasWidth = canvasHeight * aspectRatio; + } else { + canvasWidth = containerWidth; + canvasHeight = canvasWidth / aspectRatio; + } + // 设置 canvas 大小为实际显示大小 + canvas.width = canvasWidth; + canvas.height = canvasHeight; + // 居中 canvas + const paddingLeft = (containerWidth - canvasWidth) / 2; + const paddingTop = (containerHeight - canvasHeight) / 2; + Object.assign(canvas.style, { + padding: `${paddingTop}px ${paddingLeft}px` + }); + // 重新绘制当前帧 + drawFrame(); + } catch (error) { + console.error("Error in resize function:", error); } - Object.assign(canvas.style, { - padding: `${paddingTop}px ${paddingLeft}px` - }); }; + // 在视频元数据加载完成后设置 canvas 尺寸并绘制第一帧 art.on("video:loadedmetadata", ()=>{ canvas.width = video.videoWidth; canvas.height = video.videoHeight; resize(); }); + // 在视频数据可用时绘制第一帧 + art.on("video:canplay", ()=>{ + drawFrame(); + }); art.on("video:play", ()=>{ - cancelAnimationFrame(animationFrame); - animationFrame = requestAnimationFrame(drawFrame); + if ("requestVideoFrameCallback" in HTMLVideoElement.prototype) video.requestVideoFrameCallback(videoFrameCallback); + else { + cancelAnimationFrame(animationFrame); + animationFrame = requestAnimationFrame(videoFrameCallback); + } }); art.on("video:pause", ()=>{ - cancelAnimationFrame(animationFrame); + if (!("requestVideoFrameCallback" in HTMLVideoElement.prototype)) cancelAnimationFrame(animationFrame); }); art.on("resize", resize); const destroy = ()=>{ - cancelAnimationFrame(animationFrame); + if ("requestVideoFrameCallback" in HTMLVideoElement.prototype) video.cancelVideoFrameCallback(videoFrameCallback); + else cancelAnimationFrame(animationFrame); + // 清理其他可能的资源... }; art.on("destroy", destroy); if (typeof callback === "function") callback.call(art, video, option.url, art); diff --git a/docs/uncompiled/artplayer-proxy-canvas/index.js.map b/docs/uncompiled/artplayer-proxy-canvas/index.js.map index 86fc971aa..df7d68be4 100644 --- a/docs/uncompiled/artplayer-proxy-canvas/index.js.map +++ b/docs/uncompiled/artplayer-proxy-canvas/index.js.map @@ -1 +1 @@ -{"mappings":"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","sources":["packages/artplayer-proxy-canvas/src/index.js","node_modules/@parcel/transformer-js/src/esmodule-helpers.js"],"sourcesContent":["export default function artplayerProxyCanvas(callback) {\n return (art) => {\n const { option, constructor } = art;\n const { createElement, def } = constructor.utils;\n\n const canvas = createElement('canvas');\n const ctx = canvas.getContext('2d');\n const video = createElement('video');\n\n let animationFrame = null;\n const { propertys, methods, prototypes, events } = constructor.config;\n const keys = [...propertys, ...methods, ...prototypes];\n\n const originalCanvasProperties = {};\n ['width', 'height'].forEach((prop) => {\n originalCanvasProperties[prop] = Object.getOwnPropertyDescriptor(HTMLCanvasElement.prototype, prop);\n });\n\n keys.forEach((key) => {\n if (key === 'width' || key === 'height') {\n def(canvas, key, {\n get() {\n return originalCanvasProperties[key].get.call(this);\n },\n set(value) {\n originalCanvasProperties[key].set.call(this, value);\n video[key] = value;\n },\n });\n } else {\n def(canvas, key, {\n get() {\n const value = video[key];\n return typeof value === 'function' ? value.bind(video) : value;\n },\n set(value) {\n video[key] = value;\n },\n });\n }\n });\n\n setTimeout(() => {\n for (let index = 0; index < events.length; index++) {\n const event = events[index];\n art.proxy(video, event, (event) => {\n art.emit(`video:${event.type}`, event);\n });\n }\n });\n\n let lastDrawTime = 0;\n const drawFrame = (timestamp) => {\n if (!video.paused && !video.ended) {\n if (timestamp - lastDrawTime > 1000 / 30) {\n ctx.drawImage(video, 0, 0, canvas.width, canvas.height);\n lastDrawTime = timestamp;\n }\n animationFrame = requestAnimationFrame(drawFrame);\n }\n };\n\n const resize = () => {\n const player = art.template?.$player;\n if (!player || option.autoSize) return;\n\n const aspectRatio = canvas.videoWidth / canvas.videoHeight;\n const containerWidth = player.clientWidth;\n const containerHeight = player.clientHeight;\n const containerRatio = containerWidth / containerHeight;\n\n let paddingLeft = 0;\n let paddingTop = 0;\n\n if (containerRatio > aspectRatio) {\n const canvasWidth = containerHeight * aspectRatio;\n paddingLeft = (containerWidth - canvasWidth) / 2;\n } else {\n const canvasHeight = containerWidth / aspectRatio;\n paddingTop = (containerHeight - canvasHeight) / 2;\n }\n\n Object.assign(canvas.style, {\n padding: `${paddingTop}px ${paddingLeft}px`,\n });\n };\n\n art.on('video:loadedmetadata', () => {\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n resize();\n });\n\n art.on('video:play', () => {\n cancelAnimationFrame(animationFrame);\n animationFrame = requestAnimationFrame(drawFrame);\n });\n\n art.on('video:pause', () => {\n cancelAnimationFrame(animationFrame);\n });\n\n art.on('resize', resize);\n\n const destroy = () => {\n cancelAnimationFrame(animationFrame);\n };\n\n art.on('destroy', destroy);\n\n if (typeof callback === 'function') {\n callback.call(art, video, option.url, art);\n } else {\n video.src = option.url;\n }\n\n return canvas;\n };\n}\n\nif (typeof window !== 'undefined') {\n window['artplayerProxyCanvas'] = artplayerProxyCanvas;\n}\n","exports.interopDefault = function (a) {\n return a && a.__esModule ? a : {default: a};\n};\n\nexports.defineInteropFlag = function (a) {\n Object.defineProperty(a, '__esModule', {value: true});\n};\n\nexports.exportAll = function (source, dest) {\n Object.keys(source).forEach(function (key) {\n if (\n key === 'default' ||\n key === '__esModule' ||\n Object.prototype.hasOwnProperty.call(dest, key)\n ) {\n return;\n }\n\n Object.defineProperty(dest, key, {\n enumerable: true,\n get: function () {\n return source[key];\n },\n });\n });\n\n return dest;\n};\n\nexports.export = function (dest, destName, get) {\n Object.defineProperty(dest, destName, {\n enumerable: true,\n get: get,\n });\n};\n"],"names":[],"version":3,"file":"index.js.map","sourceRoot":"../../../"} \ No newline at end of file +{"mappings":"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","sources":["packages/artplayer-proxy-canvas/src/index.js","node_modules/@parcel/transformer-js/src/esmodule-helpers.js"],"sourcesContent":["export default function artplayerProxyCanvas(callback) {\n return (art) => {\n const { option, constructor } = art;\n const { createElement, def } = constructor.utils;\n\n const canvas = createElement('canvas');\n const ctx = canvas.getContext('2d');\n const video = createElement('video');\n\n let animationFrame = null;\n const { propertys, methods, prototypes, events } = constructor.config;\n const keys = [...propertys, ...methods, ...prototypes];\n\n // 存储 canvas 的原始属性\n const originalCanvasProperties = {};\n ['width', 'height'].forEach((prop) => {\n originalCanvasProperties[prop] = Object.getOwnPropertyDescriptor(HTMLCanvasElement.prototype, prop);\n });\n\n keys.forEach((key) => {\n if (key === 'width' || key === 'height') {\n def(canvas, key, {\n get() {\n return originalCanvasProperties[key].get.call(this);\n },\n set(value) {\n originalCanvasProperties[key].set.call(this, value);\n video[key] = value;\n },\n });\n } else {\n def(canvas, key, {\n get() {\n const value = video[key];\n return typeof value === 'function' ? value.bind(video) : value;\n },\n set(value) {\n video[key] = value;\n },\n });\n }\n });\n\n setTimeout(() => {\n for (let index = 0; index < events.length; index++) {\n const event = events[index];\n art.proxy(video, event, (event) => {\n art.emit(`video:${event.type}`, event);\n });\n }\n });\n\n const drawFrame = async () => {\n if (!video.videoWidth || !video.videoHeight) {\n console.log('Video dimensions not ready yet');\n return;\n }\n try {\n if (video.readyState >= 2) {\n // HAVE_CURRENT_DATA or higher\n // 使用 createImageBitmap 进行预处理\n const bitmap = await createImageBitmap(video);\n ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);\n bitmap.close();\n } else {\n console.log('Video not ready for frame extraction');\n }\n } catch (error) {\n console.error('Error drawing video frame:', error);\n }\n };\n\n // 使用 requestVideoFrameCallback 进行精确同步\n let videoFrameCallback;\n if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {\n videoFrameCallback = () => {\n drawFrame();\n video.requestVideoFrameCallback(videoFrameCallback);\n };\n } else {\n // 降级方案:使用 requestAnimationFrame\n videoFrameCallback = () => {\n drawFrame();\n animationFrame = requestAnimationFrame(videoFrameCallback);\n };\n }\n\n const resize = () => {\n try {\n const player = art.template?.$player;\n if (!player || option.autoSize) return;\n\n const aspectRatio = video.videoWidth / video.videoHeight;\n const containerWidth = player.clientWidth;\n const containerHeight = player.clientHeight;\n const containerRatio = containerWidth / containerHeight;\n\n let canvasWidth, canvasHeight;\n if (containerRatio > aspectRatio) {\n canvasHeight = containerHeight;\n canvasWidth = canvasHeight * aspectRatio;\n } else {\n canvasWidth = containerWidth;\n canvasHeight = canvasWidth / aspectRatio;\n }\n\n // 设置 canvas 大小为实际显示大小\n canvas.width = canvasWidth;\n canvas.height = canvasHeight;\n\n // 居中 canvas\n const paddingLeft = (containerWidth - canvasWidth) / 2;\n const paddingTop = (containerHeight - canvasHeight) / 2;\n\n Object.assign(canvas.style, {\n padding: `${paddingTop}px ${paddingLeft}px`,\n });\n\n // 重新绘制当前帧\n drawFrame();\n } catch (error) {\n console.error('Error in resize function:', error);\n }\n };\n\n // 在视频元数据加载完成后设置 canvas 尺寸并绘制第一帧\n art.on('video:loadedmetadata', () => {\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n resize();\n });\n\n // 在视频数据可用时绘制第一帧\n art.on('video:canplay', () => {\n drawFrame();\n });\n\n art.on('video:play', () => {\n if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {\n video.requestVideoFrameCallback(videoFrameCallback);\n } else {\n cancelAnimationFrame(animationFrame);\n animationFrame = requestAnimationFrame(videoFrameCallback);\n }\n });\n\n art.on('video:pause', () => {\n if (!('requestVideoFrameCallback' in HTMLVideoElement.prototype)) {\n cancelAnimationFrame(animationFrame);\n }\n });\n\n art.on('resize', resize);\n\n const destroy = () => {\n if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {\n video.cancelVideoFrameCallback(videoFrameCallback);\n } else {\n cancelAnimationFrame(animationFrame);\n }\n // 清理其他可能的资源...\n };\n\n art.on('destroy', destroy);\n\n if (typeof callback === 'function') {\n callback.call(art, video, option.url, art);\n } else {\n video.src = option.url;\n }\n\n return canvas;\n };\n}\n\nif (typeof window !== 'undefined') {\n window['artplayerProxyCanvas'] = artplayerProxyCanvas;\n}\n","exports.interopDefault = function (a) {\n return a && a.__esModule ? a : {default: a};\n};\n\nexports.defineInteropFlag = function (a) {\n Object.defineProperty(a, '__esModule', {value: true});\n};\n\nexports.exportAll = function (source, dest) {\n Object.keys(source).forEach(function (key) {\n if (\n key === 'default' ||\n key === '__esModule' ||\n Object.prototype.hasOwnProperty.call(dest, key)\n ) {\n return;\n }\n\n Object.defineProperty(dest, key, {\n enumerable: true,\n get: function () {\n return source[key];\n },\n });\n });\n\n return dest;\n};\n\nexports.export = function (dest, destName, get) {\n Object.defineProperty(dest, destName, {\n enumerable: true,\n get: get,\n });\n};\n"],"names":[],"version":3,"file":"index.js.map","sourceRoot":"../../../"} \ No newline at end of file diff --git a/packages/artplayer-proxy-canvas/src/index.js b/packages/artplayer-proxy-canvas/src/index.js index 1b967032a..0e164fb9c 100644 --- a/packages/artplayer-proxy-canvas/src/index.js +++ b/packages/artplayer-proxy-canvas/src/index.js @@ -11,6 +11,7 @@ export default function artplayerProxyCanvas(callback) { const { propertys, methods, prototypes, events } = constructor.config; const keys = [...propertys, ...methods, ...prototypes]; + // 存储 canvas 的原始属性 const originalCanvasProperties = {}; ['width', 'height'].forEach((prop) => { originalCanvasProperties[prop] = Object.getOwnPropertyDescriptor(HTMLCanvasElement.prototype, prop); @@ -49,61 +50,115 @@ export default function artplayerProxyCanvas(callback) { } }); - let lastDrawTime = 0; - const drawFrame = (timestamp) => { - if (!video.paused && !video.ended) { - if (timestamp - lastDrawTime > 1000 / 30) { - ctx.drawImage(video, 0, 0, canvas.width, canvas.height); - lastDrawTime = timestamp; + const drawFrame = async () => { + if (!video.videoWidth || !video.videoHeight) { + console.log('Video dimensions not ready yet'); + return; + } + try { + if (video.readyState >= 2) { + // HAVE_CURRENT_DATA or higher + // 使用 createImageBitmap 进行预处理 + const bitmap = await createImageBitmap(video); + ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height); + bitmap.close(); + } else { + console.log('Video not ready for frame extraction'); } - animationFrame = requestAnimationFrame(drawFrame); + } catch (error) { + console.error('Error drawing video frame:', error); } }; + // 使用 requestVideoFrameCallback 进行精确同步 + let videoFrameCallback; + if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) { + videoFrameCallback = () => { + drawFrame(); + video.requestVideoFrameCallback(videoFrameCallback); + }; + } else { + // 降级方案:使用 requestAnimationFrame + videoFrameCallback = () => { + drawFrame(); + animationFrame = requestAnimationFrame(videoFrameCallback); + }; + } + const resize = () => { - const player = art.template?.$player; - if (!player || option.autoSize) return; + try { + const player = art.template?.$player; + if (!player || option.autoSize) return; - const aspectRatio = canvas.videoWidth / canvas.videoHeight; - const containerWidth = player.clientWidth; - const containerHeight = player.clientHeight; - const containerRatio = containerWidth / containerHeight; + const aspectRatio = video.videoWidth / video.videoHeight; + const containerWidth = player.clientWidth; + const containerHeight = player.clientHeight; + const containerRatio = containerWidth / containerHeight; - let paddingLeft = 0; - let paddingTop = 0; + let canvasWidth, canvasHeight; + if (containerRatio > aspectRatio) { + canvasHeight = containerHeight; + canvasWidth = canvasHeight * aspectRatio; + } else { + canvasWidth = containerWidth; + canvasHeight = canvasWidth / aspectRatio; + } - if (containerRatio > aspectRatio) { - const canvasWidth = containerHeight * aspectRatio; - paddingLeft = (containerWidth - canvasWidth) / 2; - } else { - const canvasHeight = containerWidth / aspectRatio; - paddingTop = (containerHeight - canvasHeight) / 2; + // 设置 canvas 大小为实际显示大小 + canvas.width = canvasWidth; + canvas.height = canvasHeight; + + // 居中 canvas + const paddingLeft = (containerWidth - canvasWidth) / 2; + const paddingTop = (containerHeight - canvasHeight) / 2; + + Object.assign(canvas.style, { + padding: `${paddingTop}px ${paddingLeft}px`, + }); + + // 重新绘制当前帧 + drawFrame(); + } catch (error) { + console.error('Error in resize function:', error); } - - Object.assign(canvas.style, { - padding: `${paddingTop}px ${paddingLeft}px`, - }); }; + // 在视频元数据加载完成后设置 canvas 尺寸并绘制第一帧 art.on('video:loadedmetadata', () => { canvas.width = video.videoWidth; canvas.height = video.videoHeight; resize(); }); + // 在视频数据可用时绘制第一帧 + art.on('video:canplay', () => { + drawFrame(); + }); + art.on('video:play', () => { - cancelAnimationFrame(animationFrame); - animationFrame = requestAnimationFrame(drawFrame); + if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) { + video.requestVideoFrameCallback(videoFrameCallback); + } else { + cancelAnimationFrame(animationFrame); + animationFrame = requestAnimationFrame(videoFrameCallback); + } }); art.on('video:pause', () => { - cancelAnimationFrame(animationFrame); + if (!('requestVideoFrameCallback' in HTMLVideoElement.prototype)) { + cancelAnimationFrame(animationFrame); + } }); art.on('resize', resize); const destroy = () => { - cancelAnimationFrame(animationFrame); + if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) { + video.cancelVideoFrameCallback(videoFrameCallback); + } else { + cancelAnimationFrame(animationFrame); + } + // 清理其他可能的资源... }; art.on('destroy', destroy);