Skip to content

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Quiz Maker — Documentation

PSC practice quiz video generator. Paste your questions, get a shareable video.


Table of Contents

  1. User Guide
  2. Architecture
  3. Customisation Reference

1. User Guide

Quick Start

  1. Open index.html in a Chromium-based browser (Chrome, Edge, Brave).

    Firefox is not recommended — MediaRecorder MP4 support varies.

  2. The textarea is pre-filled with sample questions. Replace the content with your own quiz data (see format below).
  3. Click Generate Video.
  4. Keep the tab in the foreground while the video generates.
  5. When complete, the video downloads automatically.

Quiz Data Format

The textarea expects a JavaScript array assigned to const quizData. Each item in the array is one question.

const quizData = [
  {
    q: "Your question text here",
    options: ["Option A", "Option B", "Option C", "Option D"],
    correct: 0, // zero-based index of the correct option
  },
  {
    q: "Second question",
    options: ["Apple", "Banana", "Mango", "Grape"],
    correct: 2, // "Mango" is correct
  },
];
Field Type Description
q string The question text. Supports Malayalam and other Unicode.
options array Exactly 4 answer choices.
correct number Index (0–3) of the correct answer.

The app uses eval() internally to parse the data, so standard JS array syntax is required.


Generation Process

Each question goes through four phases automatically:

Phase Duration What happens
Typing ~50 ms per character Question text types out character by character with keyboard sound
Options reveal 1 second All four answer boxes appear
Countdown 3 seconds A 3-2-1 timer ticks down with audio beeps
Answer reveal 2.5 seconds Correct answer box highlights green

The total video duration depends on question count and question length.


Output File

  • Format: MP4 (H.264/AVC) if the browser supports it, otherwise WebM (VP9)
  • Resolution: 1080 × 1920 pixels (vertical / portrait — optimised for Reels/Shorts)
  • Frame rate: 30 FPS
  • Bitrate: 8 Mbps video
  • Audio: Mixed stereo from the Web Audio API (keyboard clicks + countdown beeps)
  • Filename: Quiz_HD_<timestamp>.mp4

Tips & Best Practices

  • Keep the tab focused. The browser releases the screen wake lock when the tab is hidden. The app re-acquires it when you return, but brief tab switches can cause dropped frames.
  • Use short questions. Very long question strings will increase per-question duration because the typing phase scales with character count.
  • Malayalam font. The app loads Noto Sans Malayalam from Google Fonts. Make sure you have an internet connection when opening the page, otherwise fallback fonts may render incorrectly.
  • Batch generation. Paste all your questions in one run. Each generation is one continuous video, so it is more efficient than generating questions individually.

2. Architecture

Overview

Quiz Maker is a single-file web application (index.html). It has no build step, no server, and no external runtime dependencies beyond CDN-loaded fonts and Tailwind CSS. All video encoding happens entirely in the browser using standard Web APIs.

index.html
├── <head>         — Tailwind CSS (CDN), Noto Sans Malayalam (Google Fonts)
├── #ui-setup      — Input panel (textarea + generate button)
├── #ui-render     — Live preview canvas + status text
├── <style>        — Minimal custom CSS (layout, canvas, badge)
└── <script>       — All application logic (~300 lines)

Rendering Pipeline

quizData array
      │
      ▼
startQuizProcess()          ← async loop over each question
      │
      ├── Typing phase       → render() called per character  ─┐
      ├── Options phase      → render() called once            │  Canvas 2D API
      ├── Countdown phase    → render() called per tick        │  (1080 × 1920)
      └── Answer phase       → render() called once           ─┘
                                                                │
                                                                ▼
                                                    canvas.captureStream(30)
                                                    (live 30 FPS video track)

The render() function draws a complete frame on every call:

  1. White background fill
  2. QUESTION N / TOTAL label (top)
  3. Question text with word-wrapping (drawWrappedText)
  4. Option boxes with roundRect — highlighted green when ansIdx matches
  5. Countdown number (large red text) when timer !== null

Audio Engine

The audio engine is built entirely on the Web Audio API — no audio files are loaded.

Initialisation (initAudioEngine)

AudioContext
    ├── createMediaStreamDestination()   → streamDest  (routed into the recording)
    ├── Tick buffer (pre-generated)      → 100ms sine wave at 1100 Hz, decays in ~16ms
    └── Silent oscillator (gain 0.00001) → keeps the audio stream alive during silence

Keyboard Click Sound (triggerKeyClick)

Three synthesised layers fire simultaneously on each character:

Layer Frequencies Duration Purpose
Thock (body) Bandpass @ 420 Hz, Q=1.2 60 ms Low-mid punch of key bottom-out
Tick (snap) High-pass @ 3500 Hz 18 ms Crisp plastic registration click
Clack (echo) Bandpass @ 900 Hz, Q=2.0 12 ms, +4 ms delay Subtle key-up rebound

All three layers use white-noise bursts shaped by exponential gain envelopes.

Countdown Beep (triggerTick)

Pre-generated sine wave at 1100 Hz with a sharp exponential decay (~16 ms). Fires once per countdown second and is routed to both the speaker and the recording stream.


Recording Pipeline

canvas.captureStream(30)  →  VideoTrack  ─┐
                                           ├──  MediaStream  →  MediaRecorder
streamDest.stream         →  AudioTrack  ─┘
                                                    │
                                           ondataavailable
                                           (every 100ms chunk)
                                                    │
                                             recordedChunks[]
                                                    │
                                           mediaRecorder.stop()
                                                    │
                                           finalizeExport()
                                                    │
                                  Blob → Object URL → <a>.click() → download

MIME type selection:

// Preferred: native MP4 (Chrome 130+)
"video/mp4;codecs=avc1";

// Fallback: WebM VP9 (all Chromium versions)
"video/webm;codecs=vp9";

Wake lock: navigator.wakeLock.request('screen') is called at the start of generation and re-acquired on visibilitychange events to prevent the screen from sleeping mid-recording.


Data Flow Diagram

User pastes quizData
        │
        ▼
   startEngine()
        │
        ├── eval() parses quizData array
        ├── initAudioEngine()  →  AudioContext + streamDest
        ├── canvas.captureStream(30)  →  videoStream
        ├── MediaStream(video + audio tracks)
        ├── MediaRecorder.start(100ms)
        │
        ▼
  startQuizProcess()   [async loop]
        │
        ├── render()  per character / phase
        ├── triggerKeyClick()  per character
        ├── triggerTick()  per countdown tick
        │
        ▼
  mediaRecorder.stop()
        │
        ▼
  finalizeExport()
        │
        ├── Blob  ←  recordedChunks[]
        ├── Object URL
        └── Auto-download

3. Customisation Reference

What to change Where Variable / value
Typing speed startQuizProcess() await wait(50) — increase for slower typing
Click sound decay triggerKeyClick() now + 0.03 on the gain ramp
Click sound volume triggerKeyClick() thockGain.gain.setValueAtTime(0.35, ...)
Countdown beep pitch initAudioEngine() 1100 in Math.sin(2 * Math.PI * 1100 * t)
Answer highlight colour render() "#10b981" (Tailwind emerald-500)
Video bitrate startEngine() videoBitsPerSecond: 8000000
Canvas resolution HTML width="1080" height="1920" on <canvas>
Options pause duration startQuizProcess() await wait(1000) after options render
Answer reveal duration startQuizProcess() await wait(2500) after answer highlight

Releases

Packages

Contributors

Languages