Skip to content

feat(thumb): add stream_thumbnail plugin - #133

Merged
xsahil03x merged 8 commits into
mainfrom
feat/stream-video-thumbnail
Jul 21, 2026
Merged

feat(thumb): add stream_thumbnail plugin#133
xsahil03x merged 8 commits into
mainfrom
feat/stream-video-thumbnail

Conversation

@xsahil03x

@xsahil03x xsahil03x commented Jul 21, 2026

Copy link
Copy Markdown
Member

What

Adds stream_thumbnail, a Flutter plugin for creating a thumbnail from a local video file or a video URL. Supports Android, iOS, and web.

Details

  • Public APIStreamThumbnail (thumbnailData / thumbnailFile / thumbnailFiles) and the StreamThumbnailFormat enum, exposed via a single barrel (package:stream_thumbnail/stream_thumbnail.dart).
  • iOS — supports both Swift Package Manager and CocoaPods; WebP encoding via libwebp; results delivered on the main thread (work stays backgrounded).
  • Android — Kotlin DSL (build.gradle.kts) with Flutter's Built-in Kotlin (no KGP applied); namespace io.getstream.stream_thumbnail.
  • Web — canvas / <video> implementation via package:web, with a timeout guard so a stalled source can't hang.
  • Workspace wiringplugin_platform_interface added to shared deps; thumb PR-title/changelog scope; SPM build artifacts gitignored.
  • Tests — unit tests for the public API, method-channel argument encoding, the format-index (native wire) contract, and the thumbnailFile path. Example app (Android/iOS/web) demonstrating usage.

Verification

Built and verified on:

  • iOS — Swift Package Manager ✅ and CocoaPods ✅
  • Android ✅ (no Kotlin-Gradle-Plugin deprecation warning)
  • Web ✅
  • flutter analyze clean · flutter test green

Notes

  • Derived from get_video_thumbnail (MIT); the LICENSE retains the upstream copyright.
  • The iOS plugin class is the uniquely-named StreamThumbnailPlugin. Once stream_chat_flutter swaps get_thumbnail_videostream_thumbnail, this resolves the duplicate-VideoThumbnailPlugin-symbol crash reported in stream-chat-flutter#2360.

🤖 Generated with Claude Code

A Flutter plugin for creating a thumbnail from a local video file or from
a video URL, supporting Android, iOS, and web. The iOS implementation
supports both Swift Package Manager and CocoaPods.

- Public Dart API: `StreamVideoThumbnail` (thumbnailData/File/Files) and
  `StreamThumbnailFormat`, exposed via a single barrel.
- Register the package in the workspace: add `plugin_platform_interface`
  to shared dependencies and ignore SPM build artifacts.
- Unit tests covering the API surface, method-channel encoding, and the
  format index contract.
- Example app (Android/iOS/web) demonstrating thumbnail generation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xsahil03x
xsahil03x requested a review from a team as a code owner July 21, 2026 11:24
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds the stream_thumbnail Flutter plugin with Dart APIs and platform implementations for Android, iOS, and web. It supports in-memory, file, and batch thumbnail generation, JPEG/PNG/WebP output, configurable sizing and timestamps, an interactive example app, tests, and package release metadata.

Changes

Video thumbnail plugin

