in packages/calling-stateful-client/src/StreamUtils.ts [262:332]
async function createViewUnparentedVideo(
context: CallContext,
internalContext: InternalCallContext,
stream: LocalVideoStreamState,
options?: CreateViewOptions
): Promise<CreateViewResult | undefined> {
const renderInfo = internalContext.getUnparentedRenderInfo(stream);
if (renderInfo && renderInfo.status === 'Rendered') {
console.warn('Unparented LocalVideoStream is already rendered');
return;
}
if (renderInfo && renderInfo.status === 'Rendering') {
// Do not log to console here as this is a very common situation due to UI rerenders while
// the video rendering is in progress.
return;
}
if (renderInfo && renderInfo.status === 'Stopping') {
console.warn('Unparented LocalVideoStream is in the middle of stopping');
return;
}
const localVideoStream = new LocalVideoStream(stream.source);
const renderer = new VideoStreamRenderer(localVideoStream);
internalContext.setUnparentedRenderInfo(stream, localVideoStream, 'Rendering', undefined);
let view: VideoStreamRendererView;
try {
view = await renderer.createView(options);
} catch (e) {
// Special case for unparented views. Since they are not tied to anything and created by us based on the calls to
// this function we'll delete it to clean up the data since keeping it around doesn't help us and if developer wants
// to create a new view they can check that the view is not rendered and call this function again.
internalContext.deleteUnparentedRenderInfo(stream);
throw e;
}
// Since render could take some time, we need to check if the stream is still valid and if we received a signal to
// stop rendering.
const refreshedRenderInfo = internalContext.getUnparentedRenderInfo(stream);
if (!refreshedRenderInfo) {
// Unparented stream's RenderInfo was deleted. Currently this shouldn't happen but if it does we'll just dispose the
// renderer and clean up state. If developer wanted the stream they could call this function again and that should
// generate new working state via this function.
renderer.dispose();
context.deleteDeviceManagerUnparentedView(stream);
return;
}
if (refreshedRenderInfo.status === 'Stopping') {
// Stop render was called on this stream after we had started rendering. We will dispose this view and do not
// put the view into the state. Special case for unparented views, delete them from state when stopped to free up
// the memory since we were the ones generating this and not tied to any Call state.
internalContext.deleteUnparentedRenderInfo(stream);
context.deleteDeviceManagerUnparentedView(stream);
return;
}
// Else the stream still exists and status is not telling us to stop rendering. Complete the render process by
// updating the state.
internalContext.setUnparentedRenderInfo(stream, localVideoStream, 'Rendered', renderer);
internalContext.subscribeToUnparentedViewVideoEffects(localVideoStream, context);
context.setDeviceManagerUnparentedView(stream, convertFromSDKToDeclarativeVideoStreamRendererView(view));
return {
renderer,
view
};
}