Skip to content

Conversation

@yrendaoai
Copy link

Fix: Support LiveKit Client in Non-Secure Context (HTTP Environment)

Problem

The original code directly calls navigator.mediaDevices APIs without checking for secure context. In HTTP environments (non-HTTPS), these APIs are restricted by browser security policies:

  • navigator.mediaDevices may be undefined
  • addEventListener('devicechange') fails even if mediaDevices exists
  • getUserMedia() calls throw errors

This causes the WebRTC video player to fail in HTTP environments, breaking the live video streaming functionality.

Solution

  • Added secure context checks before accessing navigator.mediaDevices APIs

Changes

1. Code Changes in Fixed PR

The fixed PR adds security context checks in src/room/Room.ts:

Before:

navigator.mediaDevices?.addEventListener('devicechange', this.handleDeviceChange, {
  signal: abortController.signal,
});

After:

if (window.isSecureContext && navigator.mediaDevices?.addEventListener) {
  navigator.mediaDevices.addEventListener('devicechange', this.handleDeviceChange, {
    signal: abortController.signal,
  });
}

Fixed locations:

  • Device change event listener registration (constructor)
  • Device change event listener removal (cleanup)
  • getUserMedia() calls for video tracks
  • getUserMedia() calls for audio tracks

Testing

  • Tested in HTTP environment - no errors when accessing navigator.mediaDevices
  • Tested in HTTPS environment - functionality works as expected
  • WebRTC video player works correctly in both secure and non-secure contexts
  • Device change events are properly handled when available

Related

This fix ensures compatibility with HTTP environments while maintaining full functionality in HTTPS environments. The changes are backward compatible and do not affect existing HTTPS deployments.

Notes

  • The fixed PR maintains the same API as the original livekit-client
  • All features work identically in secure contexts
  • In non-secure contexts, device change events are gracefully skipped
  • getUserMedia() calls fall back to dummy tracks when not available

Prevents crashes in non-HTTPS environments by checking window.isSecureContext
before accessing navigator.mediaDevices API.
@changeset-bot
Copy link

changeset-bot bot commented Nov 26, 2025

⚠️ No Changeset found

Latest commit: 4438fcb

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@CLAassistant
Copy link

CLAassistant commented Nov 26, 2025

CLA assistant check
All committers have signed the CLA.

- Add window.isSecureContext checks before accessing navigator.mediaDevices
- Prevent crashes in non-HTTPS environments when mediaDevices is undefined
- Add checks for devicechange event listeners (add/remove)
- Add checks for getUserMedia calls (video/audio)
- Gracefully fallback to empty MediaStream in non-secure contexts
src/room/Room.ts Outdated
signal: abortController.signal,
});
if (window.isSecureContext && navigator.mediaDevices?.addEventListener) {
navigator.mediaDevices.addEventListener('devicechange', this.handleDeviceChange, {
Copy link
Contributor

@lukasIO lukasIO Nov 26, 2025

Choose a reason for hiding this comment

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

we use optional chaining for this in other places already, I think this could be used here as well with the same effect as explicitly checking for secureContext?

Suggested change
navigator.mediaDevices.addEventListener('devicechange', this.handleDeviceChange, {
navigator.mediaDevices?.addEventListener('devicechange', this.handleDeviceChange, {

you mention

addEventListener('devicechange') fails even if mediaDevices exists

under which circumstances is this the case?

Copy link
Author

Choose a reason for hiding this comment

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

Hi! Thanks for the question — here’s the context in which addEventListener('devicechange') fails even when navigator.mediaDevices exists.

In my case:

The page was running over HTTP (insecure context).

In this environment, navigator.mediaDevices does exist, but it only exposes methods like getUserMedia.
image

However, mediaDevices does not implement addEventListener, so calling it results in:

TypeError: navigator.mediaDevices.addEventListener is not a function

This matches the spec behavior: devicechange and event handling on mediaDevices are only guaranteed in secure contexts (HTTPS). On HTTP, the object may be present but not fully functional.

So the failure happens specifically when:

  • navigator.mediaDevices exists
  • but the browser/environment doesn’t provide the event interface (e.g., insecure context)

I tested this in:

Chrome Version 142.0.7444.176 (Official Build) (64-bit)

React 18.2.0 (though the framework isn't relevant but who knows; it's a browser API behavior)

Let me know if you'd like me to test in other environments.

Copy link
Author

Choose a reason for hiding this comment

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

For the change suggestion, I got a safer approach would be:

navigator.mediaDevices?.addEventListener?.('devicechange', this.handleDeviceChange,

This ensures we only call the handler when both the object and the method are available, which matches Chrome browser behavior in non-secure contexts.

Happy to make a PR if this approach sounds reasonable!

Copy link
Contributor

Choose a reason for hiding this comment

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

that approach sounds great, thanks!

Copy link
Contributor

@lukasIO lukasIO left a comment

Choose a reason for hiding this comment

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

thanks for the PR and the detailed description!

? (
await window.navigator.mediaDevices.getUserMedia({ video: true })
? (await window.navigator.mediaDevices?.getUserMedia?.({ video: true }) ?? new MediaStream()
).getVideoTracks()[0]
Copy link
Author

Choose a reason for hiding this comment

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

Note: The getUserMedia calls with ?? new MediaStream() fallback (lines 2502, 2529)
ensure we don't get undefined when calling .getVideoTracks()[0] in non-secure contexts
where mediaDevices?.getUserMedia?.() returns undefined due to optional chaining.

Copy link
Contributor

@lukasIO lukasIO Nov 28, 2025

Choose a reason for hiding this comment

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

instead of falling back to new MediaStream() we could simply check for publishOptions.useRealTracks && window.navigator.mediaDevices?.getUserMedia above

Copy link
Contributor

@lukasIO lukasIO left a comment

Choose a reason for hiding this comment

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

thank you, just one more thing and could you run pnpm format locally to ensure the prettier tests aren't failing

? (
await window.navigator.mediaDevices.getUserMedia({ video: true })
? (await window.navigator.mediaDevices?.getUserMedia?.({ video: true }) ?? new MediaStream()
).getVideoTracks()[0]
Copy link
Contributor

@lukasIO lukasIO Nov 28, 2025

Choose a reason for hiding this comment

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

instead of falling back to new MediaStream() we could simply check for publishOptions.useRealTracks && window.navigator.mediaDevices?.getUserMedia above

src/room/Room.ts Outdated
? (window.isSecureContext && navigator.mediaDevices
? await navigator.mediaDevices.getUserMedia({ audio: true })
: new MediaStream()
? (await navigator.mediaDevices?.getUserMedia?.({ audio: true }) ?? new MediaStream()
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above

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.

3 participants