Watermark QR code over webcam feeds to deter screen recording
This commit is contained in:
parent
a70d6d54b3
commit
f802de88ce
6
package-lock.json
generated
6
package-lock.json
generated
|
@ -10,6 +10,7 @@
|
|||
"dependencies": {
|
||||
"floating-vue": "^2.0.0-beta.24",
|
||||
"interactjs": "^1.10.18",
|
||||
"qrcodejs": "github:danielgjackson/qrcodejs",
|
||||
"vue": "^3.3.4",
|
||||
"vue-mention": "^2.0.0-alpha.3",
|
||||
"vue3-emoji-picker": "^1.1.7",
|
||||
|
@ -1680,6 +1681,11 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcodejs": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "git+ssh://git@github.com/danielgjackson/qrcodejs.git#86770ec12f0f9abee8728fc9018ab7bd0949f4bc",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
"dependencies": {
|
||||
"floating-vue": "^2.0.0-beta.24",
|
||||
"interactjs": "^1.10.18",
|
||||
"qrcodejs": "github:danielgjackson/qrcodejs",
|
||||
"vue": "^3.3.4",
|
||||
"vue-mention": "^2.0.0-alpha.3",
|
||||
"vue3-emoji-picker": "^1.1.7",
|
||||
|
|
43
src/App.vue
43
src/App.vue
|
@ -19,6 +19,7 @@ import LocalStorage from './lib/LocalStorage';
|
|||
import VideoFlag from './lib/VideoFlag';
|
||||
import StatusMessage from './lib/StatusMessage';
|
||||
import { SoundEffects, DefaultSounds } from './lib/sounds';
|
||||
import WatermarkImage from './lib/watermark';
|
||||
|
||||
// WebRTC configuration.
|
||||
const configuration = {
|
||||
|
@ -181,6 +182,10 @@ export default {
|
|||
rememberExpresslyClosed: true, // remember cams we expressly closed
|
||||
autoMuteWebcams: false, // auto-mute other cameras' audio channels
|
||||
|
||||
// My watermark image for screen recording protection.
|
||||
// Set after login in setWatermark.
|
||||
watermark: null,
|
||||
|
||||
// Who all is watching me? map of users.
|
||||
watching: {},
|
||||
|
||||
|
@ -1630,6 +1635,10 @@ export default {
|
|||
onLoggedIn() {
|
||||
// Called after the first 'me' is received from the chat server, e.g. once per login.
|
||||
|
||||
// Load our watermark image.
|
||||
this.webcam.watermark = WatermarkImage(this.username);
|
||||
this.ChatClient(`Watermark image created: <img src="${this.webcam.watermark}" width="120">`);
|
||||
|
||||
// Do we auto-broadcast our camera?
|
||||
if (this.webcam.autoshare) {
|
||||
this.startVideo({ force: true });
|
||||
|
@ -4709,20 +4718,36 @@ export default {
|
|||
<!-- Video Feeds-->
|
||||
|
||||
<!-- My video -->
|
||||
<VideoFeed v-show="webcam.active" :local-video="true" :username="username"
|
||||
:popped-out="WebRTC.poppedOut[username]" :is-explicit="webcam.nsfw" :is-muted="webcam.muted"
|
||||
:is-source-muted="webcam.muted" @mute-video="muteMe()" @popout="popoutVideo"
|
||||
<VideoFeed v-show="webcam.active"
|
||||
:local-video="true"
|
||||
:username="username"
|
||||
:popped-out="WebRTC.poppedOut[username]"
|
||||
:is-explicit="webcam.nsfw"
|
||||
:is-muted="webcam.muted"
|
||||
:is-source-muted="webcam.muted"
|
||||
:watermark-image="webcam.watermark"
|
||||
@mute-video="muteMe()"
|
||||
@popout="popoutVideo"
|
||||
@open-profile="showProfileModal"
|
||||
@set-volume="setVideoVolume">
|
||||
</VideoFeed>
|
||||
|
||||
<!-- Others' videos -->
|
||||
<VideoFeed v-for="(stream, username) in WebRTC.streams" v-bind:key="username" :username="username"
|
||||
:popped-out="WebRTC.poppedOut[username]" :is-explicit="isUsernameCamNSFW(username)"
|
||||
:is-source-muted="isSourceMuted(username)" :is-muted="isMuted(username)"
|
||||
:is-watching-me="isWatchingMe(username)" :is-frozen="WebRTC.frozenStreamDetected[username]"
|
||||
@reopen-video="openVideoByUsername" @mute-video="muteVideo" @popout="popoutVideo"
|
||||
@close-video="expresslyCloseVideo" @set-volume="setVideoVolume"
|
||||
<VideoFeed v-for="(stream, username) in WebRTC.streams"
|
||||
v-bind:key="username"
|
||||
:username="username"
|
||||
:popped-out="WebRTC.poppedOut[username]"
|
||||
:is-explicit="isUsernameCamNSFW(username)"
|
||||
:is-source-muted="isSourceMuted(username)"
|
||||
:is-muted="isMuted(username)"
|
||||
:is-watching-me="isWatchingMe(username)"
|
||||
:is-frozen="WebRTC.frozenStreamDetected[username]"
|
||||
:watermark-image="webcam.watermark"
|
||||
@reopen-video="openVideoByUsername"
|
||||
@mute-video="muteVideo"
|
||||
@popout="popoutVideo"
|
||||
@close-video="expresslyCloseVideo"
|
||||
@set-volume="setVideoVolume"
|
||||
@open-profile="showProfileModal">
|
||||
</VideoFeed>
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ export default {
|
|||
isSourceMuted: Boolean, // camera is muted on the broadcaster's end
|
||||
isWatchingMe: Boolean, // other video is watching us back
|
||||
isFrozen: Boolean, // video is detected as frozen
|
||||
watermarkImage: Image, // watermark image to overlay (nullable)
|
||||
},
|
||||
components: {
|
||||
Slider,
|
||||
|
@ -102,7 +103,13 @@ export default {
|
|||
'popped-out': poppedOut,
|
||||
'popped-in': !poppedOut,
|
||||
}" @mouseover="mouseOver = true" @mouseleave="mouseOver = false">
|
||||
<video class="feed" :id="videoID" autoplay :muted="localVideo" playsinline></video>
|
||||
<video class="feed" :id="videoID" autoplay disablepictureinpicture :muted="localVideo" playsinline></video>
|
||||
|
||||
<!-- Watermark layer -->
|
||||
<div v-if="watermarkImage">
|
||||
<img :src="watermarkImage" class="watermark">
|
||||
<img :src="watermarkImage" class="corner-watermark seethru invert-color">
|
||||
</div>
|
||||
|
||||
<!-- Caption -->
|
||||
<div class="caption" :class="textColorClass">
|
||||
|
@ -187,4 +194,29 @@ video {
|
|||
.seethru {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
/* Watermark image */
|
||||
.watermark {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
width: 40%;
|
||||
height: 40%;
|
||||
opacity: 0.02;
|
||||
}
|
||||
.corner-watermark {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
bottom: 4px;
|
||||
width: 20%;
|
||||
min-width: 32px;
|
||||
min-height: 32px;
|
||||
max-height: 20%;
|
||||
}
|
||||
.invert-color {
|
||||
filter: invert(100%);
|
||||
}
|
||||
</style>
|
||||
|
|
27
src/lib/watermark.js
Normal file
27
src/lib/watermark.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
import QrCode from 'qrcodejs';
|
||||
|
||||
// WatermarkImage outputs a QR code containing watermark data about the current user.
|
||||
//
|
||||
// To help detect when someone has screen recorded and shared it, and being able to know who/when/etc.
|
||||
function WatermarkImage(username) {
|
||||
let now = new Date();
|
||||
let dateString = [
|
||||
now.getFullYear(),
|
||||
('0' + (now.getMonth()+1)).slice(-2),
|
||||
('0' + (now.getDate())).slice(-2),
|
||||
].join('-');
|
||||
|
||||
let fields = [
|
||||
window.location.hostname,
|
||||
username,
|
||||
dateString,
|
||||
].join(' ');
|
||||
|
||||
console.error("watermark message:", fields);
|
||||
|
||||
const matrix = QrCode.generate(fields);
|
||||
const uri = QrCode.render('svg-uri', matrix);
|
||||
return uri;
|
||||
}
|
||||
|
||||
export default WatermarkImage;
|
Loading…
Reference in New Issue
Block a user