Layer / File(s) Summary
Dart API and platform channel
packages/stream_thumbnail/lib/**, packages/stream_thumbnail/pubspec.yaml, packages/stream_thumbnail/test/**
Defines public thumbnail methods, supported formats, platform-interface contracts, method-channel request/response handling, package exports, and tests for forwarding and argument encoding.
Android thumbnail backend
packages/stream_thumbnail/android/**
Adds the Android library configuration and Kotlin plugin implementation for URI loading, frame extraction, resizing, compression, file output, asynchronous execution, and result callbacks.
iOS thumbnail backend
packages/stream_thumbnail/ios/**
Adds CocoaPods and Swift Package manifests plus Objective-C thumbnail generation using AVFoundation and JPEG, PNG, or WebP encoding.
Web thumbnail backend
packages/stream_thumbnail/lib/stream_thumbnail_web.dart
Adds browser video and canvas processing with optional request headers, format-specific blob export, resizing, object URLs, and media/canvas error handling.
Example application and release support
packages/stream_thumbnail/example/**, README.md, CHANGELOG.md, LICENSE, .github/workflows/pr_title.yml, .gitignore
Adds the Android, iOS, and web example project, thumbnail preview UI, package documentation and release files, semantic workflow scopes, and SwiftPM ignore rules.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant StreamThumbnail
  participant PlatformImplementation
  participant VideoDecoder
  participant ImageOutput

  App->>StreamThumbnail: Request thumbnail
  StreamThumbnail->>PlatformImplementation: Forward video and options
  PlatformImplementation->>VideoDecoder: Load video and capture frame
  VideoDecoder-->>PlatformImplementation: Video frame
  PlatformImplementation->>ImageOutput: Encode or save thumbnail
  ImageOutput-->>PlatformImplementation: Bytes or file path
  PlatformImplementation-->>StreamThumbnail: Thumbnail result
  StreamThumbnail-->>App: Uint8List or XFile
Loading

Suggested reviewers: renefloor

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding the stream_thumbnail plugin.
Description check ✅ Passed The description covers the feature, implementation details, verification, and notes, but it omits the template's Linear/GitHub issue fields, CLA checklist, and screenshots section.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/stream-video-thumbnail

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.88889% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 44.60%. Comparing base (59b3f4e) to head (ef5b25a).

Files with missing lines Patch % Lines
...ges/stream_thumbnail/lib/src/stream_thumbnail.dart 88.88% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #133      +/-   ##
==========================================
+ Coverage   44.53%   44.60%   +0.06%     
==========================================
  Files         178      179       +1     
  Lines        7243     7252       +9     
==========================================
+ Hits         3226     3235       +9     
  Misses       4017     4017              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/stream_video_thumbnail/android/src/main/AndroidManifest.xml`:
- Around line 1-3: Remove the package attribute from the manifest element in
AndroidManifest.xml, leaving the existing android namespace declaration intact
and relying on the module namespace configured in build.gradle.kts.

In
`@packages/stream_video_thumbnail/android/src/main/kotlin/io/getstream/stream_video_thumbnail/StreamVideoThumbnailPlugin.kt`:
- Around line 50-51: Update the method handling in StreamVideoThumbnailPlugin so
callId is extracted only inside request branches that provide it, rather than
unconditionally after reading arguments. Ensure arg-less methods such as
getPlatformVersion do not cast null or missing arguments, while preserving
callId handling for branches that require it.
- Around line 154-158: Update the exception handling around
buildThumbnailFile/buildThumbnailData in the files batch loop to catch the
decode failure represented by non-IOException exceptions, including
NullPointerException, and continue processing remaining videos. Preserve the
existing skip behavior and final onResult("result#files", callId, results)
response.
- Around line 40-46: Update onAttachedToEngine in StreamVideoThumbnailPlugin to
assign the FlutterPluginBinding applicationContext to the plugin’s context field
before handling method calls. Preserve the existing MethodChannel setup while
ensuring buildThumbnailFile and contentResolver access use the initialized
context.

In
`@packages/stream_video_thumbnail/example/android/app/src/profile/AndroidManifest.xml`:
- Around line 1-7: Add the android.permission.INTERNET uses-permission entry to
the example app’s main AndroidManifest.xml, matching the existing declaration in
the profile manifest, so release builds can fetch the remote sample video.

In
`@packages/stream_video_thumbnail/ios/stream_video_thumbnail/Sources/stream_video_thumbnail/StreamVideoThumbnailPlugin.m`:
- Around line 107-112: Update the thumbnail generation guard after
copyCGImageAtTime to return nil when either error is non-nil or cgImage is NULL,
preventing subsequent CGImageGetColorSpace and UIImage imageWithCGImage calls
from receiving a null image.
- Around line 126-180: Fix ownership handling in the WebP conversion flow:
remove CGColorSpaceRelease and CGDataProviderRelease for the borrowed refs
returned by CGImageGetColorSpace and CGImageGetDataProvider, and ensure cgImage
is released on successful completion. After creating the NSData result, release
the WebP encoder buffer with WebPFree before returning, while preserving
existing failure cleanup.

In
`@packages/stream_video_thumbnail/lib/src/stream_video_thumbnail_method_channel.dart`:
- Around line 134-155: Update thumbnailFiles, thumbnailFile, and thumbnailData
to clean up the callId entry from _futures when methodChannel.invokeMethod
throws before native acknowledgement. Wrap each invocation and existing result
handling with exception cleanup, then rethrow the original exception; preserve
the current successful response and completer.future behavior.
- Around line 91-98: Update _createCompleterAndCallId to generate call IDs from
a monotonic counter rather than completer.hashCode, incrementing the counter for
each request before storing the completer in _futures. Preserve the existing
completer creation, registration, and tuple return behavior.

In `@packages/stream_video_thumbnail/lib/src/stream_video_thumbnail.dart`:
- Around line 51-73: Replace the debug-only assert checks for empty video input
in the thumbnailFile and thumbnailData public APIs with explicit ArgumentError
validation that always executes. Preserve the existing platform delegation for
valid non-empty video values and update the corresponding empty-video test
expectations to assert ArgumentError.

In `@packages/stream_video_thumbnail/lib/stream_video_thumbnail_web.dart`:
- Around line 130-260: The _createThumbnail completer can remain unresolved when
video loading or seeking produces no terminal event. Add a timeout guard around
the completer future that completes it with an appropriate thumbnail-generation
error, while preserving successful loaded/seeked completion and existing error
handling; ensure the timeout is cancelled or ignored once the completer has
completed.
- Around line 145-154: Update the loadedmetadata handler around
video.currentTime so the zero-offset case completes without relying on a seeked
event that may not fire. When timeSec is zero, resolve the pending operation
immediately (using the existing completion flow); preserve normal seeking and
cleanup behavior for nonzero offsets.

In `@packages/stream_video_thumbnail/test/stream_video_thumbnail_test.dart`:
- Around line 126-133: Update the empty video path test for
StreamVideoThumbnail.thumbnailData to expect an ArgumentError instead of an
AssertionError, matching the explicit validation used by
thumbnailData/thumbnailFile and ensuring the test remains valid in release
builds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: abc36140-56fc-4afe-8f37-bb6377c57d46

📥 Commits

Reviewing files that changed from the base of the PR and between 59b3f4e and 8e98f8d.

⛔ Files ignored due to path filters (32)
  • packages/stream_video_thumbnail/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata is excluded by !**/*.xcworkspace/contents.xcworkspacedata
  • packages/stream_video_thumbnail/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved is excluded by !**/Package.resolved
  • packages/stream_video_thumbnail/example/ios/Runner.xcworkspace/contents.xcworkspacedata is excluded by !**/*.xcworkspace/contents.xcworkspacedata
  • packages/stream_video_thumbnail/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved is excluded by !**/Package.resolved
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/web/favicon.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/web/icons/Icon-192.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/web/icons/Icon-512.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/web/icons/Icon-maskable-192.png is excluded by !**/*.png
  • packages/stream_video_thumbnail/example/web/icons/Icon-maskable-512.png is excluded by !**/*.png
