Electron

for

Web developers

>

_

If you are like most web developers, you will often yourselfΒ building web apps 🌐, and sometimes mobile appsπŸ“±Β Β with the stack you possess.


Today we will be looking into extending that even further?

Let us dabble in Desktop Apps today πŸ˜‡

πŸ€”

WHAT IS ELECTRON?

If you didn't get that,


Electron is an open-source software framework developed and maintained by GitHub. It allows for the development of desktop GUI applications using web technologies

This should simplify it now πŸ˜ͺ

Why USE ELECTRON?

πŸ“ƒπŸ“„

πŸ—ž

πŸ—ž

❌

βœ…

SINGLE CODEBASE (Mac, Windows & Linux)

Why USE ELECTRON?

πŸ‘©πŸΌβ€πŸ’»πŸ‘¨πŸΎβ€πŸ’»πŸ‘¨πŸΎβ€πŸ’»πŸ‘©πŸΌβ€πŸ’»

🌐 πŸ‘¨πŸΎβ€πŸ’»Β 

❌

βœ…

LESS DEVELOPERS TO HIRE

Why USE ELECTRON?

πŸ’²πŸ’²πŸ’°πŸ’°πŸ’Έ

πŸ’²πŸ’²

❌

βœ…

SAVES YOU MONEY & TIME

And also, because these guys are using it 😎

As well as these guys 😎

Don't USE ELECTRONΒ when..

> BIG file sizes are a problem

Β 

Β 

Β 

> Electron apps are resource-intensive, thus apps built with Electron uses more CPU and RAM.

Β 

Β 

Β 

But wait 🀫, there is a workaround?

Slack rebuilt it's desktop app with Electron and found ways to make up for the RAM and CPU-intensive nature of it.

Β 

There is a workaround after all.

> npx create-electron-app twa-me-screenrecorder
> npm start

// To run the application and for testing
> npm run make

// To build/bundle it into a desktop application
// It bundles automatically according
//  to the platform you are currently on
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Twa Me πŸ“Έ - Screen Recording App for MacOS, Windows & Linux </title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/foundation-sites@6.6.3/dist/css/foundation.min.css" integrity="sha256-ogmFxjqiTMnZhxCqVmcqTvjfe1Y/ec4WaRj/aQPvn+I=" crossorigin="anonymous">
    <link rel="stylesheet" href="index.css">
    <script defer src="render.js"></script>
  </head>
  <body>
    <h1 class="title"> Twa Me πŸ“Έ </h1>
    <h2>
      Screen Recorder for MacOS, Windows & Linux
    </h2>
    <main>

      <video src="#" id="videoPreview"></video>
  
      <!-- Actions buttons -->
      <button id="startButton" type="button" class="button success -start">
        <div class="button__icon material-icons">play_arrow</div>
        <div class="button__text">
          Start
        </div>
      </button>
      <button id="stopButton" type="button" class="button alert -stop">
        <div class="button__icon material-icons">stop</div>
          <div class="button__text">
            Stop
          </div>
        </button>
      <button id="pauseButton" type="button" class="button warning -pause">
        <div class="button__icon material-icons">pause</div>
          <div class="button__text">
            Pause
          </div>
        </button>

        <p id="feedback" class="saveLocation"></p>
        
        <hr />
        <button id="selectVideoSource" class="button">
          <div class="button__icon material-icons">play_arrow</div>
            <div class="button-text">
              Choose a Video Source
            </div>
      </button>
    </main>
  </body>
</html>
// render.js file

// Getting buttons 
// Click to start recording
const startButton = document.getElementById('startButton');
startButton.onclick = e => {
    // Run only when a video source has been selected
    if (!mediaRecorder) {
        document.getElementById('feedback').innerText = 'Please select a video source first'
    }
    else {
        mediaRecorder.start();
        startButton.classList.add('warning');
        startButton.querySelector('.button__text').innerText = 'Recording';
    }
}

function changeText(newText) {
    querySelector('.button__text').innerText = newText;
}

function reset(buttonRef) {
    buttonRef.classList.remove('warning', 'secondary');
    switch (buttonRef) {
        case 'startButton':
            buttonRef.changeText('Start');
            break;
        case 'pauseButton':
            buttonRef.changeText('Pause');
            break;
        default:
            return
    }
}

// Click to stop the  recording
const stopButton = document.getElementById('stopButton');
stopButton.onclick = e => {
    // Run only when a video source has been selected
    if (!mediaRecorder) {
        document.getElementById('feedback').innerText = 'Please select a video source first'
    }
    else {
        mediaRecorder.stop();
        startButton.classList.add('secondary');
        reset(startButton);
    }
    
}

