@scarlett-player/watermark 1.12.0 → 1.14.0
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 +6 -4
- package/dist/index.cjs +46 -18
- package/dist/index.js +46 -18
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @scarlett-player/watermark
|
|
2
2
|
|
|
3
|
-
Anti-piracy watermark plugin for [Scarlett Player](https://scarlettplayer.com). Overlays text (typically the viewer's email or account id) or an image on the player, at a fixed corner or moving to a random position on a timer, with an optional delay before it first appears. The overlay is
|
|
3
|
+
Anti-piracy watermark plugin for [Scarlett Player](https://scarlettplayer.com). Overlays text (typically the viewer's email or account id) or an image on the player, at a fixed corner or moving to a random position on a timer, with an optional delay before it first appears. The overlay is hidden until the first play and hidden again on ended, so it never sits on the poster. It stays visible while paused — hiding it there would leave screenshots and screen captures unmarked.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -81,7 +81,7 @@ watermark?.getConfig();
|
|
|
81
81
|
| `setOpacity(opacity)` | Set opacity, clamped to 0 to 1 |
|
|
82
82
|
| `setImageHeight(height)` | Set the maximum image height in px |
|
|
83
83
|
| `setPadding(padding)` | Set the edge padding in px for every edge and re-apply the current position |
|
|
84
|
-
| `show()` / `hide()` | Toggle visibility without touching the timers |
|
|
84
|
+
| `show()` / `hide()` | Toggle the mark's inline `visibility` without touching the timers |
|
|
85
85
|
| `getConfig()` | The original config merged with the current position, opacity, image height and padding |
|
|
86
86
|
|
|
87
87
|
## Per-track watermarks
|
|
@@ -94,17 +94,19 @@ When `@scarlett-player/playlist` is present the plugin listens for `playlist:cha
|
|
|
94
94
|
|
|
95
95
|
## Styling
|
|
96
96
|
|
|
97
|
-
The overlay is a single `div` appended to the player container with inline positioning, `pointer-events: none`, white text with a subtle shadow, and a 0.5s transition. It carries these classes for your own CSS:
|
|
97
|
+
The overlay is a single `div` appended to the player container with inline positioning, `pointer-events: none`, white text with a subtle shadow, and a 0.5s transition. Visibility is driven by an inline `visibility` (`hidden` until the first play), not by the classes below, so the plugin needs no stylesheet of its own. It carries these classes for your own CSS:
|
|
98
98
|
|
|
99
99
|
| Class | When |
|
|
100
100
|
|---|---|
|
|
101
101
|
| `sp-watermark` | Always |
|
|
102
|
-
| `sp-watermark--visible` / `sp-watermark--hidden` | Current visibility |
|
|
102
|
+
| `sp-watermark--visible` / `sp-watermark--hidden` | Current visibility, mirroring the inline `visibility` |
|
|
103
103
|
| `sp-watermark--top-left`, `sp-watermark--top-right`, `sp-watermark--bottom-left`, `sp-watermark--bottom-right`, `sp-watermark--center` | Current position, applied after the first `setPosition()` or dynamic move |
|
|
104
104
|
| `sp-watermark--dynamic` | Present when `dynamic` is on, applied after the first position change |
|
|
105
105
|
|
|
106
106
|
The element also has a `data-position` attribute holding the current position. No CSS custom properties are used.
|
|
107
107
|
|
|
108
|
+
A `MutationObserver` on the player container re-attaches the overlay if it is removed and restores its `opacity`, `visibility`, `pointer-events`, `position` and `z-index` if its inline style is edited. It restores the values the plugin last applied — the ones your `setOpacity()` / `hide()` calls set, not the original config — and it only watches the overlay's own style, so styling anything else inside the container is unaffected.
|
|
109
|
+
|
|
108
110
|
## Events
|
|
109
111
|
|
|
110
112
|
The plugin emits no events. It listens to `playback:play`, `playback:pause`, `playback:ended` and `playlist:change`.
|
package/dist/index.cjs
CHANGED
|
@@ -26,10 +26,18 @@ __export(index_exports, {
|
|
|
26
26
|
module.exports = __toCommonJS(index_exports);
|
|
27
27
|
|
|
28
28
|
// src/version.ts
|
|
29
|
-
var PKG_VERSION = true ? "1.
|
|
29
|
+
var PKG_VERSION = true ? "1.13.0" : "0.0.0-dev";
|
|
30
30
|
|
|
31
31
|
// src/index.ts
|
|
32
32
|
var POSITIONS = ["top-left", "top-right", "bottom-left", "bottom-right", "center"];
|
|
33
|
+
function clampOpacity(value, fallback = 0.5) {
|
|
34
|
+
if (!Number.isFinite(value)) return fallback;
|
|
35
|
+
return Math.max(0, Math.min(1, value));
|
|
36
|
+
}
|
|
37
|
+
function clampPx(value, floor, fallback) {
|
|
38
|
+
if (!Number.isFinite(value)) return fallback;
|
|
39
|
+
return Math.max(floor, value);
|
|
40
|
+
}
|
|
33
41
|
function getPositionStyles(padding, bottomPadding) {
|
|
34
42
|
return {
|
|
35
43
|
"top-left": `top:${padding}px;left:${padding}px;`,
|
|
@@ -45,11 +53,12 @@ function createWatermarkPlugin(config = {}) {
|
|
|
45
53
|
let dynamicTimer = null;
|
|
46
54
|
let showDelayTimer = null;
|
|
47
55
|
let currentPosition = config.position || "bottom-right";
|
|
48
|
-
|
|
56
|
+
let currentOpacity = clampOpacity(config.opacity ?? 0.5);
|
|
57
|
+
let currentVisible = false;
|
|
49
58
|
const fontSize = config.fontSize ?? 14;
|
|
50
|
-
let currentImageHeight = config.imageHeight ?? 40;
|
|
51
|
-
let currentPadding = config.padding ?? 10;
|
|
52
|
-
let currentBottomPadding = config.padding ?? 40;
|
|
59
|
+
let currentImageHeight = clampPx(config.imageHeight ?? 40, 1, 40);
|
|
60
|
+
let currentPadding = clampPx(config.padding ?? 10, 0, 10);
|
|
61
|
+
let currentBottomPadding = clampPx(config.padding ?? 40, 0, 40);
|
|
53
62
|
const dynamic = config.dynamic ?? false;
|
|
54
63
|
const dynamicInterval = config.dynamicInterval ?? 1e4;
|
|
55
64
|
const showDelay = config.showDelay ?? 0;
|
|
@@ -60,7 +69,7 @@ function createWatermarkPlugin(config = {}) {
|
|
|
60
69
|
const createElement = () => {
|
|
61
70
|
const el = document.createElement("div");
|
|
62
71
|
el.className = "sp-watermark sp-watermark--hidden";
|
|
63
|
-
el.style.cssText = `position:absolute;z-index:10;pointer-events:none;opacity:${
|
|
72
|
+
el.style.cssText = `position:absolute;z-index:10;pointer-events:none;visibility:${currentVisible ? "visible" : "hidden"};opacity:${currentOpacity};font-size:${fontSize}px;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,0.6);font-family:sans-serif;transition:all 0.5s ease;${positionStyles[currentPosition]}`;
|
|
64
73
|
el.setAttribute("data-position", currentPosition);
|
|
65
74
|
updateContent(el);
|
|
66
75
|
return el;
|
|
@@ -106,8 +115,7 @@ function createWatermarkPlugin(config = {}) {
|
|
|
106
115
|
}
|
|
107
116
|
});
|
|
108
117
|
element.setAttribute("data-position", position);
|
|
109
|
-
const
|
|
110
|
-
const visClass = isVisible ? " sp-watermark--visible" : " sp-watermark--hidden";
|
|
118
|
+
const visClass = currentVisible ? " sp-watermark--visible" : " sp-watermark--hidden";
|
|
111
119
|
element.className = `sp-watermark sp-watermark--${position}${visClass}${dynamic ? " sp-watermark--dynamic" : ""}`;
|
|
112
120
|
};
|
|
113
121
|
const randomizePosition = () => {
|
|
@@ -116,12 +124,16 @@ function createWatermarkPlugin(config = {}) {
|
|
|
116
124
|
setPosition(next);
|
|
117
125
|
};
|
|
118
126
|
const show = () => {
|
|
127
|
+
currentVisible = true;
|
|
119
128
|
if (!element) return;
|
|
129
|
+
element.style.visibility = "visible";
|
|
120
130
|
element.classList.remove("sp-watermark--hidden");
|
|
121
131
|
element.classList.add("sp-watermark--visible");
|
|
122
132
|
};
|
|
123
133
|
const hide = () => {
|
|
134
|
+
currentVisible = false;
|
|
124
135
|
if (!element) return;
|
|
136
|
+
element.style.visibility = "hidden";
|
|
125
137
|
element.classList.remove("sp-watermark--visible");
|
|
126
138
|
element.classList.add("sp-watermark--hidden");
|
|
127
139
|
};
|
|
@@ -175,11 +187,18 @@ function createWatermarkPlugin(config = {}) {
|
|
|
175
187
|
}
|
|
176
188
|
}
|
|
177
189
|
} else if (mutation.type === "attributes" && mutation.attributeName === "style") {
|
|
178
|
-
if (element)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
190
|
+
if (mutation.target !== element) continue;
|
|
191
|
+
const restore = [
|
|
192
|
+
["opacity", String(currentOpacity)],
|
|
193
|
+
["visibility", currentVisible ? "visible" : "hidden"],
|
|
194
|
+
["pointer-events", "none"],
|
|
195
|
+
["position", "absolute"],
|
|
196
|
+
["z-index", "10"]
|
|
197
|
+
];
|
|
198
|
+
for (const [prop, value] of restore) {
|
|
199
|
+
if (element.style.getPropertyValue(prop) !== value) {
|
|
200
|
+
element.style.setProperty(prop, value);
|
|
201
|
+
}
|
|
183
202
|
}
|
|
184
203
|
}
|
|
185
204
|
}
|
|
@@ -208,6 +227,10 @@ function createWatermarkPlugin(config = {}) {
|
|
|
208
227
|
}
|
|
209
228
|
});
|
|
210
229
|
const unsubEnded = api.on("playback:ended", () => {
|
|
230
|
+
if (showDelayTimer) {
|
|
231
|
+
clearTimeout(showDelayTimer);
|
|
232
|
+
showDelayTimer = null;
|
|
233
|
+
}
|
|
211
234
|
hide();
|
|
212
235
|
stopDynamic();
|
|
213
236
|
});
|
|
@@ -243,25 +266,30 @@ function createWatermarkPlugin(config = {}) {
|
|
|
243
266
|
},
|
|
244
267
|
setPosition,
|
|
245
268
|
setOpacity(value) {
|
|
246
|
-
|
|
269
|
+
currentOpacity = clampOpacity(value, currentOpacity);
|
|
270
|
+
if (element) element.style.opacity = String(currentOpacity);
|
|
247
271
|
},
|
|
248
272
|
setImageHeight(height) {
|
|
249
|
-
currentImageHeight =
|
|
273
|
+
currentImageHeight = clampPx(height, 1, currentImageHeight);
|
|
250
274
|
if (element) {
|
|
251
275
|
const img = element.querySelector("img");
|
|
252
276
|
if (img) img.style.maxHeight = `${currentImageHeight}px`;
|
|
253
277
|
}
|
|
254
278
|
},
|
|
255
279
|
setPadding(value) {
|
|
256
|
-
currentPadding =
|
|
257
|
-
currentBottomPadding =
|
|
280
|
+
currentPadding = clampPx(value, 0, currentPadding);
|
|
281
|
+
currentBottomPadding = clampPx(
|
|
282
|
+
Math.max(value, config.padding ?? 40),
|
|
283
|
+
0,
|
|
284
|
+
currentBottomPadding
|
|
285
|
+
);
|
|
258
286
|
positionStyles = getPositionStyles(currentPadding, currentBottomPadding);
|
|
259
287
|
setPosition(currentPosition);
|
|
260
288
|
},
|
|
261
289
|
show,
|
|
262
290
|
hide,
|
|
263
291
|
getConfig() {
|
|
264
|
-
return { ...config, position: currentPosition, opacity:
|
|
292
|
+
return { ...config, position: currentPosition, opacity: currentOpacity, imageHeight: currentImageHeight, padding: currentPadding };
|
|
265
293
|
}
|
|
266
294
|
};
|
|
267
295
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
// src/version.ts
|
|
2
|
-
var PKG_VERSION = true ? "1.
|
|
2
|
+
var PKG_VERSION = true ? "1.13.0" : "0.0.0-dev";
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
5
|
var POSITIONS = ["top-left", "top-right", "bottom-left", "bottom-right", "center"];
|
|
6
|
+
function clampOpacity(value, fallback = 0.5) {
|
|
7
|
+
if (!Number.isFinite(value)) return fallback;
|
|
8
|
+
return Math.max(0, Math.min(1, value));
|
|
9
|
+
}
|
|
10
|
+
function clampPx(value, floor, fallback) {
|
|
11
|
+
if (!Number.isFinite(value)) return fallback;
|
|
12
|
+
return Math.max(floor, value);
|
|
13
|
+
}
|
|
6
14
|
function getPositionStyles(padding, bottomPadding) {
|
|
7
15
|
return {
|
|
8
16
|
"top-left": `top:${padding}px;left:${padding}px;`,
|
|
@@ -18,11 +26,12 @@ function createWatermarkPlugin(config = {}) {
|
|
|
18
26
|
let dynamicTimer = null;
|
|
19
27
|
let showDelayTimer = null;
|
|
20
28
|
let currentPosition = config.position || "bottom-right";
|
|
21
|
-
|
|
29
|
+
let currentOpacity = clampOpacity(config.opacity ?? 0.5);
|
|
30
|
+
let currentVisible = false;
|
|
22
31
|
const fontSize = config.fontSize ?? 14;
|
|
23
|
-
let currentImageHeight = config.imageHeight ?? 40;
|
|
24
|
-
let currentPadding = config.padding ?? 10;
|
|
25
|
-
let currentBottomPadding = config.padding ?? 40;
|
|
32
|
+
let currentImageHeight = clampPx(config.imageHeight ?? 40, 1, 40);
|
|
33
|
+
let currentPadding = clampPx(config.padding ?? 10, 0, 10);
|
|
34
|
+
let currentBottomPadding = clampPx(config.padding ?? 40, 0, 40);
|
|
26
35
|
const dynamic = config.dynamic ?? false;
|
|
27
36
|
const dynamicInterval = config.dynamicInterval ?? 1e4;
|
|
28
37
|
const showDelay = config.showDelay ?? 0;
|
|
@@ -33,7 +42,7 @@ function createWatermarkPlugin(config = {}) {
|
|
|
33
42
|
const createElement = () => {
|
|
34
43
|
const el = document.createElement("div");
|
|
35
44
|
el.className = "sp-watermark sp-watermark--hidden";
|
|
36
|
-
el.style.cssText = `position:absolute;z-index:10;pointer-events:none;opacity:${
|
|
45
|
+
el.style.cssText = `position:absolute;z-index:10;pointer-events:none;visibility:${currentVisible ? "visible" : "hidden"};opacity:${currentOpacity};font-size:${fontSize}px;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,0.6);font-family:sans-serif;transition:all 0.5s ease;${positionStyles[currentPosition]}`;
|
|
37
46
|
el.setAttribute("data-position", currentPosition);
|
|
38
47
|
updateContent(el);
|
|
39
48
|
return el;
|
|
@@ -79,8 +88,7 @@ function createWatermarkPlugin(config = {}) {
|
|
|
79
88
|
}
|
|
80
89
|
});
|
|
81
90
|
element.setAttribute("data-position", position);
|
|
82
|
-
const
|
|
83
|
-
const visClass = isVisible ? " sp-watermark--visible" : " sp-watermark--hidden";
|
|
91
|
+
const visClass = currentVisible ? " sp-watermark--visible" : " sp-watermark--hidden";
|
|
84
92
|
element.className = `sp-watermark sp-watermark--${position}${visClass}${dynamic ? " sp-watermark--dynamic" : ""}`;
|
|
85
93
|
};
|
|
86
94
|
const randomizePosition = () => {
|
|
@@ -89,12 +97,16 @@ function createWatermarkPlugin(config = {}) {
|
|
|
89
97
|
setPosition(next);
|
|
90
98
|
};
|
|
91
99
|
const show = () => {
|
|
100
|
+
currentVisible = true;
|
|
92
101
|
if (!element) return;
|
|
102
|
+
element.style.visibility = "visible";
|
|
93
103
|
element.classList.remove("sp-watermark--hidden");
|
|
94
104
|
element.classList.add("sp-watermark--visible");
|
|
95
105
|
};
|
|
96
106
|
const hide = () => {
|
|
107
|
+
currentVisible = false;
|
|
97
108
|
if (!element) return;
|
|
109
|
+
element.style.visibility = "hidden";
|
|
98
110
|
element.classList.remove("sp-watermark--visible");
|
|
99
111
|
element.classList.add("sp-watermark--hidden");
|
|
100
112
|
};
|
|
@@ -148,11 +160,18 @@ function createWatermarkPlugin(config = {}) {
|
|
|
148
160
|
}
|
|
149
161
|
}
|
|
150
162
|
} else if (mutation.type === "attributes" && mutation.attributeName === "style") {
|
|
151
|
-
if (element)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
163
|
+
if (mutation.target !== element) continue;
|
|
164
|
+
const restore = [
|
|
165
|
+
["opacity", String(currentOpacity)],
|
|
166
|
+
["visibility", currentVisible ? "visible" : "hidden"],
|
|
167
|
+
["pointer-events", "none"],
|
|
168
|
+
["position", "absolute"],
|
|
169
|
+
["z-index", "10"]
|
|
170
|
+
];
|
|
171
|
+
for (const [prop, value] of restore) {
|
|
172
|
+
if (element.style.getPropertyValue(prop) !== value) {
|
|
173
|
+
element.style.setProperty(prop, value);
|
|
174
|
+
}
|
|
156
175
|
}
|
|
157
176
|
}
|
|
158
177
|
}
|
|
@@ -181,6 +200,10 @@ function createWatermarkPlugin(config = {}) {
|
|
|
181
200
|
}
|
|
182
201
|
});
|
|
183
202
|
const unsubEnded = api.on("playback:ended", () => {
|
|
203
|
+
if (showDelayTimer) {
|
|
204
|
+
clearTimeout(showDelayTimer);
|
|
205
|
+
showDelayTimer = null;
|
|
206
|
+
}
|
|
184
207
|
hide();
|
|
185
208
|
stopDynamic();
|
|
186
209
|
});
|
|
@@ -216,25 +239,30 @@ function createWatermarkPlugin(config = {}) {
|
|
|
216
239
|
},
|
|
217
240
|
setPosition,
|
|
218
241
|
setOpacity(value) {
|
|
219
|
-
|
|
242
|
+
currentOpacity = clampOpacity(value, currentOpacity);
|
|
243
|
+
if (element) element.style.opacity = String(currentOpacity);
|
|
220
244
|
},
|
|
221
245
|
setImageHeight(height) {
|
|
222
|
-
currentImageHeight =
|
|
246
|
+
currentImageHeight = clampPx(height, 1, currentImageHeight);
|
|
223
247
|
if (element) {
|
|
224
248
|
const img = element.querySelector("img");
|
|
225
249
|
if (img) img.style.maxHeight = `${currentImageHeight}px`;
|
|
226
250
|
}
|
|
227
251
|
},
|
|
228
252
|
setPadding(value) {
|
|
229
|
-
currentPadding =
|
|
230
|
-
currentBottomPadding =
|
|
253
|
+
currentPadding = clampPx(value, 0, currentPadding);
|
|
254
|
+
currentBottomPadding = clampPx(
|
|
255
|
+
Math.max(value, config.padding ?? 40),
|
|
256
|
+
0,
|
|
257
|
+
currentBottomPadding
|
|
258
|
+
);
|
|
231
259
|
positionStyles = getPositionStyles(currentPadding, currentBottomPadding);
|
|
232
260
|
setPosition(currentPosition);
|
|
233
261
|
},
|
|
234
262
|
show,
|
|
235
263
|
hide,
|
|
236
264
|
getConfig() {
|
|
237
|
-
return { ...config, position: currentPosition, opacity:
|
|
265
|
+
return { ...config, position: currentPosition, opacity: currentOpacity, imageHeight: currentImageHeight, padding: currentPadding };
|
|
238
266
|
}
|
|
239
267
|
};
|
|
240
268
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scarlett-player/watermark",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "Watermark Plugin for Scarlett Player - anti-piracy text/image overlay",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"tsup": "^8.5.1",
|
|
31
31
|
"typescript": "^5.3.0",
|
|
32
32
|
"vitest": "^1.6.0",
|
|
33
|
-
"@scarlett-player/core": "1.
|
|
33
|
+
"@scarlett-player/core": "1.14.0"
|
|
34
34
|
},
|
|
35
35
|
"keywords": [
|
|
36
36
|
"video",
|