📒 Files selected for processing (62)
  • .gitignore
  • melos.yaml
  • packages/stream_video_thumbnail/CHANGELOG.md
  • packages/stream_video_thumbnail/LICENSE
  • packages/stream_video_thumbnail/README.md
  • packages/stream_video_thumbnail/android/build.gradle.kts
  • packages/stream_video_thumbnail/android/gradle.properties
  • packages/stream_video_thumbnail/android/gradle/wrapper/gradle-wrapper.properties
  • packages/stream_video_thumbnail/android/settings.gradle.kts
  • packages/stream_video_thumbnail/android/src/main/AndroidManifest.xml
  • packages/stream_video_thumbnail/android/src/main/kotlin/io/getstream/stream_video_thumbnail/StreamVideoThumbnailPlugin.kt
  • packages/stream_video_thumbnail/example/.metadata
  • packages/stream_video_thumbnail/example/README.md
  • packages/stream_video_thumbnail/example/analysis_options.yaml
  • packages/stream_video_thumbnail/example/android/app/build.gradle.kts
  • packages/stream_video_thumbnail/example/android/app/src/debug/AndroidManifest.xml
  • packages/stream_video_thumbnail/example/android/app/src/main/AndroidManifest.xml
  • packages/stream_video_thumbnail/example/android/app/src/main/kotlin/io/getstream/stream_video_thumbnail_example/MainActivity.kt
  • packages/stream_video_thumbnail/example/android/app/src/main/res/drawable-v21/launch_background.xml
  • packages/stream_video_thumbnail/example/android/app/src/main/res/drawable/launch_background.xml
  • packages/stream_video_thumbnail/example/android/app/src/main/res/values-night/styles.xml
  • packages/stream_video_thumbnail/example/android/app/src/main/res/values/styles.xml
  • packages/stream_video_thumbnail/example/android/app/src/profile/AndroidManifest.xml
  • packages/stream_video_thumbnail/example/android/build.gradle.kts
  • packages/stream_video_thumbnail/example/android/gradle.properties
  • packages/stream_video_thumbnail/example/android/gradle/wrapper/gradle-wrapper.properties
  • packages/stream_video_thumbnail/example/android/settings.gradle.kts
  • packages/stream_video_thumbnail/example/ios/Flutter/AppFrameworkInfo.plist
  • packages/stream_video_thumbnail/example/ios/Flutter/Debug.xcconfig
  • packages/stream_video_thumbnail/example/ios/Flutter/Release.xcconfig
  • packages/stream_video_thumbnail/example/ios/Runner.xcodeproj/project.pbxproj
  • packages/stream_video_thumbnail/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  • packages/stream_video_thumbnail/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  • packages/stream_video_thumbnail/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  • packages/stream_video_thumbnail/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  • packages/stream_video_thumbnail/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  • packages/stream_video_thumbnail/example/ios/Runner/AppDelegate.swift
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
  • packages/stream_video_thumbnail/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
  • packages/stream_video_thumbnail/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
  • packages/stream_video_thumbnail/example/ios/Runner/Base.lproj/Main.storyboard
  • packages/stream_video_thumbnail/example/ios/Runner/Info.plist
  • packages/stream_video_thumbnail/example/ios/Runner/Runner-Bridging-Header.h
  • packages/stream_video_thumbnail/example/ios/Runner/SceneDelegate.swift
  • packages/stream_video_thumbnail/example/ios/RunnerTests/RunnerTests.swift
  • packages/stream_video_thumbnail/example/lib/main.dart
  • packages/stream_video_thumbnail/example/pubspec.yaml
  • packages/stream_video_thumbnail/example/web/index.html
  • packages/stream_video_thumbnail/example/web/manifest.json
  • packages/stream_video_thumbnail/ios/stream_video_thumbnail.podspec
  • packages/stream_video_thumbnail/ios/stream_video_thumbnail/Package.swift
  • packages/stream_video_thumbnail/ios/stream_video_thumbnail/Sources/stream_video_thumbnail/StreamVideoThumbnailPlugin.m
  • packages/stream_video_thumbnail/ios/stream_video_thumbnail/Sources/stream_video_thumbnail/include/stream_video_thumbnail/StreamVideoThumbnailPlugin.h
  • packages/stream_video_thumbnail/lib/src/stream_thumbnail_format.dart
  • packages/stream_video_thumbnail/lib/src/stream_video_thumbnail.dart
  • packages/stream_video_thumbnail/lib/src/stream_video_thumbnail_method_channel.dart
  • packages/stream_video_thumbnail/lib/src/stream_video_thumbnail_platform.dart
  • packages/stream_video_thumbnail/lib/stream_video_thumbnail.dart
  • packages/stream_video_thumbnail/lib/stream_video_thumbnail_web.dart
  • packages/stream_video_thumbnail/pubspec.yaml
  • packages/stream_video_thumbnail/test/stream_video_thumbnail_test.dart

