← back to demon-of-fire__accessable-studio

Function bodies 189 total

All specs Real LLM only Function bodies
flipHorizontal function · javascript · L214-L220 (7 LOC)
src/js/photoeditor.js
  function flipHorizontal() {
    if (!originalImage) return;
    saveState();
    flipH = !flipH;
    drawImage();
    Accessibility.announce(flipH ? 'Flipped horizontally' : 'Horizontal flip removed');
  }
flipVertical function · javascript · L223-L229 (7 LOC)
src/js/photoeditor.js
  function flipVertical() {
    if (!originalImage) return;
    saveState();
    flipV = !flipV;
    drawImage();
    Accessibility.announce(flipV ? 'Flipped vertically' : 'Vertical flip removed');
  }
resize function · javascript · L232-L239 (8 LOC)
src/js/photoeditor.js
  function resize(newWidth, newHeight) {
    if (!originalImage) return;
    saveState();
    canvas.width = newWidth;
    canvas.height = newHeight;
    drawImage();
    Accessibility.announce(`Image resized to ${newWidth} by ${newHeight} pixels`);
  }
setAdjustment function · javascript · L242-L247 (6 LOC)
src/js/photoeditor.js
  function setAdjustment(name, value) {
    if (name in adjustments) {
      adjustments[name] = parseInt(value);
      drawImage();
    }
  }
