Skip to content

Add video renderer pool prewarming API #839

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from

Conversation

sampepose
Copy link

🎯 Goal

Eliminate UI blocking when video renderers are first created during video calls, improving user experience by preventing 150-200ms hangs.

📝 Summary

  • Add VideoRendererConfiguration.prewarm() public API for creating video renderers proactively
  • Implement yielding mechanism to prevent continuous UI thread blocking during prewarming
  • Fix critical thread safety race condition in ReusePool initialization
  • Add internal pool configuration method with proper async/await handling

🛠 Implementation

New Public API:

// In AppDelegate or early app lifecycle - simple, never blocks
VideoRendererConfiguration.prewarm(count: 4)

Key Implementation Details:

  • VideoRendererConfiguration: New public enum providing prewarm(count:) method that can be called from any thread
  • Yielding Strategy: Creates video renderers one at a time with await Task.yield() between each creation to prevent blocking the UI thread for extended periods
  • Thread Safety Fix: Fixed race condition in ReusePool.init() where initial elements were created outside of the thread-safe queue.sync block
  • Pool Management: Added addToAvailable() method to ReusePool for adding pre-created elements beyond initial capacity

Technical Approach:

  • Video renderers must be created on the main thread (UIView requirement), but prewarming spreads the work across multiple runloop cycles
  • Default pool capacity remains 0 (no behavior change without explicit prewarming)
  • Frame size during creation is irrelevant as it gets overwritten when renderers are acquired for actual use

🧪 Manual Testing Notes

  1. Without Prewarming: Create a video call and observe ~150-200ms delay when first video view appears
  2. With Prewarming: Call VideoRendererConfiguration.prewarm(count: 2) early in app lifecycle, then create video call - should see immediate video appearance
  3. UI Responsiveness: During prewarming, verify UI remains responsive (no continuous blocking)
  4. Thread Safety: Call prewarming from background threads to ensure no crashes

☑️ Contributor Checklist

  • I have signed the Stream CLA (required)
  • This change follows zero ⚠️ policy (required)
  • This change should receive manual QA
  • Changelog is updated with client-facing changes
  • New code is covered by unit tests
  • Affected documentation updated (tutorial, CMS)

- Add VideoRendererConfiguration public API for prewarming video renderers
- Add internal configure method to VideoRendererPool for capacity management
- Fix thread safety issue in ReusePool initialization
- Eliminates 150-200ms UI hangs when video views are first created
@sampepose sampepose requested a review from a team as a code owner June 8, 2025 21:42
/// Used for prewarming to add elements beyond initial capacity.
///
/// - Parameter element: The pre-created element to add to the pool.
func addToAvailable(_ element: Element) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This method defies the Pool's purpose. The pool is responsible for creating its elements on demand.

///
/// - Parameter initialCapacity: The number of video renderers to pre-create.
@MainActor
static func configure(initialCapacity: Int) async {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is what is happening on the reusePool initialisation. I don't see a reason for this method.

Copy link
Author

Choose a reason for hiding this comment

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

The default value is 0: https://github.com/GetStream/stream-video-swift/blob/develop/Sources/StreamVideoSwiftUI/Utils/VideoRendererPool/VideoRendererPool.swift#L20

This entire PR could be removed if that default value is changed, but I didn't want to change the default behavior for all users unexpectedly. So this PR is just a way to expose that number.

/// // Then later initialize StreamVideo as usual
/// let streamVideo = StreamVideo(apiKey: apiKey, user: user, token: token)
/// ```
public static func prewarm(count: Int = 2) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This method looks unnecessary. Can you elaborate more on why this is needed and why the existing warm up on init isn't sufficient?

let element = factory()
available.append(element)
// Initialize the pool with a set number of elements (thread-safe)
queue.sync {
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm i'm not sure about this one. Even without the sync, there aren't multiple threads accessing the initialiser at the same (if they were they would be creating different instances).

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