{"record":{"id":"781344ed67d6a172","repo":"jitsi/jitsi-meet","slug":"nolocalstreams","errorCode":null,"errorMessage":"NoLocalStreams","messagePattern":"NoLocalStreams","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"react/features/recording/components/Recording/LocalRecordingManager.web.ts","lineNumber":177,"sourceCode":"        this.writableStream = await this.fileHandle?.createWritable();\n\n        const supportsCaptureHandle = !isEmbedded();\n        const tabId = uuidV4();\n\n        this.selfRecording.on = onlySelf;\n        let gdmStream: MediaStream = new MediaStream();\n        const tracks = getTrackState(getState());\n\n        if (onlySelf) {\n            const audioTrack: MediaStreamTrack | undefined = getLocalTrack(tracks, MEDIA_TYPE.AUDIO)?.jitsiTrack?.track;\n            let videoTrack: MediaStreamTrack | undefined = getLocalTrack(tracks, MEDIA_TYPE.VIDEO)?.jitsiTrack?.track;\n\n            if (videoTrack && videoTrack.readyState !== 'live') {\n                videoTrack = undefined;\n            }\n\n            if (!audioTrack && !videoTrack) {\n                throw new Error('NoLocalStreams');\n            }\n\n            this.selfRecording.withVideo = Boolean(videoTrack);\n            const localTracks: MediaStreamTrack[] = [];\n\n            audioTrack && localTracks.push(audioTrack.clone());\n            videoTrack && localTracks.push(videoTrack.clone());\n            this.stream = new MediaStream(localTracks);\n        } else {\n            if (supportsCaptureHandle) {\n                // @ts-ignore\n                navigator.mediaDevices.setCaptureHandleConfig({\n                    handle: `JitsiMeet-${tabId}`,\n                    permittedOrigins: [ '*' ]\n                });\n            }\n\n            gdmStream = await navigator.mediaDevices.getDisplayMedia({","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/jitsi/jitsi-meet/blob/98de6219cc7ddbe07ace9fde045aff90a242ba01/react/features/recording/components/Recording/LocalRecordingManager.web.ts#L159-L195","documentation":"Thrown by startLocalRecording on web when the user has no live local audio or video track to record. Before starting a local recording, the manager checks the local tracks from redux; video tracks that are not in readyState 'live' are discarded. If both audio and video are absent (or ended), recording cannot proceed and 'NoLocalStreams' is raised.","triggerScenarios":"Calling startLocalRecording() when the local participant's audio is muted-without-track (or track ended) and video is disabled or its MediaStreamTrack.readyState !== 'live'. This includes calling before media permission was granted, after stopping all tracks, or when the user joined in audio-only/muted mode with no gdm audio source.","commonSituations":"User joins with mic/camera permissions denied or revoked mid-call; track was stopped and disposed; startLocalRecording invoked from UI before waitForInitialization/local tracks are ready; browser blocked getUserMedia.","solutions":["Verify the participant has at least one live local track (unmuted audio or active video) before showing/enabling the local recording button","Check track.readyState === 'live' on the raw jitsiTrack.track before calling startLocalRecording","If permissions were denied, re-request mic/camera access and retry once tracks exist","Handle the thrown error in the caller to show a 'you need at least one active mic or camera' notification instead of crashing"],"exampleFix":"// before\nawait localRecordingManager.startLocalRecording();\n\n// after\nconst tracks = getState()['features/base/tracks'];\nconst audio = getLocalTrack(tracks, MEDIA_TYPE.AUDIO)?.jitsiTrack?.track;\nconst video = getLocalTrack(tracks, MEDIA_TYPE.VIDEO)?.jitsiTrack?.track;\nconst hasLive = t => t && t.readyState === 'live';\nif (!hasLive(audio) && !hasLive(video)) {\n    dispatch(showErrorNotification({ titleKey: 'localRecording.noStreams' }));\n} else {\n    await localRecordingManager.startLocalRecording();\n}","handlingStrategy":"validation","validationCode":"const tracks = getState()['features/base/tracks'];\nconst audio = getLocalTrack(tracks, MEDIA_TYPE.AUDIO)?.jitsiTrack?.track;\nconst video = getLocalTrack(tracks, MEDIA_TYPE.VIDEO)?.jitsiTrack?.track;\nconst hasLiveTrack = (audio && audio.readyState === 'live')\n    || (video && video.readyState === 'live');\nif (!hasLiveTrack) {\n    // show 'enable mic or camera first' notification instead of calling\n}","typeGuard":"const isLiveTrack = (t?: MediaStreamTrack | null): t is MediaStreamTrack =>\n    Boolean(t) && t!.readyState === 'live';","tryCatchPattern":"try {\n    await manager.startLocalRecording();\n} catch (e: unknown) {\n    if (e instanceof Error && e.message === 'NoLocalStreams') {\n        showError('Enable your microphone or camera before recording.');\n    } else {\n        throw e;\n    }\n}","preventionTips":["Disable/gate the local-record button until a live local track exists in redux","Subscribe to track ended/muted events to update button state","Re-request media permissions before starting the recording flow"],"tags":["local-recording","media-tracks","getusermedia","webrtc"],"backgroundTag":"no-media-tracks-available","analyzedSha":"98de6219cc7ddbe07ace9fde045aff90a242ba01","analyzedAt":"2026-08-28T15:15:59.482Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}