applyPreset function · javascript · L250-L281 (32 LOC)
src/js/photoeditor.js
  function applyPreset(presetName) {
    saveState();
    switch (presetName) {
      case 'none':
        resetAdjustments();
        break;
      case 'grayscale':
        adjustments = { brightness: 0, contrast: 10, saturation: -100, sharpness: 0 };
        break;
      case 'sepia':
        adjustments = { brightness: 10, contrast: -10, saturation: -60, sharpness: 0 };
        break;
      case 'vintage':
        adjustments = { brightness: 15, contrast: -15, saturation: -30, sharpness: 0 };
        break;
      case 'warm':
        adjustments = { brightness: 10, contrast: 5, saturation: 15, sharpness: 0 };
        break;
      case 'cool':
        adjustments = { brightness: 0, contrast: 5, saturation: -10, sharpness: 0 };
        break;
      case 'vivid':
        adjustments = { brightness: 5, contrast: 20, saturation: 50, sharpness: 10 };
        break;
      case 'noir':
        adjustments = { brightness: -5, contrast: 40, saturation: -100, sharpness: 5 };
        break;
   
resetAdjustments function · javascript · L284-L290 (7 LOC)
src/js/photoeditor.js
  function resetAdjustments() {
    adjustments = { brightness: 0, contrast: 0, saturation: 0, sharpness: 0 };
    rotation = 0;
    flipH = false;
    flipV = false;
    updateAdjustmentSliders();
  }
updateAdjustmentSliders function · javascript · L293-L303 (11 LOC)
src/js/photoeditor.js
  function updateAdjustmentSliders() {
    for (const [name, value] of Object.entries(adjustments)) {
      const slider = document.getElementById(`photo-${name}`);
      const display = document.getElementById(`photo-${name}-val`);
      if (slider) {
        slider.value = value;
        slider.setAttribute('aria-valuetext', String(value));
      }
      if (display) display.textContent = value;
    }
  }
Powered by Repobility — scan your code at https://repobility.com
saveImage function · javascript · L306-L326 (21 LOC)
src/js/photoeditor.js
  async function saveImage(filePath, format = 'png') {
    if (!canvas) return;
    const mimeTypes = {
      png: 'image/png',
      jpg: 'image/jpeg',
      jpeg: 'image/jpeg',
      webp: 'image/webp',
      bmp: 'image/bmp',
    };
    const mime = mimeTypes[format] || 'image/png';

    return new Promise((resolve) => {
      canvas.toBlob((blob) => {
        const reader = new FileReader();
        reader.onload = () => {
          resolve(reader.result);
        };
        reader.readAsArrayBuffer(blob);
      }, mime, 0.95);
    });
  }
init function · javascript · L329-L341 (13 LOC)
src/js/photoeditor.js
  function init() {
    ['brightness', 'contrast', 'saturation', 'sharpness'].forEach(name => {
      const slider = document.getElementById(`photo-${name}`);
      if (slider) {
        slider.addEventListener('input', () => {
          setAdjustment(name, slider.value);
          const display = document.getElementById(`photo-${name}-val`);
          if (display) display.textContent = slider.value;
          slider.setAttribute('aria-valuetext', slider.value);
        });
      }
    });
  }
detectBackgroundColors function · javascript · L348-L421 (74 LOC)
src/js/photoeditor.js
  function detectBackgroundColors() {
    if (!originalImage) return null;
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = originalImage.width;
    tempCanvas.height = originalImage.height;
    const tempCtx = tempCanvas.getContext('2d');
    tempCtx.drawImage(originalImage, 0, 0);

    const w = tempCanvas.width;
    const h = tempCanvas.height;

    // Get ALL pixel data at once (fast), then sample edges from the array
    const allData = tempCtx.getImageData(0, 0, w, h).data;
    const edgePixels = [];
    const edgeSize = Math.max(3, Math.floor(Math.min(w, h) * 0.02));

    // Top and bottom edges
    for (let x = 0; x < w; x += 2) {
      for (let y = 0; y < edgeSize; y++) {
        const i = (y * w + x) * 4;
        edgePixels.push({ r: allData[i], g: allData[i + 1], b: allData[i + 2] });
      }
      for (let y = h - edgeSize; y < h; y++) {
        const i = (y * w + x) * 4;
        edgePixels.push({ r: allData[i], g: allData[i + 1], b: allData[i
detectBackgroundColor function · javascript · L424-L427 (4 LOC)
src/js/photoeditor.js
  function detectBackgroundColor() {
    const colors = detectBackgroundColors();
    return colors && colors.length > 0 ? colors[0] : null;
  }
isBackgroundMulti function · javascript · L430-L437 (8 LOC)
src/js/photoeditor.js
  function isBackgroundMulti(r, g, b, bgColors, tol) {
    const tolSq = tol * tol;
    for (const bg of bgColors) {
      const dr = r - bg.r, dg = g - bg.g, db = b - bg.b;
      if (dr * dr + dg * dg + db * db < tolSq) return true;
    }
    return false;
  }
isBackground function · javascript · L440-L443 (4 LOC)
src/js/photoeditor.js
  function isBackground(r, g, b, bg, tol) {
    const dr = r - bg.r, dg = g - bg.g, db = b - bg.b;
    return dr * dr + dg * dg + db * db < tol * tol;
  }
removeBackground function · javascript · L446-L549 (104 LOC)
src/js/photoeditor.js
  async function removeBackground(tolerance = 70) {
    if (!originalImage || !ctx) {
      Accessibility.announce('No image loaded');
      return 'No image loaded.';
    }
    saveState();

    // Draw raw image (no adjustments) so we work on clean pixels
    const w = originalImage.width;
    const h = originalImage.height;
    canvas.width = w;
    canvas.height = h;
    drawImageRaw();

    const bgColors = detectBackgroundColors();
    if (!bgColors || bgColors.length === 0) return 'Could not detect background color.';

    const imageData = ctx.getImageData(0, 0, w, h);
    const data = imageData.data;
    let removed = 0;
    const total = data.length / 4;

    // First pass: identify background pixels using multi-color matching
    const isBg = new Uint8Array(w * h);
    for (let i = 0; i < total; i++) {
      const idx = i * 4;
      if (isBackgroundMulti(data[idx], data[idx + 1], data[idx + 2], bgColors, tolerance)) {
        isBg[i] = 1;
      }
    }

    // Flood fill fro
boxBlur function · javascript · L552-L598 (47 LOC)
src/js/photoeditor.js
  function boxBlur(srcData, w, h, radius) {
    const dst = new Uint8ClampedArray(srcData.length);
    const size = radius * 2 + 1;
    const area = size * size;

    // Horizontal pass
    const temp = new Uint8ClampedArray(srcData.length);
    for (let y = 0; y < h; y++) {
      for (let x = 0; x < w; x++) {
        let rSum = 0, gSum = 0, bSum = 0, aSum = 0;
        for (let dx = -radius; dx <= radius; dx++) {
          const sx = Math.min(w - 1, Math.max(0, x + dx));
          const idx = (y * w + sx) * 4;
          rSum += srcData[idx];
          gSum += srcData[idx + 1];
          bSum += srcData[idx + 2];
          aSum += srcData[idx + 3];
        }
        const idx = (y * w + x) * 4;
        temp[idx] = rSum / size;
        temp[idx + 1] = gSum / size;
        temp[idx + 2] = bSum / size;
        temp[idx + 3] = aSum / size;
      }
    }

    // Vertical pass
    for (let y = 0; y < h; y++) {
      for (let x = 0; x < w; x++) {
        let rSum = 0, gSum = 0, bSum = 0, aSum 
Source: Repobility analyzer · https://repobility.com
blurBackground function · javascript · L601-L689 (89 LOC)
src/js/photoeditor.js
  async function blurBackground(tolerance = 70, blurRadius = 30) {
    if (!originalImage || !ctx) {
      Accessibility.announce('No image loaded');
      return 'No image loaded.';
    }
    saveState();

    const w = originalImage.width;
    const h = originalImage.height;
    canvas.width = w;
    canvas.height = h;
    drawImageRaw();

    // For large images, use CSS blur on a temp canvas (fast, hardware-accelerated)
    const sharpData = ctx.getImageData(0, 0, w, h);
    const bgColors = detectBackgroundColors();
    if (!bgColors || bgColors.length === 0) return 'Could not detect background color.';

    // Build background mask via flood fill from edges
    const mask = new Uint8Array(w * h);
    for (let i = 0; i < w * h; i++) {
      const idx = i * 4;
      mask[i] = isBackgroundMulti(sharpData.data[idx], sharpData.data[idx + 1], sharpData.data[idx + 2], bgColors, tolerance) ? 1 : 0;
    }

    const bgMask = new Uint8Array(w * h);
    const queue = [];
    for (let x = 0;
parseRegion function · javascript · L696-L716 (21 LOC)
src/js/photoeditor.js
  function parseRegion(description) {
    const d = description.toLowerCase();
    let x = 0.25, y = 0.2, w = 0.5, h = 0.6;

    if (d.includes('left')) { x = 0; w = 0.4; }
    else if (d.includes('right')) { x = 0.6; w = 0.4; }
    else if (d.includes('center') || d.includes('middle')) { x = 0.2; w = 0.6; }

    if (d.includes('top')) { y = 0; h = 0.4; }
    else if (d.includes('bottom')) { y = 0.6; h = 0.4; }

    if (d.includes('top') && d.includes('left')) { x = 0; y = 0; w = 0.4; h = 0.4; }
    if (d.includes('top') && d.includes('right')) { x = 0.6; y = 0; w = 0.4; h = 0.4; }
    if (d.includes('bottom') && d.includes('left')) { x = 0; y = 0.6; w = 0.4; h = 0.4; }
    if (d.includes('bottom') && d.includes('right')) { x = 0.6; y = 0.6; w = 0.4; h = 0.4; }

    if (d.includes('small') || d.includes('tiny')) { w *= 0.5; h *= 0.5; x += w * 0.25; y += h * 0.25; }
    else if (d.includes('large') || d.includes('big')) { w = Math.min(w * 1.4, 0.9); h = Math.min(h * 1.4, 0.9); }

    re
removeRegion function · javascript · L719-L858 (140 LOC)
src/js/photoeditor.js
  async function removeRegion(description) {
    if (!originalImage || !ctx) {
      Accessibility.announce('No image loaded');
      return 'No image loaded. Open an image first.';
    }
    saveState();

    canvas.width = originalImage.width;
    canvas.height = originalImage.height;
    drawImageRaw();

    const region = parseRegion(description);
    const cw = canvas.width;
    const ch = canvas.height;
    const rx = Math.round(region.x * cw);
    const ry = Math.round(region.y * ch);
    const rw = Math.round(region.w * cw);
    const rh = Math.round(region.h * ch);

    const imageData = ctx.getImageData(0, 0, cw, ch);
    const data = imageData.data;

    // Build a border sampling band (pixels just outside the region)
    const bandSize = Math.max(8, Math.floor(Math.min(rw, rh) * 0.15));

    // Helper to get a pixel safely
    const getPixel = (px, py) => {
      const cx2 = Math.max(0, Math.min(cw - 1, px));
      const cy2 = Math.max(0, Math.min(ch - 1, py));
      const 
smartRemove function · javascript · L868-L1092 (225 LOC)
src/js/photoeditor.js
  async function smartRemove(description, coords) {
    if (!originalImage || !ctx) {
      Accessibility.announce('No image loaded');
      return 'No image loaded.';
    }
    saveState();

    const w = canvas.width;
    const h = canvas.height;
    drawImageRaw();

    const imageData = ctx.getImageData(0, 0, w, h);
    const data = imageData.data;

    // Use precise coordinates if provided (percentages 0-100), otherwise fall back to text parsing
    let region;
    const hasPreciseCoords = coords && coords.x !== undefined && coords.y !== undefined;
    if (hasPreciseCoords) {
      region = {
        x: (coords.x || 0) / 100,
        y: (coords.y || 0) / 100,
        w: (coords.w || 20) / 100,
        h: (coords.h || 20) / 100,
      };
    } else {
      region = parseRegion(description);
    }
    const rx = Math.max(0, Math.round(region.x * w));
    const ry = Math.max(0, Math.round(region.y * h));
    const rw = Math.min(w - rx, Math.round(region.w * w));
    const rh = Math.
insertImage function · javascript · L1105-L1149 (45 LOC)
src/js/photoeditor.js
  async function insertImage(imagePath, position = 'center', scale = 0.3, opacity = 100) {
    if (!originalImage || !ctx) {
      Accessibility.announce('No image loaded');
      return 'No image loaded. Open a base image first.';
    }
    saveState();

    return new Promise((resolve) => {
      const overlay = new Image();
      overlay.onload = () => {
        const cw = canvas.width;
        const ch = canvas.height;

        // Calculate overlay size
        let ow = Math.round(overlay.width * scale);
        let oh = Math.round(overlay.height * scale);
        // Clamp to canvas size
        if (ow > cw * 0.95) { const r = (cw * 0.95) / ow; ow = Math.round(ow * r); oh = Math.round(oh * r); }
        if (oh > ch * 0.95) { const r = (ch * 0.95) / oh; ow = Math.round(ow * r); oh = Math.round(oh * r); }

        // Calculate position
        const pos = (position || 'center').toLowerCase();
        let x = (cw - ow) / 2, y = (ch - oh) / 2; // default center
        if (pos.includes
insertImageFromPicker function · javascript · L1154-L1166 (13 LOC)
src/js/photoeditor.js
  async function insertImageFromPicker(position = 'center', scale = 0.3) {
    if (!window.api) {
      Accessibility.announce('Insert image requires the desktop application');
      return;
    }
    const result = await window.api.showOpenDialog({
      title: 'Select Image to Insert',
      filters: [{ name: 'Images', extensions: ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'svg'] }],
      properties: ['openFile'],
    });
    if (result.canceled || result.filePaths.length === 0) return;
    return insertImage(result.filePaths[0], position, scale, 100);
  }
drawRect function · javascript · L1180-L1213 (34 LOC)
src/js/photoeditor.js
  async function drawRect(position, widthPct, heightPct, color, borderRadius = 0) {
    if (!originalImage || !ctx) return 'No image loaded.';
    saveState();
    drawImage(); // make sure canvas is current

    const cw = canvas.width, ch = canvas.height;
    const rw = Math.round((widthPct / 100) * cw);
    const rh = Math.round((heightPct / 100) * ch);

    const region = parseRegion(position || 'center');
    const rx = Math.round((region.x + region.w / 2) * cw - rw / 2);
    const ry = Math.round((region.y + region.h / 2) * ch - rh / 2);

    ctx.fillStyle = color || '#FFD700';
    if (borderRadius > 0) {
      ctx.beginPath();
      ctx.moveTo(rx + borderRadius, ry);
      ctx.lineTo(rx + rw - borderRadius, ry);
      ctx.quadraticCurveTo(rx + rw, ry, rx + rw, ry + borderRadius);
      ctx.lineTo(rx + rw, ry + rh - borderRadius);
      ctx.quadraticCurveTo(rx + rw, ry + rh, rx + rw - borderRadius, ry + rh);
      ctx.lineTo(rx + borderRadius, ry + rh);
      ctx.quadraticCurveTo
drawTextOnPhoto function · javascript · L1224-L1265 (42 LOC)
src/js/photoeditor.js
  async function drawTextOnPhoto(text, position = 'center', fontSize = 5, color = '#ffffff', bgColor = '', font = 'Arial') {
    if (!originalImage || !ctx) return 'No image loaded.';
    saveState();
    drawImage();

    const cw = canvas.width, ch = canvas.height;
    const fontPx = Math.round((fontSize / 100) * ch);
    ctx.font = `bold ${fontPx}px ${font}`;
    ctx.textBaseline = 'middle';
    ctx.textAlign = 'center';

    const region = parseRegion(position || 'center');
    const tx = Math.round((region.x + region.w / 2) * cw);
    const ty = Math.round((region.y + region.h / 2) * ch);

    // Measure text
    const metrics = ctx.measureText(text);
    const textW = metrics.width;
    const textH = fontPx;
    const padding = Math.round(fontPx * 0.4);

    // Draw background behind text if specified
    if (bgColor) {
      ctx.fillStyle = bgColor;
      ctx.fillRect(tx - textW / 2 - padding, ty - textH / 2 - padding, textW + padding * 2, textH + padding * 2);
    }

    // Dra
Methodology: Repobility · https://repobility.com/research/state-of-ai-code-2026/
fillRegion function · javascript · L1270-L1287 (18 LOC)
src/js/photoeditor.js
  async function fillRegion(position, color) {
    if (!originalImage || !ctx) return 'No image loaded.';
    saveState();
    drawImage();

    const cw = canvas.width, ch = canvas.height;
    const region = parseRegion(position || 'center');
    const rx = Math.round(region.x * cw);
    const ry = Math.round(region.y * ch);
    const rw = Math.round(region.w * cw);
    const rh = Math.round(region.h * ch);

    ctx.fillStyle = color || '#FFD700';
    ctx.fillRect(rx, ry, rw, rh);

    await bakeCanvas();
    return `Filled region "${position}" with ${color} (${rw}x${rh}px).`;
  }
getImageDataURL function · javascript · L1290-L1298 (9 LOC)
src/js/photoeditor.js
  function getImageDataURL() {
    if (!canvas) return null;
    try {
      return canvas.toDataURL('image/png');
    } catch (e) {
      console.error('getImageDataURL failed:', e);
      return null;
    }
  }
syncAudioClips function · javascript · L26-L71 (46 LOC)
src/js/player.js
  function syncAudioClips(currentTime) {
    const allClips = Timeline.getClips();
    const audioClips = allClips.filter(c => c.type === 'audio');

    // Start audio clips that should be playing
    for (const clip of audioClips) {
      const clipEnd = clip.startTime + clip.duration;
      const shouldPlay = isPlaying && currentTime >= clip.startTime && currentTime < clipEnd;

      if (shouldPlay) {
        if (!activeAudioElements.has(clip.id)) {
          // Create and start a new audio element for this clip
          const audio = new Audio(clip.filePath);
          audio.volume = isMuted ? 0 : (clip.volume ?? 100) / 100 * (video.volume || 1);
          audio.currentTime = currentTime - clip.startTime;
          audio.play().catch(() => {}); // ignore autoplay errors
          activeAudioElements.set(clip.id, audio);
        } else {
          // Already playing - check if we need to correct drift
          const audio = activeAudioElements.get(clip.id);
          const expected
stopAllAudio function · javascript · L74-L80 (7 LOC)
src/js/player.js
  function stopAllAudio() {
    for (const [clipId, audio] of activeAudioElements) {
      audio.pause();
      audio.src = '';
    }
    activeAudioElements.clear();
  }
loadVideo function · javascript · L83-L104 (22 LOC)
src/js/player.js
  function loadVideo(filePath) {
    currentSource = filePath;
    video.src = filePath;
    video.style.display = 'block';
    if (noVideoMsg) noVideoMsg.style.display = 'none';

    video.addEventListener('loadedmetadata', () => {
      seekSlider.max = Math.floor(video.duration * 10) / 10;
      updateTimeDisplay();
      detectFrameRate();
      // Generate filmstrip thumbnails
      filmstripGenerated = false;
      setTimeout(() => generateFilmstrip(), 300);
      Accessibility.announce(`Video loaded. Duration: ${Accessibility.formatTime(video.duration)}`);
      Accessibility.setStatus('Video loaded: ' + filePath.split(/[\\/]/).pop());
    }, { once: true });

    video.addEventListener('error', () => {
      Accessibility.announce('Error loading video file');
      Accessibility.setStatus('Error: Could not load video');
    }, { once: true });
  }
play function · javascript · L107-L130 (24 LOC)
src/js/player.js
  function play() {
    const hasVideo = video.src && video.src !== '';
    const hasAudioClips = Timeline.getClips().some(c => c.type === 'audio');

    if (!hasVideo && !hasAudioClips) {
      Accessibility.announce('No media loaded. Import media first.');
      return;
    }

    if (hasVideo) {
      video.play();
    }
    isPlaying = true;
    syncAudioClips(getCurrentTime());

    // Start audio sync loop if we have audio clips
    if (hasAudioClips) {
      startAudioSyncLoop();
    }

    playBtn.textContent = 'Pause';
    playBtn.setAttribute('aria-label', 'Pause');
    Accessibility.announceStatus('Playing');
  }
pause function · javascript · L133-L141 (9 LOC)
src/js/player.js
  function pause() {
    video.pause();
    isPlaying = false;
    stopAllAudio();
    stopAudioSyncLoop();
    playBtn.textContent = 'Play';
    playBtn.setAttribute('aria-label', 'Play');
    Accessibility.announceStatus('Paused');
  }
togglePlay function · javascript · L144-L147 (4 LOC)
src/js/player.js
  function togglePlay() {
    if (isPlaying) pause();
    else play();
  }
Repobility — same analyzer, your code, free for public repos · /scan/
stop function · javascript · L150-L162 (13 LOC)
src/js/player.js
  function stop() {
    video.pause();
    video.currentTime = 0;
    isPlaying = false;
    stopAllAudio();
    stopAudioSyncLoop();
    audioOnlyTime = 0;
    playBtn.textContent = 'Play';
    playBtn.setAttribute('aria-label', 'Play');
    updateTimeDisplay();
    Timeline.setPlayheadPosition(0);
    Accessibility.announceStatus('Stopped');
  }
startAudioSyncLoop function · javascript · L169-L186 (18 LOC)
src/js/player.js
  function startAudioSyncLoop() {
    if (audioSyncInterval) return;
    lastRealTime = performance.now();
    audioSyncInterval = setInterval(() => {
      if (!isPlaying) return;
      const now = performance.now();
      const delta = (now - lastRealTime) / 1000;
      lastRealTime = now;

      const hasVideo = video.src && video.src !== '' && video.duration;
      if (!hasVideo) {
        // Audio-only mode: advance our own clock
        audioOnlyTime += delta;
        updateTimeDisplay();
      }
      syncAudioClips(getCurrentTime());
    }, 50);
  }
stopAudioSyncLoop function · javascript · L188-L193 (6 LOC)
src/js/player.js
  function stopAudioSyncLoop() {
    if (audioSyncInterval) {
      clearInterval(audioSyncInterval);
      audioSyncInterval = null;
    }
  }
skipForward function · javascript · L196-L200 (5 LOC)
src/js/player.js
  function skipForward(seconds = 5) {
    video.currentTime = Math.min(video.currentTime + seconds, video.duration || 0);
    updateTimeDisplay();
    Accessibility.announceStatus(`Skipped forward to ${Accessibility.formatTime(video.currentTime)}`);
  }
skipBack function · javascript · L203-L207 (5 LOC)
src/js/player.js
  function skipBack(seconds = 5) {
    video.currentTime = Math.max(video.currentTime - seconds, 0);
    updateTimeDisplay();
    Accessibility.announceStatus(`Skipped back to ${Accessibility.formatTime(video.currentTime)}`);
  }
detectFrameRate function · javascript · L212-L235 (24 LOC)
src/js/player.js
  function detectFrameRate() {
    // Try to detect FPS using requestVideoFrameCallback
    if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
      let lastTime = null;
      let samples = [];
      const detect = (now, metadata) => {
        if (lastTime !== null) {
          const delta = metadata.mediaTime - lastTime;
          if (delta > 0 && delta < 0.2) {
            samples.push(1 / delta);
          }
        }
        lastTime = metadata.mediaTime;
        if (samples.length < 10) {
          video.requestVideoFrameCallback(detect);
        } else {
          const avgFps = samples.reduce((a, b) => a + b, 0) / samples.length;
          detectedFPS = Math.round(avgFps);
          if (detectedFPS < 10 || detectedFPS > 120) detectedFPS = 30;
        }
      };
      video.requestVideoFrameCallback(detect);
    }
  }
frameForward function · javascript · L241-L267 (27 LOC)
src/js/player.js
  function frameForward() {
    if (!video.src || !video.duration) {
      Accessibility.announce('No video loaded');
      return;
    }

    const frameTime = 1 / detectedFPS;
    const targetTime = Math.min(video.currentTime + frameTime, video.duration);

    // Unmute so you hear the audio while stepping
    video.muted = false;
    video.playbackRate = 0.5; // slow so the audio blip is audible
    video.play();

    // Stop after one frame's worth of real time
    setTimeout(() => {
      video.pause();
      video.playbackRate = 1;
      video.currentTime = targetTime; // snap to exact frame
      isPlaying = false;
      playBtn.textContent = 'Play';
      playBtn.setAttribute('aria-label', 'Play video');
      updateTimeDisplay();
      const frameNum = Math.round(video.currentTime * detectedFPS);
      Accessibility.announceStatus(`Frame ${frameNum}, ${Accessibility.formatTimeDisplay(video.currentTime)}`);
    }, Math.round(frameTime * 1000 * 2)); // 2x because playbackRate is
frameBack function · javascript · L273-L301 (29 LOC)
src/js/player.js
  function frameBack() {
    if (!video.src || !video.duration) {
      Accessibility.announce('No video loaded');
      return;
    }

    const frameTime = 1 / detectedFPS;
    const targetTime = Math.max(video.currentTime - frameTime, 0);

    // Seek to the target frame
    video.currentTime = targetTime;

    // Play a tiny blip so user hears where they are
    video.muted = false;
    video.playbackRate = 0.5;
    video.play();

    setTimeout(() => {
      video.pause();
      video.playbackRate = 1;
      video.currentTime = targetTime; // snap back to exact frame
      isPlaying = false;
      playBtn.textContent = 'Play';
      playBtn.setAttribute('aria-label', 'Play video');
      updateTimeDisplay();
      const frameNum = Math.round(video.currentTime * detectedFPS);
      Accessibility.announceStatus(`Frame ${frameNum}, ${Accessibility.formatTimeDisplay(video.currentTime)}`);
    }, Math.round(frameTime * 1000 * 2));
  }
Powered by Repobility — scan your code at https://repobility.com
seekTo function · javascript · L304-L318 (15 LOC)
src/js/player.js
  function seekTo(time) {
    const maxTime = getDuration();
    const seekTime = Math.max(0, Math.min(time, maxTime));
    const hasVideo = video.src && video.src !== '' && video.duration;
    if (hasVideo) {
      video.currentTime = Math.min(seekTime, video.duration);
    }
    audioOnlyTime = seekTime;
    // Re-sync audio clips to new position
    stopAllAudio();
    if (isPlaying) {
      syncAudioClips(seekTime);
    }
    updateTimeDisplay();
  }
setVolume function · javascript · L321-L335 (15 LOC)
src/js/player.js
  function setVolume(val) {
    video.volume = val / 100;
    volumeSlider.value = val;
    volumeSlider.setAttribute('aria-valuetext', `${val} percent`);
    volumeSlider.setAttribute('aria-label', `Volume, currently ${val} percent`);
    if (val === 0) {
      isMuted = true;
      muteBtn.textContent = 'Unmute';
      muteBtn.setAttribute('aria-label', 'Unmute audio');
    } else {
      isMuted = false;
      muteBtn.textContent = 'Mute';
      muteBtn.setAttribute('aria-label', 'Mute audio');
    }
  }
toggleMute function · javascript · L338-L360 (23 LOC)
src/js/player.js
  function toggleMute() {
    if (isMuted) {
      video.muted = false;
      isMuted = false;
      // Unmute all active audio elements
      for (const [, audio] of activeAudioElements) {
        audio.volume = video.volume || 1;
      }
      muteBtn.textContent = 'Mute';
      muteBtn.setAttribute('aria-label', 'Mute audio');
      Accessibility.announceStatus('Audio unmuted');
    } else {
      video.muted = true;
      isMuted = true;
      // Mute all active audio elements
      for (const [, audio] of activeAudioElements) {
        audio.volume = 0;
      }
      muteBtn.textContent = 'Unmute';
      muteBtn.setAttribute('aria-label', 'Unmute audio');
      Accessibility.announceStatus('Audio muted');
    }
  }
updateTimeDisplay function · javascript · L366-L388 (23 LOC)
src/js/player.js
  function updateTimeDisplay() {
    const current = getCurrentTime();
    const duration = getDuration();
    const currentStr = Accessibility.formatTimeDisplay(current);
    const durationStr = Accessibility.formatTimeDisplay(duration);
    timeDisplay.textContent = `${currentStr} / ${durationStr}`;
    seekSlider.value = current;
    seekSlider.setAttribute('aria-valuetext',
      `${Accessibility.formatTime(current)} of ${Accessibility.formatTime(duration)}`);

    // Update filmstrip playhead position
    if (filmstripPlayhead && filmstripContainer && duration > 0) {
      const pct = current / duration;
      const containerWidth = filmstripContainer.offsetWidth;
      filmstripPlayhead.style.left = (pct * containerWidth) + 'px';
    }

    // Update the playhead info text (visible + read by screen reader on demand)
    updatePlayheadInfo(current);

    // Update timeline playhead
    Timeline.setPlayheadPosition(current);
  }
updatePlayheadInfo function · javascript · L391-L415 (25 LOC)
src/js/player.js
  function updatePlayheadInfo(time) {
    const infoEl = document.getElementById('playhead-position-text');
    if (!infoEl) return;

    const currentSecond = Math.floor(time);
    // Only update text once per second to avoid excessive DOM writes
    if (currentSecond === lastAnnouncedSecond) return;
    lastAnnouncedSecond = currentSecond;

    const clipsHere = Timeline.getClipsAtTime(time);
    const timeStr = Accessibility.formatTime(time);
    let info = `Playhead at ${timeStr}.`;

    if (clipsHere.length === 0) {
      info += ' No clips at this position.';
    } else {
      const descriptions = clipsHere.map(c => {
        const remaining = (c.startTime + c.duration) - time;
        return `${c.type} clip "${c.name}" (${Accessibility.formatTime(remaining)} remaining)`;
      });
      info += ' At this position: ' + descriptions.join(', ') + '.';
    }

    infoEl.textContent = info;
  }
announceWhereAmI function · javascript · L418-L462 (45 LOC)
src/js/player.js
  function announceWhereAmI() {
    const current = getCurrentTime();
    const duration = getDuration();
    const clipsHere = Timeline.getClipsAtTime(current);
    const timeStr = Accessibility.formatTime(current);
    const durationStr = Accessibility.formatTime(duration);

    let msg = `Playhead is at ${timeStr} out of ${durationStr} total.`;

    if (clipsHere.length === 0) {
      msg += ' There are no clips at this position. ';
      // Find the nearest clip
      const allClips = Timeline.getClips();
      if (allClips.length > 0) {
        const sorted = allClips.slice().sort((a, b) => {
          const distA = Math.min(Math.abs(a.startTime - current), Math.abs(a.startTime + a.duration - current));
          const distB = Math.min(Math.abs(b.startTime - current), Math.abs(b.startTime + b.duration - current));
          return distA - distB;
        });
        const nearest = sorted[0];
        if (current < nearest.startTime) {
          msg += `The next clip is "${nearest.n
generateFilmstrip function · javascript · L465-L536 (72 LOC)
src/js/player.js
  function generateFilmstrip() {
    if (!filmstripCanvas || !video.duration || filmstripGenerated) return;

    const ctx = filmstripCanvas.getContext('2d');
    if (!ctx) return;

    const container = filmstripContainer;
    const width = container.offsetWidth;
    const height = container.offsetHeight;

    // Set canvas resolution
    filmstripCanvas.width = width * 2;  // 2x for sharpness
    filmstripCanvas.height = height * 2;
    filmstripCanvas.style.width = width + 'px';
    filmstripCanvas.style.height = height + 'px';
    ctx.scale(2, 2);

    const duration = video.duration;
    const thumbWidth = Math.max(40, height * (video.videoWidth / video.videoHeight || 16/9));
    const numThumbs = Math.ceil(width / thumbWidth) + 1;
    const timeStep = duration / numThumbs;

    // Use an offscreen video to grab frames without interrupting playback
    const offscreen = document.createElement('video');
    offscreen.src = video.src;
    offscreen.muted = true;
    offscreen.preloa
grabNextFrame function · javascript · L499-L535 (37 LOC)
src/js/player.js
    function grabNextFrame() {
      if (thumbIndex >= numThumbs) {
        filmstripGenerated = true;
        offscreen.remove();
        // Draw subtle frame borders
        ctx.strokeStyle = 'rgba(255,255,255,0.1)';
        ctx.lineWidth = 0.5;
        for (let i = 1; i < numThumbs; i++) {
          const x = i * thumbWidth;
          ctx.beginPath();
          ctx.moveTo(x, 0);
          ctx.lineTo(x, height);
          ctx.stroke();
        }
        return;
      }

      const seekTime = thumbIndex * timeStep;
      offscreen.currentTime = seekTime;

      offscreen.addEventListener('seeked', function onSeeked() {
        offscreen.removeEventListener('seeked', onSeeked);

        const x = thumbIndex * thumbWidth;
        try {
          ctx.drawImage(offscreen, x, 0, thumbWidth, height);
        } catch (e) {
          // If cross-origin or error, draw a placeholder
          ctx.fillStyle = `hsl(${(thumbIndex * 30) % 360}, 30%, 25%)`;
          ctx.fillRect(x, 0, thumbWidth, 
Source: Repobility analyzer · https://repobility.com
initFilmstripClick function · javascript · L539-L587 (49 LOC)
src/js/player.js
  function initFilmstripClick() {
    if (!filmstripContainer) return;

    let isDragging = false;

    function seekFromMouse(e) {
      const rect = filmstripContainer.getBoundingClientRect();
      const x = e.clientX - rect.left;
      const pct = Math.max(0, Math.min(1, x / rect.width));
      const time = pct * (video.duration || 0);
      seekTo(time);
    }

    filmstripContainer.addEventListener('mousedown', (e) => {
      isDragging = true;
      seekFromMouse(e);
    });

    document.addEventListener('mousemove', (e) => {
      if (isDragging) seekFromMouse(e);
    });

    document.addEventListener('mouseup', () => {
      isDragging = false;
    });

    // Touch support
    filmstripContainer.addEventListener('touchstart', (e) => {
      isDragging = true;
      const touch = e.touches[0];
      const rect = filmstripContainer.getBoundingClientRect();
      const x = touch.clientX - rect.left;
      const pct = Math.max(0, Math.min(1, x / rect.width));
      seekTo(pct
seekFromMouse function · javascript · L544-L550 (7 LOC)
src/js/player.js
    function seekFromMouse(e) {
      const rect = filmstripContainer.getBoundingClientRect();
      const x = e.clientX - rect.left;
      const pct = Math.max(0, Math.min(1, x / rect.width));
      const time = pct * (video.duration || 0);
      seekTo(time);
    }
getCurrentTime function · javascript · L590-L594 (5 LOC)
src/js/player.js
  function getCurrentTime() {
    const hasVideo = video.src && video.src !== '' && video.duration;
    if (hasVideo) return video.currentTime || 0;
    return audioOnlyTime;
  }
‹ prevpage 3 / 4next ›