Skip to content

StreamGrid v1.2.3 - Stream Video Scaling Toggle#11

Merged
LordKnish merged 9 commits intomainfrom
v1.2.3
Sep 1, 2025
Merged

StreamGrid v1.2.3 - Stream Video Scaling Toggle#11
LordKnish merged 9 commits intomainfrom
v1.2.3

Conversation

@LordKnish
Copy link
Owner

@LordKnish LordKnish commented Sep 1, 2025

This PR adds a new feature to StreamGrid that allows users to toggle between different video scaling modes for each stream individually.

New Features

Stream Video Scaling Toggle

  • Added per-stream fit mode toggle button in the stream header
  • Two modes available:
    • Fit Mode (contain): Preserves aspect ratio, may show black bars
    • Fill Mode (cover): Fills the entire cell, may crop edges
  • Toggle button appears next to the stop button when a stream is playing
  • Visual feedback with different background colors based on current mode
  • Tooltips indicate current mode: "Switch to Fill Mode" / "Switch to Fit Mode"

Technical Changes

Type Updates

  • Added fitMode?: 'contain' | 'cover' property to the Stream interface
  • Default value is 'contain' (aspect ratio preserved)

Component Updates

StreamCard.tsx

  • Added toggle button with Material-UI icons (CropFree/AspectRatio)
  • Implemented handleToggleFitMode function to switch between modes
  • Applied object-fit CSS property to both iframe (Twitch) and ReactPlayer components
  • State persists per stream using the existing store update mechanism

Styling

  • Video elements properly scale based on selected mode
  • Consistent behavior across all stream types (YouTube, Twitch, HLS, DASH, local files)

User Experience

  • Settings are automatically saved and restored across sessions
  • Import/export functionality includes the fitMode property
  • Seamless switching without stream interruption

Testing

Tested with various stream types and aspect ratios:

  • ✅ YouTube streams
  • ✅ Twitch streams
  • ✅ HLS/DASH streams
  • ✅ Local video files
  • ✅ Different aspect ratios (16:9, 4:3, vertical videos)

Version

  • Updated to v1.2.3

Summary by Sourcery

Add per-stream video scaling toggle and RTSP streaming support, improve stream paste UX, update security policy and build configuration.

New Features:

  • Add per-stream video scaling toggle (contain/cover) with persistent state and UI button
  • Introduce RTSP streaming support with FFmpeg integration and new IPC preload APIs

Enhancements:

  • Improve paste handling logic in AddStreamDialog for logo and stream URL fields
  • Modify content security policy to allow blob and media sources at build and runtime
  • Optimize Electron Vite build configuration with manual chunking and code splitting

Build:

  • Bump application version to v1.2.3 and add express and fluent-ffmpeg dependencies
  • Adjust electron-builder targets formatting for macOS, Windows, and Linux

Documentation:

  • Add RTSP streaming setup and usage instructions to README with FFmpeg prerequisite

Chores:

  • Remove React PropTypes from StreamCard and disable prop-types and no-explicit-any ESLint rules

- Remove blanket preventDefault() that blocked all paste operations
- Allow normal paste for non-URL content and invalid URLs
- Maintain auto-detection features for valid stream URLs
- Add missing extractStreamInfo dependency
- Update URL validation to accept rtsp:// and rtsps:// protocols
- Add RTSP stream type detection in detectStreamType()
- Fix error messages to properly handle RTSP URLs
- Update onChange handler to process RTSP URLs correctly

Fixes issue where RTSP URLs were incorrectly marked as invalid
in the Add Stream dialog.
- Add fitMode property to Stream interface ('contain' | 'cover')
- Add toggle button in StreamCard to switch between fit modes
- Implement scaling using CSS transforms for iframes and container scaling
- Button appears next to Stop button when stream is playing
- Persist fit mode preference per stream across sessions
- Include fit mode in import/export functionality
- Update ESLint config to disable prop-types for TypeScript
- Bump version to 1.2.3
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Sep 1, 2025

Reviewer's Guide

Introduces a per-stream fitMode state and toggle in StreamCard—hooking into the store to persist ‘contain’/‘cover’ modes and applying corresponding object-fit and scaling styles—while extending types and preload APIs for RTSP, adjusting CSP headers in main and renderer to support blob/media sources, refactoring AddStreamDialog’s paste handler for cleaner URL handling, updating documentation and package metadata for RTSP support, and refining build and lint configurations for code splitting and rule overrides.

Sequence diagram for toggling video scaling mode in StreamCard

sequenceDiagram
    actor User
    participant StreamCard
    participant Store
    User->>StreamCard: Clicks fit/fill toggle button
    StreamCard->>Store: updateStream(stream.id, { fitMode })
    Store-->>StreamCard: Updates stream state
    StreamCard-->>User: Rerenders video with new scaling mode
Loading

Class diagram for updated Stream type and StreamCard component

classDiagram
    class Stream {
      +id: string
      +name: string
      +streamUrl: string
      +logoUrl: string
      +chatId?: string
      +isLivestream?: boolean
      +fitMode?: "contain" | "cover"
    }
    class StreamCard {
      +handleToggleFitMode()
      +currentFitMode
      +updateStream()
      +render()
    }
    StreamCard --> Stream: uses
Loading

File-Level Changes

Change Details Files
Implement per-stream video scaling toggle in StreamCard
  • Derive currentFitMode from stream.fitMode with default 'contain'
  • Add handleToggleFitMode callback to updateStream store action
  • Render IconButton with dynamic background, tooltip and CropFree/AspectRatio icons
  • Wrap iframe and ReactPlayer in Box applying object-fit, overflow and transform based on fitMode