Comment thread packages/stream_thumbnail/android/src/main/AndroidManifest.xml
Comment thread packages/stream_thumbnail/lib/src/stream_thumbnail.dart
Comment thread packages/stream_thumbnail/lib/stream_thumbnail_web.dart
Comment thread packages/stream_thumbnail/lib/stream_thumbnail_web.dart
Comment thread packages/stream_thumbnail/test/stream_thumbnail_test.dart
- Add a `thumb` scope to the conventional PR-title check and map it to
  packages/stream_video_thumbnail for the changelog verifier.
- Exclude the platform-implementation layer (web, method channel, and
  platform-interface boilerplate) from coverage; it is verified end-to-end
  via the example app rather than unit tests.
- Add a thumbnailFile unit test covering the public API facade.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xsahil03x xsahil03x changed the title feat: add stream_video_thumbnail plugin feat(thumb): add stream_video_thumbnail plugin Jul 21, 2026
xsahil03x and others added 2 commits July 21, 2026 13:57
- Capture the application Context in the Android plugin's
  onAttachedToEngine (and clear it on detach) so content:// inputs and the
  cache-dir save path no longer NPE.
- Use a monotonic call ID instead of Completer.hashCode in the method
  channel to avoid collisions (and hangs) under concurrent requests.
- Guard against a nil cgImage (returned without an error) on iOS.
- Add the INTERNET permission to the example app's main manifest so it can
  fetch the remote sample video.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drops "video" from the name to avoid confusion with the StreamVideo
