@therealtinhtute/baroiplayer 1.2.0 → 1.2.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 +1 -2
- package/dist/context/media.js +1 -2
- package/dist/context/media.mjs +3 -15
- package/dist/context/store.js +1 -2
- package/dist/context/store.mjs +3 -5
- package/dist/context-Bzd5QCKM.cjs +7 -0
- package/dist/context-Bzd5QCKM.cjs.map +1 -0
- package/dist/context-DD_SH4tT.js +1053 -0
- package/dist/context-DD_SH4tT.js.map +1 -0
- package/dist/core-events-BRnLMB7s.cjs +2 -0
- package/dist/core-events-BRnLMB7s.cjs.map +1 -0
- package/dist/core-events-v_mKdj5p.js +417 -0
- package/dist/core-events-v_mKdj5p.js.map +1 -0
- package/dist/core-plugins-CMBv-yVh.cjs +2 -0
- package/dist/core-plugins-CMBv-yVh.cjs.map +1 -0
- package/dist/core-plugins-CqCxLkbd.js +754 -0
- package/dist/core-plugins-CqCxLkbd.js.map +1 -0
- package/dist/core.js +7 -29
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +947 -1220
- package/dist/core.mjs.map +1 -1
- package/dist/enhanced-components-BwjBqSA1.js +2858 -0
- package/dist/enhanced-components-BwjBqSA1.js.map +1 -0
- package/dist/enhanced-components-C-ka-XLp.cjs +2 -0
- package/dist/enhanced-components-C-ka-XLp.cjs.map +1 -0
- package/dist/hooks-B-IIWPJr.js +1842 -0
- package/dist/hooks-B-IIWPJr.js.map +1 -0
- package/dist/hooks-DcHzmBwA.cjs +4 -0
- package/dist/hooks-DcHzmBwA.cjs.map +1 -0
- package/dist/index.js +27 -97
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5613 -6208
- package/dist/index.mjs.map +1 -1
- package/dist/minimal-components-BYsMQde5.js +1610 -0
- package/dist/minimal-components-BYsMQde5.js.map +1 -0
- package/dist/minimal-components-CsxQbDp7.cjs +2 -0
- package/dist/minimal-components-CsxQbDp7.cjs.map +1 -0
- package/dist/performance-monitor-DCSijLJg.js +312 -0
- package/dist/performance-monitor-DCSijLJg.js.map +1 -0
- package/dist/performance-monitor-wNFKmqMr.cjs +2 -0
- package/dist/performance-monitor-wNFKmqMr.cjs.map +1 -0
- package/dist/rolldown-runtime-BmowowmI.js +14 -0
- package/dist/rolldown-runtime-mLOUI7ql.cjs +1 -0
- package/dist/utils-D2gQ7c2R.js +64 -0
- package/dist/utils-D2gQ7c2R.js.map +1 -0
- package/dist/utils-DJaAwiZ9.cjs +2 -0
- package/dist/utils-DJaAwiZ9.cjs.map +1 -0
- package/dist/utils.js +1 -2
- package/dist/utils.mjs +3 -11
- package/package.json +28 -22
- package/src/components/AnimateLoading.tsx +14 -10
- package/src/components/examples/loading-states-demo.tsx +1 -2
- package/src/components/examples/react-19-demo.tsx +6 -10
- package/src/components/examples/volume-orientation-demo.tsx +1 -2
- package/src/components/ui/blur-poster.tsx +18 -15
- package/src/components/variants/minimal/components/VideoElement.tsx +3 -1
- package/src/core/performance/media-loading-optimizer.ts +2 -1
- package/src/hooks/useScreenshot.ts +60 -44
- package/src/types/assets.d.ts +29 -0
- package/dist/context/media.js.map +0 -1
- package/dist/context/media.mjs.map +0 -1
- package/dist/context/store.js.map +0 -1
- package/dist/context/store.mjs.map +0 -1
- package/dist/context-BYwDoSRX.js +0 -1172
- package/dist/context-BYwDoSRX.js.map +0 -1
- package/dist/context-eNS62_ac.cjs +0 -23
- package/dist/context-eNS62_ac.cjs.map +0 -1
- package/dist/core-events-B1iM2F46.cjs +0 -2
- package/dist/core-events-B1iM2F46.cjs.map +0 -1
- package/dist/core-events-DunPHKRE.js +0 -484
- package/dist/core-events-DunPHKRE.js.map +0 -1
- package/dist/core-plugins-By-4-kGs.cjs +0 -2
- package/dist/core-plugins-By-4-kGs.cjs.map +0 -1
- package/dist/core-plugins-DSBWcTuX.js +0 -821
- package/dist/core-plugins-DSBWcTuX.js.map +0 -1
- package/dist/enhanced-components-B_l3cist.js +0 -1142
- package/dist/enhanced-components-B_l3cist.js.map +0 -1
- package/dist/enhanced-components-BspFs9OO.cjs +0 -2
- package/dist/enhanced-components-BspFs9OO.cjs.map +0 -1
- package/dist/hooks-BXwI8QG6.cjs +0 -4
- package/dist/hooks-BXwI8QG6.cjs.map +0 -1
- package/dist/hooks-Bpi-jDiO.js +0 -1816
- package/dist/hooks-Bpi-jDiO.js.map +0 -1
- package/dist/minimal-components-C4z900Ro.js +0 -1926
- package/dist/minimal-components-C4z900Ro.js.map +0 -1
- package/dist/minimal-components-v4qBp6jv.cjs +0 -2
- package/dist/minimal-components-v4qBp6jv.cjs.map +0 -1
- package/dist/performance-monitor-C5k6FDXw.js +0 -301
- package/dist/performance-monitor-C5k6FDXw.js.map +0 -1
- package/dist/performance-monitor-lJmuUjQQ.cjs +0 -2
- package/dist/performance-monitor-lJmuUjQQ.cjs.map +0 -1
- package/dist/utils-CFOue_9K.cjs +0 -9
- package/dist/utils-CFOue_9K.cjs.map +0 -1
- package/dist/utils-Dzpl2ArK.js +0 -2844
- package/dist/utils-Dzpl2ArK.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/dist/utils.mjs.map +0 -1
package/package.json
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@therealtinhtute/baroiplayer",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "A customizable media player component library for React",
|
|
7
7
|
"sideEffects": false,
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"
|
|
10
|
+
"development": {
|
|
11
|
+
"types": "./src/index.ts",
|
|
12
|
+
"default": "./src/index.ts"
|
|
13
|
+
},
|
|
14
|
+
"types": "./src/index.ts",
|
|
11
15
|
"import": "./dist/index.mjs",
|
|
12
|
-
"require": "./dist/index.js"
|
|
16
|
+
"require": "./dist/index.js",
|
|
17
|
+
"default": "./dist/index.mjs"
|
|
13
18
|
},
|
|
14
|
-
"./globals.css": "./
|
|
15
|
-
"./minimal-player.css": "./
|
|
19
|
+
"./globals.css": "./src/styles/globals.css",
|
|
20
|
+
"./minimal-player.css": "./src/styles/minimal-player.css",
|
|
16
21
|
"./dist/*": "./dist/*",
|
|
17
22
|
"./lib/*": "./src/lib/*.ts",
|
|
18
23
|
"./components/*": "./src/components/*.tsx",
|
|
@@ -21,7 +26,7 @@
|
|
|
21
26
|
},
|
|
22
27
|
"main": "./dist/index.js",
|
|
23
28
|
"module": "./dist/index.mjs",
|
|
24
|
-
"types": "./
|
|
29
|
+
"types": "./src/index.ts",
|
|
25
30
|
"files": ["dist", "src", "README.md", "LICENSE"],
|
|
26
31
|
"scripts": {
|
|
27
32
|
"build": "vite build",
|
|
@@ -45,32 +50,33 @@
|
|
|
45
50
|
"@radix-ui/react-tooltip": "^1.2.7",
|
|
46
51
|
"clsx": "^2.1.1",
|
|
47
52
|
"react-player": "^3.3.1",
|
|
48
|
-
"tailwind-merge": "^3.
|
|
53
|
+
"tailwind-merge": "^3.6.0",
|
|
49
54
|
"web-vitals": "^4.2.4"
|
|
50
55
|
},
|
|
51
56
|
"devDependencies": {
|
|
52
57
|
"@rp/typescript-config": "workspace:*",
|
|
53
|
-
"@tailwindcss/postcss": "
|
|
54
|
-
"@testing-library/jest-dom": "^6.
|
|
55
|
-
"@testing-library/react": "^16.
|
|
58
|
+
"@tailwindcss/postcss": "4.3.2",
|
|
59
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
60
|
+
"@testing-library/react": "^16.3.2",
|
|
56
61
|
"@testing-library/user-event": "^14.5.2",
|
|
57
62
|
"@types/node": "^20",
|
|
58
|
-
"@types/react": "^19.
|
|
59
|
-
"@types/react-dom": "^19.
|
|
60
|
-
"@vitejs/plugin-react": "^
|
|
61
|
-
"@vitest/coverage-v8": "^
|
|
62
|
-
"babel-plugin-react-compiler": "^
|
|
63
|
-
"jsdom": "^
|
|
63
|
+
"@types/react": "^19.2.17",
|
|
64
|
+
"@types/react-dom": "^19.2.3",
|
|
65
|
+
"@vitejs/plugin-react": "^6.0.3",
|
|
66
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
67
|
+
"babel-plugin-react-compiler": "^1.0.0",
|
|
68
|
+
"jsdom": "^29.1.1",
|
|
64
69
|
"lightningcss": "^1.30.1",
|
|
65
70
|
"rollup-plugin-analyzer": "^4.0.0",
|
|
66
71
|
"rollup-plugin-bundle-analyzer": "^1.1.0",
|
|
67
|
-
"size-limit": "^
|
|
68
|
-
"
|
|
72
|
+
"@size-limit/file": "^12.1.0",
|
|
73
|
+
"size-limit": "^12.1.0",
|
|
74
|
+
"tailwindcss": "4.3.2",
|
|
69
75
|
"ts-morph": "^27.0.0",
|
|
70
|
-
"typescript": "^5",
|
|
71
|
-
"vite": "^
|
|
76
|
+
"typescript": "^5.9.3",
|
|
77
|
+
"vite": "^8.1.4",
|
|
72
78
|
"vite-bundle-analyzer": "^0.13.1",
|
|
73
|
-
"vitest": "^
|
|
79
|
+
"vitest": "^4.1.10"
|
|
74
80
|
},
|
|
75
81
|
"peerDependencies": {
|
|
76
82
|
"react": ">=18.0.0",
|
|
@@ -105,6 +111,6 @@
|
|
|
105
111
|
"url": "https://github.com/ryanphanrp/baroiplayer/issues"
|
|
106
112
|
},
|
|
107
113
|
"engines": {
|
|
108
|
-
"node": ">=
|
|
114
|
+
"node": ">=20.19.0"
|
|
109
115
|
}
|
|
110
116
|
}
|
|
@@ -3,20 +3,24 @@
|
|
|
3
3
|
import React, { useState, useEffect } from "react"
|
|
4
4
|
import { cn } from "../lib/utils"
|
|
5
5
|
|
|
6
|
-
//
|
|
7
|
-
let monaLoadingGif: string | undefined
|
|
8
|
-
|
|
9
|
-
// Import the GIF statically for bundling - this will be handled by Vite
|
|
10
|
-
// @ts-ignore - Vite handles this import
|
|
6
|
+
// Import the GIF statically for bundling (Vite / webpack may yield string or { src })
|
|
11
7
|
import monaGifSrc from "../assets/mona-loading.gif"
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
function resolveAssetUrl(src: unknown): string | undefined {
|
|
10
|
+
if (typeof src === "string" && src.length > 0) return src
|
|
11
|
+
if (
|
|
12
|
+
src &&
|
|
13
|
+
typeof src === "object" &&
|
|
14
|
+
"src" in src &&
|
|
15
|
+
typeof (src as { src: unknown }).src === "string"
|
|
16
|
+
) {
|
|
17
|
+
return (src as { src: string }).src
|
|
18
|
+
}
|
|
19
|
+
return undefined
|
|
18
20
|
}
|
|
19
21
|
|
|
22
|
+
const monaLoadingGif = resolveAssetUrl(monaGifSrc)
|
|
23
|
+
|
|
20
24
|
interface AnimateLoadingProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
21
25
|
loadingText?: string
|
|
22
26
|
gifSrc?: string
|
|
@@ -82,8 +82,7 @@ export function LoadingStatesDemo() {
|
|
|
82
82
|
|
|
83
83
|
<div className="aspect-video overflow-hidden rounded-lg bg-black">
|
|
84
84
|
<SimpleMinimalPlayer
|
|
85
|
-
src="https://
|
|
86
|
-
poster="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg"
|
|
85
|
+
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
|
|
87
86
|
crossOrigin="anonymous"
|
|
88
87
|
showLoadingState={true}
|
|
89
88
|
loadingComponent={showCustomLoading ? CustomLoadingComponent : undefined}
|
|
@@ -270,21 +270,17 @@ function PerformanceDebugPanel({
|
|
|
270
270
|
// Example usage component
|
|
271
271
|
export function React19DemoExample() {
|
|
272
272
|
const [currentSrc, setCurrentSrc] = React.useState(
|
|
273
|
-
"https://
|
|
273
|
+
"https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
|
|
274
274
|
)
|
|
275
275
|
|
|
276
276
|
const videoSources = [
|
|
277
277
|
{
|
|
278
|
-
src: "https://
|
|
279
|
-
title: "
|
|
280
|
-
poster:
|
|
281
|
-
"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg"
|
|
278
|
+
src: "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4",
|
|
279
|
+
title: "Flower (MDN)"
|
|
282
280
|
},
|
|
283
281
|
{
|
|
284
|
-
src: "https://
|
|
285
|
-
title: "
|
|
286
|
-
poster:
|
|
287
|
-
"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/ElephantsDream.jpg"
|
|
282
|
+
src: "https://www.w3schools.com/html/mov_bbb.mp4",
|
|
283
|
+
title: "Big Buck Bunny"
|
|
288
284
|
}
|
|
289
285
|
]
|
|
290
286
|
|
|
@@ -320,7 +316,7 @@ export function React19DemoExample() {
|
|
|
320
316
|
{/* Enhanced Media Player */}
|
|
321
317
|
<React19MediaPlayerDemo
|
|
322
318
|
src={currentSrc}
|
|
323
|
-
poster={
|
|
319
|
+
poster={undefined}
|
|
324
320
|
mediaId={`demo-${currentSrc}`}
|
|
325
321
|
className="w-full"
|
|
326
322
|
/>
|
|
@@ -41,8 +41,7 @@ export function VolumeOrientationDemo() {
|
|
|
41
41
|
{/* Demo Player */}
|
|
42
42
|
<div className="aspect-video overflow-hidden rounded-lg bg-black">
|
|
43
43
|
<SimpleMinimalPlayer
|
|
44
|
-
src="https://
|
|
45
|
-
poster="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg"
|
|
44
|
+
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
|
|
46
45
|
crossOrigin="anonymous"
|
|
47
46
|
className="h-full w-full"
|
|
48
47
|
volumeOrientation={orientation}
|
|
@@ -9,11 +9,11 @@ interface BlurPosterProps {
|
|
|
9
9
|
transitionDuration?: number
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
low: "
|
|
14
|
-
medium: "
|
|
15
|
-
high: "
|
|
16
|
-
}
|
|
12
|
+
const intensityBlurPx = {
|
|
13
|
+
low: "8px",
|
|
14
|
+
medium: "16px",
|
|
15
|
+
high: "32px"
|
|
16
|
+
} as const
|
|
17
17
|
|
|
18
18
|
const BlurPosterComponent = memo(function BlurPoster({
|
|
19
19
|
src,
|
|
@@ -24,15 +24,16 @@ const BlurPosterComponent = memo(function BlurPoster({
|
|
|
24
24
|
}: BlurPosterProps) {
|
|
25
25
|
return (
|
|
26
26
|
<div
|
|
27
|
-
className={cn(
|
|
28
|
-
"relative overflow-hidden bg-black/20",
|
|
29
|
-
"before:absolute before:inset-0 before:bg-center before:bg-cover before:opacity-100",
|
|
30
|
-
`before:[background-image:url('${src}')]`,
|
|
31
|
-
`before:[filter:blur(${intensity === "low" ? "8px" : intensity === "medium" ? "16px" : "32px"})]`,
|
|
32
|
-
className
|
|
33
|
-
)}
|
|
27
|
+
className={cn("relative overflow-hidden bg-black/20", className)}
|
|
34
28
|
aria-label={alt}
|
|
35
29
|
role="img">
|
|
30
|
+
<div
|
|
31
|
+
className="absolute inset-0 bg-center bg-cover opacity-100"
|
|
32
|
+
style={{
|
|
33
|
+
backgroundImage: `url('${src}')`,
|
|
34
|
+
filter: `blur(${intensityBlurPx[intensity]})`
|
|
35
|
+
}}
|
|
36
|
+
/>
|
|
36
37
|
<div
|
|
37
38
|
className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-black/20"
|
|
38
39
|
style={{ transitionDuration: `${transitionDuration}ms` }}
|
|
@@ -75,10 +76,12 @@ export const BlurPosterWithPlaceholder = memo(function BlurPosterWithPlaceholder
|
|
|
75
76
|
<div
|
|
76
77
|
className={cn(
|
|
77
78
|
"absolute inset-0 bg-center bg-cover opacity-0 transition-opacity duration-500",
|
|
78
|
-
!isLoading && "opacity-100"
|
|
79
|
-
`[background-image:url('${src}')]`,
|
|
80
|
-
`[filter:blur(${intensity === "low" ? "8px" : intensity === "medium" ? "16px" : "32px"})]`
|
|
79
|
+
!isLoading && "opacity-100"
|
|
81
80
|
)}
|
|
81
|
+
style={{
|
|
82
|
+
backgroundImage: `url('${src}')`,
|
|
83
|
+
filter: `blur(${intensityBlurPx[intensity]})`
|
|
84
|
+
}}
|
|
82
85
|
aria-hidden="true"
|
|
83
86
|
/>
|
|
84
87
|
</>
|
|
@@ -20,11 +20,13 @@ export const VideoElement = function VideoElement({
|
|
|
20
20
|
onTogglePlay
|
|
21
21
|
}: VideoElementProps) {
|
|
22
22
|
return (
|
|
23
|
+
// crossOrigin must be applied before src so the media request is CORS-enabled
|
|
24
|
+
// (required for canvas screenshot / frame capture).
|
|
23
25
|
<video
|
|
24
26
|
ref={videoRef}
|
|
27
|
+
crossOrigin={crossOrigin}
|
|
25
28
|
src={src}
|
|
26
29
|
poster={poster}
|
|
27
|
-
crossOrigin={crossOrigin}
|
|
28
30
|
className="h-full w-full object-contain"
|
|
29
31
|
onClick={onTogglePlay}>
|
|
30
32
|
{subtitleSrc && <track kind="subtitles" src={subtitleSrc} default={subtitlesEnabled} />}
|
|
@@ -356,7 +356,8 @@ export class MediaLoadingOptimizer {
|
|
|
356
356
|
reader.releaseLock()
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
-
|
|
359
|
+
// TS 5.9+: Uint8Array generic buffer typing is stricter than BlobPart
|
|
360
|
+
return new Blob(chunks as BlobPart[])
|
|
360
361
|
}
|
|
361
362
|
|
|
362
363
|
// Monitor loading progress and update metrics
|
|
@@ -1,61 +1,77 @@
|
|
|
1
1
|
import { useCallback } from "react"
|
|
2
2
|
|
|
3
|
+
function isCanvasTaintError(error: unknown): boolean {
|
|
4
|
+
if (error instanceof DOMException && error.name === "SecurityError") return true
|
|
5
|
+
if (error instanceof Error && /tainted/i.test(error.message)) return true
|
|
6
|
+
return false
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Capture the current video frame.
|
|
11
|
+
* Requires the video to be loaded with CORS-friendly headers and
|
|
12
|
+
* `crossOrigin="anonymous"` (or `use-credentials`) on the element.
|
|
13
|
+
*/
|
|
3
14
|
export function useScreenshot(videoRef: React.RefObject<HTMLVideoElement | null>) {
|
|
4
|
-
const takeScreenshot = (
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
// Draw the video frame to canvas
|
|
16
|
-
ctx.drawImage(video, 0, 0)
|
|
17
|
-
|
|
18
|
-
// Try to export the canvas
|
|
19
|
-
canvas.toBlob((blob) => {
|
|
20
|
-
if (!blob) {
|
|
21
|
-
console.error("Failed to create blob from canvas")
|
|
22
|
-
return
|
|
23
|
-
}
|
|
15
|
+
const takeScreenshot = useCallback(
|
|
16
|
+
(ctrlKey: boolean = false) => {
|
|
17
|
+
const video = videoRef.current
|
|
18
|
+
if (!video) return
|
|
19
|
+
|
|
20
|
+
if (!video.videoWidth || !video.videoHeight) {
|
|
21
|
+
console.warn("Screenshot skipped: video frame not ready yet.")
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
const canvas = document.createElement("canvas")
|
|
26
|
+
canvas.width = video.videoWidth
|
|
27
|
+
canvas.height = video.videoHeight
|
|
28
|
+
const ctx = canvas.getContext("2d")
|
|
29
|
+
if (!ctx) return
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
ctx.drawImage(video, 0, 0)
|
|
33
|
+
|
|
34
|
+
// SecurityError throws here when the canvas is tainted by a
|
|
35
|
+
// cross-origin video without usable CORS + crossOrigin on <video>.
|
|
36
|
+
canvas.toBlob((blob) => {
|
|
37
|
+
if (!blob) {
|
|
38
|
+
console.error("Screenshot failed: could not create image blob.")
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (ctrlKey) {
|
|
43
|
+
if (navigator.clipboard && window.ClipboardItem) {
|
|
44
|
+
const item = new ClipboardItem({ "image/png": blob })
|
|
45
|
+
navigator.clipboard.write([item]).catch((err) => {
|
|
46
|
+
console.error("Failed to copy screenshot to clipboard:", err)
|
|
47
|
+
})
|
|
48
|
+
} else {
|
|
49
|
+
console.warn("Clipboard API not supported for screenshot copy.")
|
|
50
|
+
}
|
|
51
|
+
return
|
|
34
52
|
}
|
|
35
|
-
|
|
36
|
-
// Download
|
|
53
|
+
|
|
37
54
|
const url = URL.createObjectURL(blob)
|
|
38
55
|
const a = document.createElement("a")
|
|
39
56
|
a.href = url
|
|
40
57
|
a.download = `screenshot-${Date.now()}.png`
|
|
41
58
|
a.click()
|
|
42
59
|
URL.revokeObjectURL(url)
|
|
60
|
+
}, "image/png")
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (isCanvasTaintError(error)) {
|
|
63
|
+
console.error(
|
|
64
|
+
"Screenshot failed: canvas tainted. The video is cross-origin without CORS allowing canvas use. " +
|
|
65
|
+
"Serve the media with Access-Control-Allow-Origin and set crossOrigin=\"anonymous\" on the player " +
|
|
66
|
+
"(must be set before the media loads)."
|
|
67
|
+
)
|
|
68
|
+
return
|
|
43
69
|
}
|
|
44
|
-
}, "image/png")
|
|
45
|
-
} catch (error) {
|
|
46
|
-
if (error instanceof Error && error.message.includes("tainted")) {
|
|
47
|
-
console.error(
|
|
48
|
-
"Canvas is tainted - video source may not have proper CORS headers. Try setting crossOrigin='anonymous' on the video element."
|
|
49
|
-
)
|
|
50
|
-
alert(
|
|
51
|
-
"Screenshot failed: The video source doesn't allow screenshots due to CORS policy. Please ensure the video server includes proper CORS headers or set crossOrigin='anonymous' on the video player."
|
|
52
|
-
)
|
|
53
|
-
} else {
|
|
54
70
|
console.error("Screenshot failed:", error)
|
|
55
|
-
alert("Screenshot failed: " + (error instanceof Error ? error.message : "Unknown error"))
|
|
56
71
|
}
|
|
57
|
-
}
|
|
58
|
-
|
|
72
|
+
},
|
|
73
|
+
[videoRef]
|
|
74
|
+
)
|
|
59
75
|
|
|
60
76
|
return { takeScreenshot }
|
|
61
77
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
declare module "*.gif" {
|
|
2
|
+
const src: string
|
|
3
|
+
export default src
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
declare module "*.png" {
|
|
7
|
+
const src: string
|
|
8
|
+
export default src
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare module "*.jpg" {
|
|
12
|
+
const src: string
|
|
13
|
+
export default src
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare module "*.jpeg" {
|
|
17
|
+
const src: string
|
|
18
|
+
export default src
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
declare module "*.webp" {
|
|
22
|
+
const src: string
|
|
23
|
+
export default src
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
declare module "*.svg" {
|
|
27
|
+
const src: string
|
|
28
|
+
export default src
|
|
29
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"media.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"media.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"store.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|