// Click to pause the recording
const pauseButton = document.getElementById('pauseButton');
pauseButton.onclick = e => {
    // Run only when a video source has been selected
    if (!mediaRecorder) {
        document.getElementById('feedback').innerText = 'Please select a video source first'
    }
    else if (mediaRecorder.state == "inactive") {
        document.getElementById('feedback').innerText = 'Please start recording first'
    }
    else if (mediaRecorder.state !== "paused") {
        mediaRecorder.pause();
        pauseButton.querySelector('.button__text').innerText = 'Resume';
        pauseButton.classList.remove('warning');
        pauseButton.classList.add('success');
    }
        else {
            mediaRecorder.resume();
            pauseButton.querySelector('.button__text').innerText = 'Pause';
            pauseButton.classList.add('warning');
            // reset(pauseButton);
        }
        
}


// Click to list all available video sources
const selectVideoSource = document.getElementById('selectVideoSource');
// Preview for video source
const previewVideo = document.querySelector('video');
selectVideoSource.onclick = getVideoSources;


const { desktopCapturer, remote } = require('electron');
const { Menu, dialog } = remote;
const { writeFile } = require('fs');
const { checkServerIdentity } = require('tls');

// Get the Available video sources
async function getVideoSources() {
    const inputSources = await desktopCapturer.getSources({
        types: ['window', 'screen']
    });

    // Populate the available video sources in a Menu/Dropdown
    const VideoSourceOptions = Menu.buildFromTemplate(
        inputSources.map(source => {
            return {
                label: source.name,
                click: () => selectSource(source),
            };
        })
    );


    VideoSourceOptions.popup();
}


// Empty array for populating recorded data
const chunks = [];
let mediaRecorder;

// Setup Options for MediaRecorder
const mediaRecorderOptions = { mimeType: 'video/webm; codecs=vp9' };
// MIME types (IANA media types)
// A media type (also known as a Multipurpose Internet Mail Extensions or MIME type) 
// Is a standard that indicates the nature and format of a document, file, or assortment of bytes.


// Select video source from the list
async function selectSource(source) {
    selectVideoSource.innerText = source.name;

    const constraints = {
        audio: false,
        video: {
            mandatory: {
                chromeMediaSource: 'desktop',
                chromeMediaSourceId: source.id,

            }
        }
    }

    // Create stream
    const stream = await navigator.mediaDevices.getUserMedia(constraints);

    // Set the video source object on the frontend to be the stream
    previewVideo.srcObject = stream;
    previewVideo.play();

    // ===== Start RECORDING ===== //


    // The MediaRecorder interface of the MediaStream Recording API provides functionality to easily record media. 
    // Source: https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder
    mediaRecorder = new MediaRecorder(stream, mediaRecorderOptions);

    // Some Events when recording starts
    mediaRecorder.ondataavailable = handleVideoDataExists;
    mediaRecorder.onstop = handleVideoDataStopped;

    
}


// Combine video chunks into one array
function handleVideoDataExists(e) {
    chunks.push(e.data);
}

const { mimeType } = mediaRecorderOptions;

// Save video file
async function handleVideoDataStopped(e) {
    const blob = new Blob(chunks, {
        type: mimeType
    });

    const buffer = Buffer.from(await blob.arrayBuffer());
    // https://stackoverflow.com/questions/11821096/what-is-the-difference-between-an-arraybuffer-and-a-blob
    // ArrayBuffer -  for manipulations to a Blob
    // The buffers module provides a way of handling streams of binary data.
    // Ref: https://www.w3schools.com/nodejs/ref_buffer.asp

    const { filePath } = await dialog.showSaveDialog({
        buttonLabel: "Save Video",
        defaultPath: `twa me ScreenRecording-01.webm`,
    });

    document.querySelector('.saveLocation').innerText = `video saved at  ${filePath}`;
    writeFile(filePath, buffer);
}



Download source code for the project here:

Β 

// To run live demo
> npm start


// To build into a desktop app
> npm run make

// ENJOY SCREEN RECORDING WITH TWA ME πŸ“Έ 

And before I forget, I built this project in at most 3 hours, was feeling drowsy so, pardon me for some of the sphagetti code πŸ˜‚Β 

ENJOY 🍸 

YOU MADE IT THROUGH THIS FAR.Β  πŸ‘ Β πŸŽ‰πŸ˜Ž

Resources

Made with Slides.com