product. Renames the Dart/native classes, files, method channel, and
Android namespace accordingly.

- Dart: StreamThumbnail / StreamThumbnailPlatform /
  MethodChannelStreamThumbnail / StreamThumbnailWeb; barrel is now
  package:stream_thumbnail/stream_thumbnail.dart.
- iOS: StreamThumbnailPlugin; SPM package/product and podspec renamed;
  method channel plugins.getstream.io/stream_thumbnail.
- Android: io.getstream.stream_thumbnail namespace + StreamThumbnailPlugin.
- Update the CI changelog-scope path in pr_title.yml.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xsahil03x xsahil03x changed the title feat(thumb): add stream_video_thumbnail plugin feat(thumb): add stream_thumbnail plugin Jul 21, 2026
xsahil03x and others added 2 commits July 21, 2026 14:37
Keeps the original `Copyright (c) 2019 John Zhong` notice alongside
Stream's, as required by the MIT license for the forked sources.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Rename the example stream_video_thumbnail_example -> stream_thumbnail_example
  (pubspec, Android namespace/package dir, iOS bundle id).
- Trim example/pubspec.yaml to essentials (drop flutter-create boilerplate,
  cupertino_icons, and flutter_lints).
- Remove example/analysis_options.yaml so it inherits the workspace config.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/stream_thumbnail/lib/src/stream_thumbnail_method_channel.dart (1)

156-159: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle non-true results safely.

In the event that invokeMethod returns null or a non-Exception string error when result != true, passing it directly to _resolveFuture will cause a runtime TypeError. This happens because _resolveFuture expects a non-nullable Object while Completer<T> enforces generic type checks on complete(value).

Wrap the non-true result in an Exception before resolving it to avoid crashes:

  • packages/stream_thumbnail/lib/src/stream_thumbnail_method_channel.dart#L156-L159: Update thumbnailFiles to safely wrap result.
  • packages/stream_thumbnail/lib/src/stream_thumbnail_method_channel.dart#L190-L193: Update thumbnailFile to safely wrap result.
  • packages/stream_thumbnail/lib/src/stream_thumbnail_method_channel.dart#L222-L225: Update thumbnailData to safely wrap result.
