Troubleshooting
Solutions to common issues when implementing Native Frame
This guide covers common issues you might encounter when implementing Native Frame and provides solutions to resolve them quickly.
Camera and Microphone Issues
Problem: The preview player shows a black screen or no audio is captured.
Common causes and solutions:
-
Permissions not granted
- Check browser permissions in the address bar or browser settings
- The browser should prompt for camera/microphone access on first use
- On mobile, check device Settings → Safari/Chrome → Camera/Microphone permissions
-
Camera/mic in use by another application
- Close other applications that might be using the camera (Zoom, Skype, etc.)
- Refresh the browser page to release any stale media streams
-
Media stream not initialized
// React: Ensure usePreviewPlayer has completed initializationconst { mediaStreamController, previewPlayer, isLoading } = usePreviewPlayer();if (isLoading) {return <div>Loading camera...</div>;}// Vanilla JS: Wait for requestEncoder to completeconst { mediaStreamController, previewPlayer } = await requestEncoder(); -
Video element not properly attached
// React: Wrap components with PlayerAPIProvider<PlayerAPIProvider player={previewPlayer}><Video /></PlayerAPIProvider>// Vanilla JS: Ensure player.attach() is calledpreviewPlayer.attach(videoElement); -
Device not available
- Check if the device actually has a camera/microphone
- Try unplugging and replugging external USB cameras
- Check for hardware switches (some laptops have physical camera disable switches)
Problem: Browser throws permission error when requesting camera/microphone.
Solutions:
-
Reset browser permissions
- Chrome: Click the lock icon in address bar → Site settings → Reset permissions
- Safari: Safari menu → Settings → Websites → Camera/Microphone → Remove site
- Firefox: Click the shield icon → Clear permissions
-
HTTPS requirement
- Camera/microphone access requires HTTPS (except localhost)
- Ensure your site is served over HTTPS in production
- For local development, use
https://localhostorhttp://localhost
-
Check browser compatibility
- Ensure you're using a supported browser (Chrome, Safari, Firefox, Edge)
- Update to the latest browser version
- Some older browsers don't support
getUserMediaAPI
-
Handle permission denial gracefully
try {const { mediaStreamController } = usePreviewPlayer();} catch (error) {if (error.name === 'NotAllowedError') {// Show UI prompting user to grant permissionsshowPermissionDeniedMessage();}}
Problem: Device selection dropdown doesn't work or switching devices fails.
Solutions:
-
Enumerate devices before selecting
// Get available devicesconst { audioDevices, videoDevices } = await mediaController.enumerateDevices();// Select specific deviceawait mediaStreamController.setVideoDevice(videoDevices[0].deviceId);await mediaStreamController.setAudioDevice(audioDevices[0].deviceId); -
Wait for device list to populate
- Device labels may be empty until permissions are granted
- Request permissions first, then enumerate devices
-
Handle device errors
try {await mediaStreamController.setVideoDevice(deviceId);} catch (error) {console.error('Failed to switch camera:', error);// Fall back to default device or show error to user} -
Check device constraints
- Some devices may not support requested resolutions
- Try setting device without additional constraints first
Problem: Preview video appears flipped or inverted.
Solutions:
-
Mirror the preview for better UX
/* Mirror the video element for preview (like a mirror) */video.preview {transform: scaleX(-1);}Note: This only affects the local preview. Viewers will see the correct orientation.
-
Handle device orientation
- Mobile devices may need orientation handling
- Use CSS transforms to adjust based on device orientation
- Consider using the Screen Orientation API
-
Rotation issues
- Some cameras (especially external USB cameras) may have incorrect rotation metadata
- This is typically a hardware/driver issue
- Can be corrected with CSS transforms on the video element
Connection and Network Issues
Problem: Call creation fails with connection error.
Common causes and solutions:
-
Invalid authentication
// Verify token is valid and not expiredconst authClient = new BaseAuthClient({authUrl: 'https://your-subdomain.nativeframe.com/api/auth',token: validBroadcasterToken, // Must be valid JWT}); -
Incorrect backend endpoint
// Ensure backend endpoint is correct (include https://)const backendEndpoints = ['https://your-subdomain.nativeframe.com' // Correct format];// Common mistakes:// ❌ 'your-subdomain.nativeframe.com' (missing https://)// ❌ 'https://nativeframe.com' (missing subdomain) -
Invalid stream key
- Verify stream key is correct and active
- Stream keys are case-sensitive
- Contact Native Frame if you need to regenerate your stream key
-
Network/firewall blocking WebRTC
- Check if corporate firewall blocks WebRTC traffic
- Verify ports are open: UDP 16384-32767, TCP 443
- Try from different network to isolate issue
-
CORS issues
- Ensure your domain is whitelisted with Native Frame
- Check browser console for CORS errors
- Verify
clientReferrermatches your domain
Problem: Call connects but disconnects unexpectedly.
Solutions:
-
Handle connection state changes
call.on('connectionStateChange', (state) => {console.log('Connection state:', state);if (state === 'disconnected') {// Attempt reconnectionattemptReconnect();}}); -
Network instability
- Poor WiFi or cellular connection
- Try wired connection if possible
- Check network speed (recommend 5+ Mbps upload for HD broadcasting)
-
Token expiration
- Implement token refresh before expiration
const authClient = new BaseAuthClient({token: currentToken,refreshToken: async () => {// Fetch new token from your backendconst newToken = await fetchNewToken();return newToken;}}); -
Browser tab backgrounded (mobile)
- Mobile browsers may suspend WebRTC when tab is backgrounded
- Warn users to keep app in foreground
- Consider implementing reconnection logic
-
Server maintenance
- Check Native Frame status page for service interruptions
- Implement retry logic with exponential backoff
Problem: Video is low quality, pixelated, or buffers frequently.
Solutions:
-
Network bandwidth insufficient
- Check upload speed for broadcaster:
speedtest.net - Check download speed for viewer:
speedtest.net - Recommended: 5+ Mbps upload (broadcast), 3+ Mbps download (view)
- Check upload speed for broadcaster:
-
Adjust video constraints
// Lower resolution for poor connectionsconst constraints = {video: {width: { ideal: 640 }, // Lower from 1280height: { ideal: 480 }, // Lower from 720frameRate: { ideal: 24 } // Lower from 30}}; -
Enable adaptive bitrate
- Native Frame automatically provides adaptive bitrate
- Ensure viewer player is configured to use ABR
- Multiple quality levels will be available based on network
-
CPU/GPU limitations
- Encoding HD video requires significant CPU
- Close other applications
- Lower resolution/framerate if device struggles
- Check CPU usage during broadcast
-
Server selection
- Ensure using the closest backend endpoint
- Geographic distance affects latency
- Configure multiple endpoints for failover
Problem: Significant delay (5+ seconds) between broadcasting and viewing.
Solutions:
-
Use WebRTC for lowest latency
- Ensure using Video Client SDK (not HLS/manifest)
- Direct call viewing has lowest latency (< 1 second)
- Manifest/HLS playback has higher latency (3-30 seconds)
-
Check network conditions
- Latency increases with poor network conditions
- Use wired connections when possible
- Minimize network hops
-
Server geographic distance
- Choose backend endpoint closest to broadcaster
- Viewers should connect to same region
- Cross-region streaming adds latency
-
Browser/device performance
- Older devices may have processing delays
- Close unnecessary browser tabs
- Disable browser extensions that might interfere
-
Expected latency ranges
- WebRTC direct: 0.5-2 seconds (glass-to-glass)
- HLS: 6-30 seconds
- Some latency is normal and expected
Authentication and Authorization Issues
Problem: Requests fail with authentication errors.
Solutions:
-
Verify token format and validity
// Token should be a valid JWT stringconst token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';// Check token hasn't expiredconst payload = JSON.parse(atob(token.split('.')[1]));const isExpired = payload.exp * 1000 < Date.now();console.log('Token expired:', isExpired); -
Correct token scope
- Broadcaster tokens: Required for creating calls and broadcasting
- Viewer tokens: Required for viewing streams
- Private viewer tokens: Required for private/direct streams
- Ensure token has appropriate scope for the operation
-
Token refresh implementation
const authClient = new BaseAuthClient({authUrl: 'https://your-subdomain.nativeframe.com/api/auth',token: initialToken,refreshToken: async () => {// Implement token refresh logicconst response = await fetch('/api/refresh-token');const { token } = await response.json();return token;}}); -
Authorization URL mismatch
- Ensure
authUrlmatches your backend endpoint - URL should include
/api/authpath - Verify HTTPS is used
- Ensure
-
Contact Native Frame
- If issues persist, verify your account is active
- Check if your domain is whitelisted
- Confirm your credentials are correct
Problem: Operation fails with permission error.
Solutions:
-
Verify token scope matches operation
// Creating call requires broadcaster tokenconst call = await createCall({auth: broadcasterAuthClient, // ✓ Correct// auth: viewerAuthClient, // ✗ Will fail});// Viewing stream requires viewer tokenconst player = await createPlayer({auth: viewerAuthClient, // ✓ Correct}); -
Check stream key permissions
- Ensure stream key is associated with your account
- Verify stream key hasn't been revoked
- Confirm stream key allows the requested operation
-
Private streaming permissions
- Private streams require special viewer tokens
- Ensure private viewer token includes correct permissions
- Verify viewer is authorized for specific stream
-
Account limitations
- Check if your account tier supports the feature
- Verify concurrent stream limits
- Confirm your account is active and in good standing
Broadcasting Issues
Problem: call.broadcast() fails or throws error.
Solutions:
-
Ensure call is connected first
// ✓ Correct: Wait for call to be createdconst call = await createCall(options);const broadcast = await call.broadcast(mediaStreamController, broadcastOptions);// ✗ Wrong: Trying to broadcast without callconst broadcast = await call.broadcast(...); // call is undefined -
Verify media stream controller
// Ensure mediaStreamController is initializedconst { mediaStreamController } = usePreviewPlayer();if (!mediaStreamController) {console.error('Media stream controller not ready');return;}// Check that media tracks existconst tracks = mediaStreamController.getTracks();console.log('Available tracks:', tracks); -
Check stream name
// Stream name is required and must be non-emptyconst broadcastOptions = {streamName: 'default', // ✓ Valid// streamName: '', // ✗ Invalid}; -
Network/firewall blocking
- Verify WebRTC traffic isn't blocked
- Check browser console for ICE connection errors
- Try from different network
-
Call state issues
// Check call state before broadcastingif (call.state === 'connected') {const broadcast = await call.broadcast(...);}
Problem: Broadcast starts successfully but stops without user action.
Solutions:
-
Handle broadcast lifecycle events
broadcast.on('stateChange', (state) => {console.log('Broadcast state:', state);if (state === 'stopped') {console.log('Broadcast stopped reason:', broadcast.stopReason);// Handle reconnection or show error to user}}); -
Camera/microphone disconnected
- Check if device was unplugged
- Handle device change events
navigator.mediaDevices.addEventListener('devicechange', async () => {console.log('Device list changed');// Re-enumerate devices and update UI}); -
Memory/resource exhaustion
- Broadcasting for extended periods may cause issues
- Ensure proper disposal of old streams
- Monitor browser memory usage
-
Network interruption
- Poor network caused connection drop
- Implement automatic reconnection logic
- Show connection quality indicators to broadcaster
-
Token expiration during broadcast
- Ensure token refresh is implemented
- Refresh tokens proactively before expiration
- Handle auth refresh during active broadcast
Problem: Broadcast appears successful but viewers can't see it.
Solutions:
-
Verify call ID sharing
// Broadcaster: Get and share call IDconst call = await createCall(options);console.log('Share this call ID with viewers:', call.id);// Viewer: Use correct call IDconst player = await createPlayer({callId: 'call-id-from-broadcaster', // Must match exactly}); -
Check viewer authentication
- Viewers need valid viewer tokens
- For private streams, viewers need private viewer tokens
- Verify viewer token hasn't expired
-
Broadcast state verification
// Check broadcast is actually activeconsole.log('Broadcast state:', broadcast.state);console.log('Broadcast stream name:', broadcast.streamName);// Viewer must request same stream nameconst player = await createPlayer({callId: callId,streamName: 'default' // Must match broadcaster's streamName}); -
Network/firewall on viewer side
- Viewer's network may block WebRTC
- Test from different network/device
- Check viewer's browser console for errors
-
Timing issues
- Viewer connected before broadcast started
- Have viewer refresh and reconnect
- Implement proper player state handling
Problem: Audio doesn't match video timing.
Solutions:
-
Check device/browser compatibility
- Some devices have audio/video sync issues
- Try different browser
- Update browser to latest version
-
Network jitter
- Unstable network can cause sync issues
- Check for packet loss
- Use wired connection if possible
-
Processing delays
- CPU overload can cause sync issues
- Close other applications
- Lower video quality/resolution
-
Browser bugs
- Some browsers have known sync issues
- Clear browser cache and restart
- Try incognito/private mode
-
Hardware acceleration
- Enable hardware acceleration in browser settings
- Chrome: Settings → Advanced → System → Hardware acceleration
- This can improve sync by offloading video processing
Viewing and Playback Issues
Problem: Player shows black screen instead of video.
Solutions:
-
Verify player initialization
// React: Ensure PlayerAPIProvider wraps Video component<PlayerAPIProvider player={player}><Video /></PlayerAPIProvider>// Vanilla JS: Ensure attach is calledplayer.attach(videoElement); -
Check video element dimensions
/* Video element needs width and height */video {width: 640px;height: 480px;}/* Or use aspect ratio */video {width: 100%;aspect-ratio: 16/9;} -
Verify broadcast is active
- Ensure broadcaster is actually streaming
- Check call ID is correct
- Verify stream name matches
-
Player not loaded
// Wait for player ready eventplayer.on('ready', () => {console.log('Player ready to display video');});// Check player stateconsole.log('Player state:', player.state); -
Browser codec support
- Some browsers don't support all video codecs
- Try different browser
- Check for hardware acceleration settings
Problem: Viewer cannot find the stream.
Solutions:
-
Verify call ID
- Double-check call ID is copied correctly
- Call IDs are case-sensitive
- Ensure no extra spaces or characters
-
Broadcast timing
- Ensure broadcaster has created the call
- Verify broadcast is active
- Call must exist before viewer can join
-
Stream name mismatch
// Broadcasterconst broadcast = await call.broadcast(msc, { streamName: 'default' });// Viewer must use same stream nameconst player = await createPlayer({callId: callId,streamName: 'default' // Must match}); -
Call already ended
- Broadcaster may have ended the call
- Call IDs are typically session-specific
- Need new call ID for new session
-
Authorization issues
- Viewer may not have permission to view stream
- Check viewer token is valid
- For private streams, ensure proper authorization
Problem: Playback is choppy with frequent pauses.
Solutions:
-
Network bandwidth
- Check viewer's internet speed
- Close bandwidth-heavy applications
- Reduce video quality if available
-
CPU/GPU overload
- Close other applications and browser tabs
- Enable hardware acceleration
- Try lower quality stream
-
Browser performance
// Monitor buffer healthplayer.on('buffering', (buffering) => {if (buffering) {console.log('Player is buffering...');}});// Check frame dropsplayer.getStats((stats) => {console.log('Dropped frames:', stats.droppedFrames);}); -
Multiple streams
- Viewing multiple streams simultaneously causes strain
- Limit number of concurrent streams
- Prioritize important streams
-
Browser extensions
- Ad blockers or privacy extensions may interfere
- Try in incognito/private mode
- Disable extensions temporarily
Problem: Can hear stream audio but video doesn't show.
Solutions:
-
Video element rendering
- Ensure video element is visible in DOM
- Check CSS doesn't hide video element
/* Check for these issues */video {display: block; /* Not 'none' */visibility: visible; /* Not 'hidden' */opacity: 1; /* Not 0 */} -
Video track availability
// Check if video track existsconst tracks = player.getTracks();const hasVideo = tracks.some(track => track.kind === 'video');console.log('Has video track:', hasVideo); -
Broadcaster camera disabled
- Broadcaster may have camera turned off
- Only audio is being broadcast
- Have broadcaster check camera state
-
Player configuration
// Ensure player accepts videoconst player = await createPlayer({video: true, // Enable videoaudio: true,}); -
Codec compatibility
- Browser may not support video codec
- Check console for codec errors
- Try different browser
Group Call Issues
Problem: Participant unable to join group call.
Solutions:
-
Verify call ID from owner
- Ensure using correct call ID
- Call must be created by owner first
- Call ID must be shared exactly as provided
-
Check participant token
// Participant needs valid tokenconst authClient = new BaseAuthClient({token: participantToken, // Must be valid}); -
Waiting room enabled
- Owner may need to approve join request
- Check if waiting in lobby
- Owner must admit participant
-
Call capacity reached
- Group calls may have participant limits
- Check if call is at max capacity
- Wait for spot to open or contact owner
-
Network/permission issues
- Same troubleshooting as regular connection issues
- Verify camera/mic permissions
- Check network connectivity
Problem: Joined call but don't see other participants' video.
Solutions:
-
Check remote stream rendering
// Listen for remote streamscall.on('remoteStreamAdded', (stream) => {console.log('Remote stream added:', stream);// Render remote stream in video elementrenderRemoteStream(stream);}); -
Other participants not broadcasting
- Verify other participants have cameras enabled
- Check if participants are actually broadcasting
- Only broadcasters will be visible
-
Subscription issues
// Ensure subscribed to remote streamsawait call.subscribe(remoteStream); -
Network bandwidth
- Multiple streams require significant bandwidth
- May need to reduce quality
- Close other applications
-
Render all remote participants
// Maintain list of remote streamsconst remoteStreams = new Map();call.on('remoteStreamAdded', (stream) => {remoteStreams.set(stream.id, stream);updateParticipantGrid();});call.on('remoteStreamRemoved', (streamId) => {remoteStreams.delete(streamId);updateParticipantGrid();});
Problem: Participants bypass waiting room or approval doesn't work.
Solutions:
-
Verify waiting room configuration
// Enable waiting room when creating callconst call = await createCall({waitingRoom: {enabled: true,},// ... other options}); -
Handle admission events
// Owner: Listen for join requestscall.on('participantJoinRequest', (participant) => {console.log('Join request from:', participant);// Admit participantawait call.admitParticipant(participant.id);// Or denyawait call.denyParticipant(participant.id);}); -
Participant not waiting
// Participant: Show waiting UIcall.on('waitingRoomEntered', () => {showWaitingUI();});call.on('waitingRoomExited', () => {hideWaitingUI();// Admitted to call}); -
Permission issues
- Owner must have permission to admit/deny
- Verify owner role/permissions
- Check token scopes
Canvas and Screen Sharing Issues
Problem: Canvas element not broadcasting or shows black.
Solutions:
-
Verify canvas stream capture
// Ensure canvas is capturing correctlyconst canvas = document.getElementById('myCanvas');const stream = canvas.captureStream(30); // 30 FPS// Check stream has tracksconsole.log('Canvas stream tracks:', stream.getTracks()); -
Canvas dimensions
// Canvas must have valid dimensionscanvas.width = 1280;canvas.height = 720;// Or use CSS sizingcanvas.style.width = '100%'; -
Canvas content rendering
- Ensure canvas is actually drawn to
- Canvas must have visible content
const ctx = canvas.getContext('2d');ctx.fillStyle = 'blue';ctx.fillRect(0, 0, canvas.width, canvas.height);// Now capture will show blue canvas -
Frame rate
// Specify frame rate when capturingconst stream = canvas.captureStream(30); // 30 FPS// Don't use 0 or omit (causes variable frame rate) -
Canvas context
- Use '2d' context for standard canvas
- WebGL contexts may have different capture behavior
- Ensure preserveDrawingBuffer for WebGL
const gl = canvas.getContext('webgl', {preserveDrawingBuffer: true});
Problem: getDisplayMedia fails or screen share doesn't start.
Solutions:
-
HTTPS requirement
- Screen sharing requires HTTPS (except localhost)
- Ensure using https:// in production
- For development, use https://localhost
-
User cancelled selection
// Handle user cancellationtry {const stream = await navigator.mediaDevices.getDisplayMedia({video: true,audio: true});} catch (error) {if (error.name === 'NotAllowedError') {console.log('User cancelled screen share');// Show appropriate message}} -
Browser support
- Ensure browser supports
getDisplayMedia - Chrome, Firefox, Edge support it
- Safari requires recent version (13+)
- Ensure browser supports
-
Check screen share constraints
const stream = await navigator.mediaDevices.getDisplayMedia({video: {cursor: 'always', // Include cursordisplaySurface: 'monitor' // Or 'window', 'browser'},audio: true // Include system audio if needed}); -
Handle screen share stop
// Detect when user stops screen share via browser UIstream.getVideoTracks()[0].addEventListener('ended', () => {console.log('Screen share stopped');handleScreenShareStop();});
Integration and Development Issues
Problem: Cannot find Native Frame packages in the npm registry.
Solution:
- Ensure proper
.npmrcconfiguration
Make sure your .npmrc file includes the following:
@video:registry=https://npm-packages.nativeframe.com/
Problem: Cannot import Native Frame packages.
Solutions:
-
Verify package installation
# For Reactnpm install @video/video-client-react# For Vanilla JS (core)npm install @video/video-client-core -
Check import statements
// React - Correct importsimport { usePreviewPlayer } from '@video/video-client-react/hooks';import { MediaStreamControllerAPIProvider } from '@video/video-client-react/context';// Vanilla JS - CDN importimport { createCall, requestPlayer } from 'vdc-cdn'; -
Import map configuration (Vanilla JS)
<script type="importmap">{"imports": {"vdc-cdn": "https://cdn.nativeframe.com/video-client-core/14.0.0/index.js"}}</script> -
TypeScript configuration
// tsconfig.json{"compilerOptions": {"moduleResolution": "node","esModuleInterop": true,"allowSyntheticDefaultImports": true}} -
Package version compatibility
- Ensure using compatible versions
- Check package.json for version conflicts
- Update to latest stable version
Problem: React hooks throwing errors.
Solutions:
-
Call hooks at top level
// ✓ Correctfunction MyComponent() {const { mediaStreamController } = usePreviewPlayer();return <div>...</div>;}// ✗ Wrong - inside callbackfunction MyComponent() {const handleClick = () => {const { mediaStreamController } = usePreviewPlayer(); // Error!};} -
Ensure functional component
// ✓ Correct - functional componentfunction MyComponent() {const { player } = usePreviewPlayer();}// ✗ Wrong - class componentclass MyComponent extends React.Component {// Can't use hooks here} -
React version
- Hooks require React 16.8+
- Update React if using older version
npm install react@latest react-dom@latest -
Multiple React copies
# Check for duplicate React installationsnpm ls react# If duplicates found, dedupenpm dedupe -
Context provider wrapping
// Ensure components are wrapped in providers<MediaStreamControllerAPIProvider mediaStreamController={msc}><MyComponent /> {/* Can now use context hooks */}</MediaStreamControllerAPIProvider>
Problem: Application bundle is larger than expected.
Solutions:
-
Exclude optional libraries
// In webpack config or bundler{plugins: [new webpack.DefinePlugin({'process.env.HLSJS_BUNDLED': JSON.stringify('false'),'process.env.MPEGTS_BUNDLED': JSON.stringify('false')})]} -
Tree shaking
// Use named imports for better tree shakingimport { usePreviewPlayer, useAuthClient } from '@video/video-client-react/hooks';// Instead ofimport * as hooks from '@video/video-client-react/hooks'; -
Code splitting
// Lazy load video componentsconst VideoPlayer = React.lazy(() => import('./VideoPlayer'));function App() {return (<Suspense fallback={<Loading />}><VideoPlayer /></Suspense>);} -
Analyze bundle
# Use bundle analyzer to see what's largenpm install --save-dev webpack-bundle-analyzer# Run analysisnpx webpack-bundle-analyzer build/stats.json -
Use CDN for Vanilla JS
- Core library from CDN doesn't count toward bundle
- Loads on demand
- Better for applications with optional video
Problem: TypeScript compilation errors with Native Frame types.
Solutions:
-
Import types correctly
import { types } from '@video/video-client-react';// Use typesconst options: types.CreateCallOptions = {streamKey: 'my-key',// ...}; -
Type declarations
// Create types.d.ts if types are missingdeclare module '@video/video-client-react' {export interface MediaStreamController {// Add missing type definitions}} -
Strict mode issues
// Handle nullable typesconst { mediaStreamController } = usePreviewPlayer();if (mediaStreamController) {// TypeScript knows it's not null heremediaStreamController.setVideoDevice(deviceId);} -
Update type definitions
# Ensure latest types are installednpm update @video/video-client-react -
Type assertions (use sparingly)
// Last resort if types are incorrectconst player = createPlayer(options) as types.Player;
Browser and Device Compatibility
Problem: Features work in Chrome but not Safari.
Solutions:
-
WebRTC vendor prefixes
// Use adapter.js for cross-browser compatibility// It normalizes WebRTC APIs across browsersimport 'webrtc-adapter'; -
Autoplay policy
// Safari requires user gesture for autoplayvideo.muted = true; // Muted videos can autoplay// Or wait for user interactionbutton.onclick = () => {video.play();}; -
getUserMedia in iframes
<!-- Safari requires allow attribute --><iframe allow="camera; microphone"></iframe> -
VP8/VP9 codec support
- Safari prefers H.264
- Ensure backend supports H.264
- Check codec negotiation in logs
-
iOS-specific
- iOS Safari requires
playsinlineattribute
<video playsinline></video>- iOS doesn't support background playback
- Tab must be active for WebRTC
- iOS Safari requires
Problem: Features work on desktop but not mobile.
Solutions:
-
Camera orientation
// Handle device orientation changeswindow.addEventListener('orientationchange', () => {// Adjust video layoutupdateVideoLayout();}); -
Mobile bandwidth constraints
- Reduce default video quality on mobile
const isMobile = /iPhone|iPad|Android/i.test(navigator.userAgent);const constraints = {video: isMobile ? {width: { ideal: 640 },height: { ideal: 480 }} : {width: { ideal: 1280 },height: { ideal: 720 }}}; -
Touch vs click events
// Handle both touch and clickbutton.addEventListener('touchstart', handleAction);button.addEventListener('click', handleAction); -
Fullscreen API
// Use webkit prefix for iOSif (video.webkitEnterFullscreen) {video.webkitEnterFullscreen();} else {video.requestFullscreen();} -
Background tab handling
- Mobile browsers aggressively suspend background tabs
- Warn users to keep app in foreground
- Handle visibility change events
document.addEventListener('visibilitychange', () => {if (document.hidden) {console.log('App backgrounded');// Pause non-critical operations}});
Problem: Users on older browsers experience issues.
Solutions:
-
Check browser support
function checkBrowserSupport() {if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {alert('Your browser does not support video streaming. Please update to a modern browser.');return false;}return true;} -
Polyfills
// Include polyfills for older browsersimport 'webrtc-adapter'; // WebRTC polyfillimport 'whatwg-fetch'; // Fetch polyfill -
Feature detection
// Detect features before usingif ('getDisplayMedia' in navigator.mediaDevices) {// Screen sharing supported} else {// Show fallback or disable feature} -
Graceful degradation
- Provide fallback UI for unsupported features
- Show clear error messages
- Link to browser update instructions
-
Minimum browser versions
- Chrome 74+
- Firefox 68+
- Safari 12.1+
- Edge 79+ (Chromium-based)
Echo Pillarbox Video
Solution:
- Ensure
previewPlayeris initialized before rendering - Verify PlayerAPIProvider wraps the EchoPillarboxWrapper
- Check that the Video component has a ref if needed
Solution:
- Make sure
EchoPillarboxVideois placed insideEchoPillarboxWrapper - Verify the Video component is also inside the same wrapper
- The components must be siblings within the wrapper
Solution:
- Add explicit sizing to EchoPillarboxWrapper via style or className
- Use
minHeight: '100%'or explicit dimensions - Ensure parent containers have proper height constraints
Solution:
- Dispose the player in useEffect cleanup
- Clean up mediaStreamController
- EchoPillarboxVideo handles its own cleanup automatically
Still Having Issues?
If you've tried the solutions above and are still experiencing problems:
- Check browser console for error messages and stack traces
- Enable debug logging in Video Client SDK for detailed diagnostics
- Test in incognito mode to rule out extensions/cache issues
- Try different network to isolate network-specific problems
- Contact Native Frame support with:
- Detailed problem description
- Browser and OS version
- Console error messages
- Steps to reproduce
- Network conditions