Using JavaScript to create special effects in Theatre
by Chris Uehlinger (@Uehreka)
let theVid = document.getElementById('science');
let lastTapTime = Date.now();
let tapTimes = [];
// Initialize with tapTimes that would average to 110bpm
for(i=0; i < 5; i++){
tapTimes[i] = 0.545454545454;
}
window.addEventListener('keydown', () => {
let timeSinceLastTap = (Date.now() - lastTapTime) / 1000;
tapTimes.unshift(timeSinceLastTap);
tapTimes.pop();
let avgInterval = tapTimes.reduce((avg, interval) => {
return avg + interval/tapTimes.length
}, 0);
let beatsPerMinute = 60 / avgInterval;
theVid.playbackRate = beatsPerMinute / 110;
lastTapTime = Date.now();
});
const seriously = new Seriously();
let source = seriously.source('#remoteVideo'); // A <video> element
let filmgrain = seriously.effect('filmgrain');
let target = seriously.target('#target'); // A <canvas> element
// Send the source to the filmgrain filter
filmgrain.source = source;
// Render the output of the filmgrain filter to the canvas
target.source = filmgrain;
// Call this to start the effect
seriously.go(function (now) {
filmgrain.time = now / 200; // Helps randomize the filmgrain
});
const seriously = new Seriously();
let source = seriously.source('camera'); // The user's webcam feed
let filmgrain = seriously.effect('filmgrain');
let target = seriously.target('#target'); // A <canvas> element
// Send the webcam feed to the filmgrain filter
filmgrain.source = source;
// Render the output of the filmgrain filter to the canvas
target.source = filmgrain;
// Call this to start the effect
seriously.go(function (now) {
filmgrain.time = now / 200; // Helps randomize the filmgrain
});
let playlist = [
{
"description": "Act 1 Intro",
"type": "VIDEO",
"href": "/act-1-intro/intro.mp4"
},
{
"description": "Indianapolis House",
"type": "PAGE",
"href":"/first-act/index.html"
},
{
"description": "Switch to Church",
"type": "GO"
},
{
"description": "Switch Back to Indianapolis House",
"type": "GO"
},
{
"description": "Turn Off Projection",
"type": "OFF"
},
{
"description": "Act 1->2 Transition",
"type": "PAGE",
"href": "/1-2-transition/index.html"
},
{
"description": "Turn off projector",
"type": "OFF"
},
{
"description": "Voiceover 1",
"type": "PAGE",
"href": "/act-2-voiceovers/vo1.html"
},
{
"description": "Live Feed",
"type": "LIVE",
"luma": false
},
{
"description": "Voiceover 3",
"type": "VIDEO",
"href": "/act-2-voiceovers/final-cuts/vo-3.mp4"
},
{
"description": "Voiceover 4",
"type": "VIDEO",
"href": "/act-2-voiceovers/final-cuts/vo4.mp4"
},
{
"description": "Voiceover 5",
"type": "VIDEO",
"href": "/act-2-voiceovers/final-cuts/vo-5.mp4"
},
{
"description": "Voiceover 6",
"type": "VIDEO",
"href": "/act-2-voiceovers/final-cuts/vo6.mp4"
},
{
"description": "Voiceover 7",
"type": "VIDEO",
"href": "/act-2-voiceovers/final-cuts/vo7.mp4"
},
{
"description": "Voiceover 8",
"type": "VIDEO",
"href": "/act-2-voiceovers/final-cuts/vo8.mp4"
},
{
"description": "Voiceover 9",
"type": "VIDEO",
"href": "/act-2-voiceovers/final-cuts/vo9.mp4"
},
{
"description": "Voiceover 10",
"type": "VIDEO",
"href": "/act-2-voiceovers/final-cuts/vo10.mp4"
},
{
"description": "Voiceover 11",
"type": "VIDEO",
"href": "/act-2-voiceovers/final-cuts/vo11.mp4"
},
{
"description": "Voiceover 12",
"type": "VIDEO",
"href": "/act-2-voiceovers/final-cuts/vo12.mp4"
},
{
"description": "Voiceover 13",
"type": "VIDEO",
"href": "/act-2-voiceovers/final-cuts/vo13.mp4"
},
{
"description": "Live Feed",
"type": "LIVE",
"luma": true
},
{
"description": "End Live Feed",
"type": "OFF"
},
{
"description": "Act 3 Intro",
"type": "VIDEO",
"href": "/act-3/act-3-intro.mp4"
},
{
"description": "Turn off projector",
"type": "OFF"
},
{
"description": "Act 3 Outro",
"type": "FINAL_MONTAGE",
},
{
"description": "Turn Off Projector",
"type": "OFF"
}
];
$scope.sendStage = (cue) => {
$scope.model.currentStaged = cue
socket.emit('staging:control', cue);
}
$scope.sendCue = (cue) => {
$scope.model.currentPlaying = cue;
$scope.model.currentStaged = null;
socket.emit('play:control', cue);
}
$scope.sendRefresh = () => {
socket.emit('refresh:control', {});
}
$scope.bail = () => {
$scope.sendCue({
description: 'BAILED',
type: 'OFF'
});
};
io.on('connection', function (socket) {
socket.on('staging:control', function (data) {
io.emit('staging:display', data);
});
socket.on('play:control', function (data) {
io.emit('play:display', data);
});
socket.on('refresh:control', function (data) {
io.emit('refresh:display', data);
});
});
// First, find out which display this is
const urlParams = new URLSearchParams(window.location.search);
const displayId = +urlParams.get('id');
if(displayId === 0) {
$('body').append(`<iframe class="live-feed-frame hidden" allow="microphone; camera" src="https://6836ac72.ngrok.io/index.html?host=true"></iframe>`);
}
let staging = null;
let playing = null;
var socket = io.connect(`https://${location.host}`);
socket.on('staging:display', function (cue) {
console.log('staging', cue);
staging = cue;
if($('.staging').length > 0){
if($('.staging').hasClass('live-feed-frame')) {
$('.staging').removeClass('staging').addClass('hidden');
} else {
$('.staging').remove();
}
}
if(displayId === 0) {
switch (cue.type) {
case TYPES.VIDEO:
$('.cue-bag').append(`<video class="staging" src="https://${location.host}${cue.href}" muted></video>`);
break;
case TYPES.PAGE:
$('.cue-bag').append(`<iframe class="staging" src="https://${location.host}${cue.href}"></iframe>`);
break;
case TYPES.GO: break;
case TYPES.LIVE:
$('.live-feed-frame').removeClass('hidden').addClass('staging');
$('.live-feed-frame')[0].contentWindow.postMessage(JSON.stringify({ command: 'STOP' }), '*');
break;
}
}
switch(cue.type){
case TYPES.OFF: break;
case TYPES.FINAL_MONTAGE:
$('.cue-bag').append(`<iframe class="staging" src="https://${location.host}/act-3-final/index.html?displayid=${displayId}" muted></iframe>`);
break;
}
});
socket.on('play:display', function (cue) {
console.log('play', cue);
playing = cue;
staging = null;
if(displayId === 0) {
switch (cue.type) {
case TYPES.VIDEO:
$('.playing').removeClass('playing').addClass('done');
$('.staging').removeClass('staging').addClass('playing');
$('.playing')[0].play();
$('.playing').on('ended', e => {
$('.playing').remove();
});
break;
case TYPES.PAGE:
$('.playing').removeClass('playing').addClass('done');
$('.staging').removeClass('staging').addClass('playing');
$('.playing')[0].contentWindow.postMessage(JSON.stringify({ command: 'START' }), '*');
break;
case TYPES.GO:
$('.playing')[0].contentWindow.postMessage(JSON.stringify({ command: 'NEXT' }), '*');
break;
case TYPES.LIVE:
$('.playing').removeClass('playing').addClass('done');
$('.staging').removeClass('staging').addClass('playing');
$('.playing')[0].contentWindow.postMessage(JSON.stringify({ command: 'START', luma: cue.luma }), '*');
break;
}
}
switch(cue.type){
case TYPES.OFF:
$('.playing').removeClass('playing').addClass('done');
break;
case TYPES.FINAL_MONTAGE:
$('.playing').removeClass('playing').addClass('done');
$('.staging').removeClass('staging').addClass('playing');
$('.playing')[0].contentWindow.postMessage(JSON.stringify({ command: 'START' }), '*');
break;
}
if($('.done').length > 0){
if($('.done').hasClass('live-feed-frame')) {
$('.done').removeClass('done').addClass('hidden');
} else {
$('.done').remove();
}
}
});
socket.on('refresh:display', function () {
location.reload();
});
let constraints = {
audio: true,
video: {
width: { min: 1024, ideal: 1280, max: 1920 },
height: { min: 776, ideal: 720, max: 1080 },
facingMode: 'user'
}
};
let stream = await navigator.mediaDevices.getUserMedia(constraints);
let videoEl = document.getElementById('myVideo');
videoEl.srcObject = stream;
// Old (deprecated) syntax:
// video.src = URL.createObjectURL(stream);
video {
animation: video-effects 5s infinite;
}
@keyframes video-effects {
0% {
filter: blur(0px) hue-rotate(0deg);
transform: rotate(0deg);
}
50% {
filter: blur(10px) hue-rotate(180deg);
}
100% {
filter: blur(0px) hue-rotate(360deg);
transform: rotate(360deg);
}
}