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);
  }
}