🛠️ Proposed fix (apply to all sites)
     if (result != true) {
-      _resolveFuture(callId, result);
+      _resolveFuture(callId, result is Exception ? result : Exception(result?.toString() ?? 'Unknown error'));
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/stream_thumbnail/lib/src/stream_thumbnail_method_channel.dart`
around lines 156 - 159, Update thumbnailFiles, thumbnailFile, and thumbnailData
so every non-true invokeMethod result is converted to a non-null Exception
before being passed to _resolveFuture, including null and string errors. Apply
the same safe wrapping at
packages/stream_thumbnail/lib/src/stream_thumbnail_method_channel.dart lines
156-159, 190-193, and 222-225.
🧹 Nitpick comments (2)
packages/stream_thumbnail/example/README.md (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align the example project name with the renamed package. The PR objectives mention renaming the plugin from stream_video_thumbnail to stream_thumbnail. The example project still uses the old name in several places; consider updating these to maintain consistency.

  • packages/stream_thumbnail/example/README.md#L1-L1: Update the title to # stream_thumbnail_example.
  • packages/stream_thumbnail/example/android/app/build.gradle.kts#L7-L26: Update the namespace and applicationId to io.getstream.stream_thumbnail_example.
  • packages/stream_thumbnail/example/ios/Runner/Info.plist#L9-L20: Update CFBundleDisplayName to Stream Thumbnail Example and CFBundleName to stream_thumbnail_example.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/stream_thumbnail/example/README.md` at line 1, Align the example
project naming with the renamed stream_thumbnail package: update the title in
packages/stream_thumbnail/example/README.md lines 1-1 to
stream_thumbnail_example; update the namespace and applicationId in
packages/stream_thumbnail/example/android/app/build.gradle.kts lines 7-26 to
io.getstream.stream_thumbnail_example; and update CFBundleDisplayName and
CFBundleName in packages/stream_thumbnail/example/ios/Runner/Info.plist lines
9-20 to Stream Thumbnail Example and stream_thumbnail_example.
packages/stream_thumbnail/example/web/index.html (1)

26-32: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update leftover references to the old plugin name.

The plugin was renamed from stream_video_thumbnail to stream_thumbnail, but the example app's web metadata still uses the old name. Consider updating these strings for consistency.

  • packages/stream_thumbnail/example/web/index.html#L26-L32: Change the apple-mobile-web-app-title and <title> content to stream_thumbnail_example.
  • packages/stream_thumbnail/example/web/manifest.json#L2-L3: Change the name and short_name to stream_thumbnail_example.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/stream_thumbnail/example/web/index.html` around lines 26 - 32,
Update the web metadata to use the renamed plugin identifier: in
packages/stream_thumbnail/example/web/index.html lines 26-32, change both
apple-mobile-web-app-title and title to stream_thumbnail_example; in
packages/stream_thumbnail/example/web/manifest.json lines 2-3, change name and
short_name to stream_thumbnail_example.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/stream_thumbnail/android/src/main/kotlin/io/getstream/stream_thumbnail/StreamThumbnailPlugin.kt`:
- Around line 108-112: Update onAttachedToEngine to recreate executor when the
existing instance is shut down, ensuring subsequent method calls use a live
executor after re-attachment. Keep the existing shutdown behavior in
onDetachedFromEngine unchanged.
- Around line 108-112: Reinitialize executor in onAttachedToEngine when the
existing executor is shut down, creating a fresh executor before handling
subsequent method calls. Preserve the existing shutdown behavior in
onDetachedFromEngine and avoid replacing an active executor.

In `@packages/stream_thumbnail/example/analysis_options.yaml`:
- Around line 12-26: Update the analysis configuration around the linter
settings in analysis_options.yaml to configure the project’s line width as 120
characters, using the appropriate formatter configuration key while preserving
the existing lint rules.

In `@packages/stream_thumbnail/example/ios/Runner.xcodeproj/project.pbxproj`:
- Line 63: Update the example Xcode project’s PBXFileReference path from
../../ios/stream_video_thumbnail to ../../ios/stream_thumbnail, and rename all
PRODUCT_BUNDLE_IDENTIFIER values using streamVideoThumbnailExample to the
corresponding streamThumbnailExample identifiers, including RunnerTests targets,
so no stale plugin name remains.

In
`@packages/stream_thumbnail/ios/stream_thumbnail/Sources/stream_thumbnail/StreamThumbnailPlugin.m`:
- Line 50: The file method must fulfill the thumbnailFile contract instead of
returning NSData directly. Update the generateThumbnail flow to write the
thumbnail data to a temporary file, then dispatch the Flutter result handling to
the main thread and use the established cross-platform file-result protocol
(including the corresponding Dart _resolveFuture handling) so the caller
receives an XFile-compatible result rather than a raw NSData or unsupported path
string.
- Line 50: The file method must fulfill the Dart file contract instead of
returning raw NSData. Update StreamThumbnailPlugin's thumbnail generation flow
to write the generated data to a temporary file, then dispatch the Flutter
result callback to the main thread and use the established cross-platform
file-result protocol (including the corresponding Dart _resolveFuture handling)
so thumbnailFile resolves an XFile rather than a Uint8List or unsupported
String.

---

Outside diff comments:
In `@packages/stream_thumbnail/lib/src/stream_thumbnail_method_channel.dart`:
- Around line 156-159: Update thumbnailFiles, thumbnailFile, and thumbnailData
so every non-true invokeMethod result is converted to a non-null Exception
before being passed to _resolveFuture, including null and string errors. Apply
the same safe wrapping at
packages/stream_thumbnail/lib/src/stream_thumbnail_method_channel.dart lines
156-159, 190-193, and 222-225.

---

Nitpick comments:
In `@packages/stream_thumbnail/example/README.md`:
- Line 1: Align the example project naming with the renamed stream_thumbnail
package: update the title in packages/stream_thumbnail/example/README.md lines
1-1 to stream_thumbnail_example; update the namespace and applicationId in
packages/stream_thumbnail/example/android/app/build.gradle.kts lines 7-26 to
io.getstream.stream_thumbnail_example; and update CFBundleDisplayName and
CFBundleName in packages/stream_thumbnail/example/ios/Runner/Info.plist lines
9-20 to Stream Thumbnail Example and stream_thumbnail_example.

In `@packages/stream_thumbnail/example/web/index.html`:
- Around line 26-32: Update the web metadata to use the renamed plugin
identifier: in packages/stream_thumbnail/example/web/index.html lines 26-32,
change both apple-mobile-web-app-title and title to stream_thumbnail_example; in
packages/stream_thumbnail/example/web/manifest.json lines 2-3, change name and
short_name to stream_thumbnail_example.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5505b57c-4e15-44ee-9ec3-818be53f743c

📥 Commits

Reviewing files that changed from the base of the PR and between 8e98f8d and b0fbafb.

⛔ Files ignored due to path filters (32)
  • packages/stream_thumbnail/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata is excluded by !**/*.xcworkspace/contents.xcworkspacedata
  • packages/stream_thumbnail/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved is excluded by !**/Package.resolved
  • packages/stream_thumbnail/example/ios/Runner.xcworkspace/contents.xcworkspacedata is excluded by !**/*.xcworkspace/contents.xcworkspacedata
  • packages/stream_thumbnail/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved is excluded by !**/Package.resolved
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/web/favicon.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/web/icons/Icon-192.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/web/icons/Icon-512.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/web/icons/Icon-maskable-192.png is excluded by !**/*.png
  • packages/stream_thumbnail/example/web/icons/Icon-maskable-512.png is excluded by !**/*.png
📒 Files selected for processing (61)
  • .github/workflows/pr_title.yml
  • packages/stream_thumbnail/CHANGELOG.md
  • packages/stream_thumbnail/LICENSE
  • packages/stream_thumbnail/README.md
  • packages/stream_thumbnail/android/build.gradle.kts
  • packages/stream_thumbnail/android/gradle.properties
  • packages/stream_thumbnail/android/gradle/wrapper/gradle-wrapper.properties
  • packages/stream_thumbnail/android/settings.gradle.kts
  • packages/stream_thumbnail/android/src/main/AndroidManifest.xml
  • packages/stream_thumbnail/android/src/main/kotlin/io/getstream/stream_thumbnail/StreamThumbnailPlugin.kt
  • packages/stream_thumbnail/example/.metadata
  • packages/stream_thumbnail/example/README.md
  • packages/stream_thumbnail/example/analysis_options.yaml
  • packages/stream_thumbnail/example/android/app/build.gradle.kts
  • packages/stream_thumbnail/example/android/app/src/debug/AndroidManifest.xml
  • packages/stream_thumbnail/example/android/app/src/main/AndroidManifest.xml
  • packages/stream_thumbnail/example/android/app/src/main/kotlin/io/getstream/stream_video_thumbnail_example/MainActivity.kt
  • packages/stream_thumbnail/example/android/app/src/main/res/drawable-v21/launch_background.xml
  • packages/stream_thumbnail/example/android/app/src/main/res/drawable/launch_background.xml
  • packages/stream_thumbnail/example/android/app/src/main/res/values-night/styles.xml
  • packages/stream_thumbnail/example/android/app/src/main/res/values/styles.xml
  • packages/stream_thumbnail/example/android/app/src/profile/AndroidManifest.xml
  • packages/stream_thumbnail/example/android/build.gradle.kts
  • packages/stream_thumbnail/example/android/gradle.properties
  • packages/stream_thumbnail/example/android/gradle/wrapper/gradle-wrapper.properties
  • packages/stream_thumbnail/example/android/settings.gradle.kts
  • packages/stream_thumbnail/example/ios/Flutter/AppFrameworkInfo.plist
  • packages/stream_thumbnail/example/ios/Flutter/Debug.xcconfig
  • packages/stream_thumbnail/example/ios/Flutter/Release.xcconfig
  • packages/stream_thumbnail/example/ios/Runner.xcodeproj/project.pbxproj
  • packages/stream_thumbnail/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  • packages/stream_thumbnail/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  • packages/stream_thumbnail/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  • packages/stream_thumbnail/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  • packages/stream_thumbnail/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  • packages/stream_thumbnail/example/ios/Runner/AppDelegate.swift
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
  • packages/stream_thumbnail/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
  • packages/stream_thumbnail/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
  • packages/stream_thumbnail/example/ios/Runner/Base.lproj/Main.storyboard
  • packages/stream_thumbnail/example/ios/Runner/Info.plist
  • packages/stream_thumbnail/example/ios/Runner/Runner-Bridging-Header.h
  • packages/stream_thumbnail/example/ios/Runner/SceneDelegate.swift
  • packages/stream_thumbnail/example/ios/RunnerTests/RunnerTests.swift
  • packages/stream_thumbnail/example/lib/main.dart
  • packages/stream_thumbnail/example/pubspec.yaml
  • packages/stream_thumbnail/example/web/index.html
  • packages/stream_thumbnail/example/web/manifest.json
  • packages/stream_thumbnail/ios/stream_thumbnail.podspec
  • packages/stream_thumbnail/ios/stream_thumbnail/Package.swift
  • packages/stream_thumbnail/ios/stream_thumbnail/Sources/stream_thumbnail/StreamThumbnailPlugin.m
  • packages/stream_thumbnail/ios/stream_thumbnail/Sources/stream_thumbnail/include/stream_thumbnail/StreamThumbnailPlugin.h
  • packages/stream_thumbnail/lib/src/stream_thumbnail.dart
  • packages/stream_thumbnail/lib/src/stream_thumbnail_format.dart
  • packages/stream_thumbnail/lib/src/stream_thumbnail_method_channel.dart
  • packages/stream_thumbnail/lib/src/stream_thumbnail_platform.dart
  • packages/stream_thumbnail/lib/stream_thumbnail.dart
  • packages/stream_thumbnail/lib/stream_thumbnail_web.dart
  • packages/stream_thumbnail/pubspec.yaml
  • packages/stream_thumbnail/test/stream_thumbnail_test.dart

Comment thread packages/stream_thumbnail/example/analysis_options.yaml Outdated
Comment thread packages/stream_thumbnail/example/ios/Runner.xcodeproj/project.pbxproj Outdated
xsahil03x and others added 2 commits July 21, 2026 14:54
… executor reuse

- Android: recreate the thread-pool executor on engine re-attachment
  (add-to-app / engine restart) instead of submitting to a shut-down one.
- iOS: deliver method-channel results on the main thread while keeping the
  heavy thumbnail work on a background queue.
- thumbnailFile: wrap the path iOS returns in an XFile so the Future<XFile>
  contract holds (Android already replies via the 'result#file' callback).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- iOS WebP path: stop over-releasing the get-refs colorSpace/dataProvider
  (double-free), release the owned cgImage, and free the libwebp output buffer
  via WebPFree — fixes memory leaks and a potential crash.
- Dart: remove the pending completer from _futures if invokeMethod throws, so
  failed calls don't accumulate in memory.
- Web: time out a stalled video and release the <video> element once the result
  settles, so a call can't hang (and retain the element) forever.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xsahil03x
xsahil03x merged commit 27ba635 into main Jul 21, 2026
10 of 13 checks passed
@xsahil03x
xsahil03x deleted the feat/stream-video-thumbnail branch July 21, 2026 14:08
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.

2 participants