src/renderer/src/components/StreamCard.tsx
Extend types and preload definitions for fitMode and RTSP support
  • Add optional fitMode property to Stream interface ('contain'
'cover')
  • Declare rtspStartStream, rtspStopStream and rtspCheckFfmpeg methods in preload index.d.ts
  • Adjust Content Security Policy to allow blob and media sources
    • Hook webContents.session.onHeadersReceived in main to inject blob: and data:file: into CSP
    • Update renderer index.html meta CSP to include blob:, data:, file: in media-src and default-src
    src/main/index.ts
    src/renderer/index.html
    Refactor AddStreamDialog paste handling for logos and streams
    • Separate logic for logo-url (delayed validation without blocking default)
    • Prevent default only when pasting valid ReactPlayer URLs
    • Normalize YouTube/Twitch URLs and auto-populate thumbnails and titles
    src/renderer/src/components/AddStreamDialog.tsx
    Update documentation and dependencies for RTSP streaming
    • Add FFmpeg installation steps and RTSP usage to README
    • Bump package version to v1.2.3 and add express, fluent-ffmpeg and their type packages
    README.md
    package.json
    Refine build and lint configurations
    • Configure electron.vite.config.mjs for aliasing, CSP removal, manual chunking and minification
    • Disable react/prop-types and @typescript-eslint/no-explicit-any in .eslintrc.cjs
    electron.vite.config.mjs
    .eslintrc.cjs

    Possibly linked issues


    Tips and commands

    Interacting with Sourcery

    • Trigger a new review: Comment @sourcery-ai review on the pull request.
    • Continue discussions: Reply directly to Sourcery's review comments.
    • Generate a GitHub issue from a review comment: Ask Sourcery to create an
      issue from a review comment by replying to it. You can also reply to a
      review comment with @sourcery-ai issue to create an issue from it.
    • Generate a pull request title: Write @sourcery-ai anywhere in the pull
      request title to generate a title at any time. You can also comment
      @sourcery-ai title on the pull request to (re-)generate the title at any time.
    • Generate a pull request summary: Write @sourcery-ai summary anywhere in
      the pull request body to generate a PR summary at any time exactly where you
      want it. You can also comment @sourcery-ai summary on the pull request to
      (re-)generate the summary at any time.
    • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
      request to (re-)generate the reviewer's guide at any time.
    • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
      pull request to resolve all Sourcery comments. Useful if you've already
      addressed all the comments and don't want to see them anymore.
    • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
      request to dismiss all existing Sourcery reviews. Especially useful if you
      want to start fresh with a new review - don't forget to comment
      @sourcery-ai review to trigger a new review!

    Customizing Your Experience

    Access your dashboard to:

    • Enable or disable review features such as the Sourcery-generated pull request
      summary, the reviewer's guide, and others.
    • Change the review language.
    • Add, remove or edit custom review instructions.
    • Adjust other review settings.

    Getting Help

    Copy link
    Contributor

    @sourcery-ai sourcery-ai bot left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Hey there - I've reviewed your changes and they look great!

    Prompt for AI Agents
    Please address the comments from this code review:
    ## Individual Comments
    
    ### Comment 1
    <location> `src/renderer/src/components/AddStreamDialog.tsx:204` </location>
    <code_context>
    +      const target = e.target as HTMLInputElement
    +      const targetId = target.id
    +
    +      // For logo URL field
    +      if (targetId === 'logo-url') {
    +        // Don't prevent default - let the paste happen normally
    +        // Then check if it's a valid image URL after a short delay
    +        setTimeout(() => {
    +          if (isValidImageUrl(pastedText)) {
    +            trySetLogoPreview(pastedText)
    +          }
    +        }, 0)
    +      }
    +      // For stream URL field
    </code_context>
    
    <issue_to_address>
    Using setTimeout for logo URL validation may introduce race conditions.
    
    Using setTimeout here may not reliably validate the pasted value if the input changes quickly or other async events interfere. Consider validating on the input event or another method that ensures the correct value is checked.
    </issue_to_address>
    
    <suggested_fix>
    <<<<<<< SEARCH
          // For logo URL field
          if (targetId === 'logo-url') {
            // Don't prevent default - let the paste happen normally
            // Then check if it's a valid image URL after a short delay
            setTimeout(() => {
              if (isValidImageUrl(pastedText)) {
                trySetLogoPreview(pastedText)
              }
            }, 0)
          }
    =======
          // For logo URL field
          if (targetId === 'logo-url') {
            // Don't prevent default - let the paste happen normally
            // Validate the pasted value on the next input event
            const handleLogoUrlInput = (event: Event) => {
              const input = event.target as HTMLInputElement;
              const value = input.value;
              if (isValidImageUrl(value)) {
                trySetLogoPreview(value);
              }
              input.removeEventListener('input', handleLogoUrlInput);
            };
            target.addEventListener('input', handleLogoUrlInput);
          }
    >>>>>>> REPLACE
    
    </suggested_fix>

    Sourcery is free for open source - if you like our reviews please consider sharing them ✨
    Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

    LordKnish and others added 3 commits September 1, 2025 12:35
    Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
    …on completes
    
    - Validating the actual input field value instead of relying on clipboard text
    
    - Automatically removing the event listener after use to prevent memory leaks
    @LordKnish LordKnish merged commit efbe67d into main Sep 1, 2025
    3 checks passed
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Labels

    None yet

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    1 participant