Share screen not running after few times in meeting using Zoom Video SDK

Description
After starting share screen then exiting it, then restarting it a few times, suddenly it stops working it doesn’t reach the finally of the promise and there is no error message as well

Browser Console Error
No error message

Which Web Video SDK version?
@zoom/videosdk”: “^2.2.12”

Video SDK Code Snippets

zoomSession
        .startShareScreen(element, options)
        .catch((error) => {
          console.log(error)
        })
        .finally(() => {
          isLoading = false
        })

To Reproduce(If applicable)
Steps to reproduce the behavior:

  1. Start share screen
  2. Exit via browser bar
  3. Do this a couple of times then you will be unable to share your screen
  4. No error message

Device (please complete the following information):

  • Macbook Air M1
  • OS: macOS 26.2
  • Browser: Chrome and Firefox
  • Browser Version
    • Chrome Version 144.0.7559.133 (Official Build) (arm64)
    • Firefox Version147.0.3 Build ID 20260203002554

Additional context

  • Session ID: nrgPAhU2S/asNDG6SRENrA==

Hi @matthew.offshorly

Thanks for your feedback.

nrgPAhU2S/asNDG6SRENrA==

We are analyzing the logs and investigating the root cause. We will keep you updated.

Thanks
Vic

Hi @vic.yang can you also check this Session ID? 9UY6cvowTxWQOwo89GZhlw==

We are encountering an empty share screen for the sharer

Steps to replicate:

  1. User #1 starts share screen then stop share screen using browser control
  2. User #2 repeats step 1
  3. User #1 repeats step 1, 4x times
  4. Then User #1 will have an empty share screen

Screenshot of screen share not rendering after 4th share screen

Code Snippet on share screen implementation

// Screen Sharing Implementation for Zoom Video SDK

// ============================================
// 1. Screen Element References & State
// ============================================
const screenVideoElement = useTemplateRef<HTMLVideoElement>('screenVideoElement')
const screenCanvasElement = useTemplateRef<HTMLCanvasElement>('screenCanvasElement')
const isShareScreenWithVideoElement = ref(false)
const isShareScreenLoading = ref(false)

// ============================================
// 2. Screen Element Management
// ============================================
const setScreenElements = (
  newScreenVideoElement: HTMLVideoElement,
  newScreenCanvasElement: HTMLCanvasElement
) => {
  screenVideoElement.value = newScreenVideoElement
  screenCanvasElement.value = newScreenCanvasElement
}

const clearScreenElements = () => {
  screenVideoElement.value = null
  screenCanvasElement.value = null
}

const getScreenVideoElement = () => screenVideoElement.value
const getScreenCanvasElement = () => screenCanvasElement.value

// ============================================
// 3. Reset Screen Elements (workaround)
// ============================================
const resetScreenElements = async () => {
  await delay(200)
  isScreenReset.value = true
  await nextTick()
  await delay(200)

  clearScreenElements()

  isScreenReset.value = false
  await nextTick()
  await delay(200)

  initScreenElements()
}

const initScreenElements = () => {
  if (screenVideoElement.value && screenCanvasElement.value) {
    setScreenElements(screenVideoElement.value, screenCanvasElement.value)
  }
}

// ============================================
// 4. Attach Screen Share
// ============================================
const withTimeout = <T>(p: Promise<T>, ms = 15000): Promise<T> => {
  return new Promise<T>((resolve, reject) => {
    const id: any = setTimeout(() => {
      reject(new Error('startShareScreen: timeout'))
    }, ms)
    p.then((v) => {
      clearTimeout(id)
      resolve(v)
    }).catch((e) => {
      clearTimeout(id)
      reject(e)
    })
  })
}

const attachScreen = async (userId?: number) => {
  let userIdentity: number

  if (userId) {
    userIdentity = Number(zoomClient.getUser(userId)?.userIdentity)
  } else {
    userIdentity = Number(zoomClient.getCurrentUserInfo().userIdentity)
    userId = zoomClient.getCurrentUserInfo().userId
  }

  // Short circuit if user id or zoom is undefined
  if (!userId || !userIdentity || !zoomSession) {
    return
  }

  // SELF SCREEN SHARE
  if (userId === zoomClient.getCurrentUserInfo().userId) {
    const element = isStartShareScreenWithVideoElement.value
      ? getScreenVideoElement()
      : getScreenCanvasElement()

    // Validate that the element is properly mounted before starting screen share
    if (!element) {
      showZoomErrorMessage(
        new Error('Screen share is not yet ready. Please try again.'),
        'attachScreen1'
      )
      return
    }

    const options = {
      ...(!zoomSession.isSupportMicrophoneAndShareAudioSimultaneously() && {
        hideShareAudioOption: true,
      }),
    }

    isShareScreenLoading.value = true

    try {
      await withTimeout(zoomSession.startShareScreen(element, options))
      isShareScreenLoading.value = false
      isShareScreenWithVideoElement.value = isStartShareScreenWithVideoElement.value
    } catch (error) {
      showZoomErrorMessage(error, 'attachScreen1')
    }
  } 
  // VIEWING OTHER'S SCREEN SHARE
  else {
    isShareScreenWithVideoElement.value = false
    const screenElement = getScreenCanvasElement()

    // Validate that the screen canvas element exists
    if (!screenElement) {
      showZoomErrorMessage(
        new Error('Screen share is not yet ready. Please try again.'),
        'attachScreen2'
      )
      return
    }

    isShareScreenLoading.value = true

    try {
      await zoomSession.startShareView(screenElement, userId)
      isShareScreenLoading.value = false
      zoomSession.updateSharedVideoQuality(VideoQuality.Video_1080P)
    } catch (error) {
      showZoomErrorMessage(error, 'attachScreen2')
    }
  }

  // Subscribe to share statistics
  zoomSession.subscribeShareStatisticData()
  setIsUserScreenSharing(true)
}

// ============================================
// 5. Detach Screen Share
// ============================================
const detachScreen = async (userId?: number) => {
  if (userId === undefined) {
    userId = zoomClient.getCurrentUserInfo()?.userId
  }

  if (userId) {
    if (userId === zoomClient.getCurrentUserInfo()?.userId) {
      isShareScreenLoading.value = true

      try {
        await zoomSession.stopShareScreen()
        isShareScreenLoading.value = false
      } catch (error) {
        showZoomErrorMessage(error, 'detachScreen1')
      }
    } else {
      try {
        await zoomSession.stopShareView()
        isShareScreenLoading.value = false
      } catch (error) {
        showZoomErrorMessage(error, 'detachScreen2')
      }
    }

    // Unsubscribe from share statistics
    zoomSession.unsubscribeShareStatisticData()
    setIsUserScreenSharing(false)
  }
}

// ============================================
// 6. Public API Methods
// ============================================
const shareScreen = async () => {
  if (zoomSession) {
    const status = zoomSession.getShareStatus()

    if (status !== ShareStatus.End) {
      await stopShareScreen()
    }

    try {
      // Ensure screen elements exist before attempting to share
      const needsVideoElement = isStartShareScreenWithVideoElement.value
      const requiredElement = needsVideoElement
        ? getScreenVideoElement()
        : getScreenCanvasElement()

      if (!requiredElement) {
        throw new Error('Screen share is not yet ready. Please try again.')
      }

      await attachScreen()
      setIsScreenSharing(true)
    } catch (error) {
      setIsScreenSharing(false)
      setShareScreen(false)
      showZoomErrorMessage(error, 'shareScreen')
    }
  }
}

const stopShareScreen = async () => {
  if (zoomSession) {
    await detachScreen()
    await delay(200)
    setIsScreenSharing(false)
  }
}

// ============================================
// 7. Event Listeners
// ============================================
const onPassivelyStopShare = async (event: PassiveStopShareReason) => {
  console.warn('[share] passively stopped by SDK event', {
    time: new Date().toISOString(),
    event,
  })
  isShareScreenLoading.value = true
  await delay(700)
  setIsUserScreenSharing(false)
  setIsScreenSharing(false)
  await nextTick()
  isShareScreenLoading.value = false
}

const onActiveShareChanged = async (payload: {
  state: ShareStateType
  userId: number
}) => {
  const { state, userId } = payload
  console.info('[share] active-share-change', { state, userId })
  
  if (state === ShareStateType.ACTIVE) {
    attachScreen(userId)
  } else if (state === ShareStateType.INACTIVE) {
    detachScreen(userId)
  }
}

// Register events
client.on('passively-stop-share', onPassivelyStopShare)
client.on('active-share-change', onActiveShareChanged)

// ============================================
// 8. HTML Template (Vue 3)
// ============================================
/*
<video-player-container>
  <div
    v-show="isScreenShared"
    ref="screenShareElement"
    class="Caller-screen Caller-screenView"
    :style="screenShareStyles"
  >
    <div class="Caller-video" @dblclick="toggleFullscreen()">
      <!-- Video element for video-based screen sharing -->
      <video
        v-if="!isScreenReset"
        v-show="isShareScreenWithVideoElement"
        ref="screenVideoElement"
        class="Caller-videoScreenView"
        muted
      />
      
      <!-- Canvas element for canvas-based screen sharing -->
      <canvas
        v-if="!isScreenReset"
        v-show="!isShareScreenWithVideoElement"
        ref="screenCanvasElement"
        class="Caller-videoScreenView"
      />
    </div>
  </div>
<video-player-container>
*/

// ============================================
// 9. Watcher for Screen Share State
// ============================================
watch(isScreenShared, (value) => {
  if (!value) {
    resetScreenElements()
    syncParticipants()
  }
  if (value && call?.value?.id) {
    const changeableViews = [ChatCallVideoTypeView.GRID]

    if (changeableViews.includes(videoCallView.value)) {
      changeVideoCallView({
        callId: call.value.id,
        view: ChatCallVideoTypeView.CONTENT,
      })
    }
  }
})

// ============================================
// 10. Key Configuration
// ============================================
const isSABEnabled = computed(
  () => typeof SharedArrayBuffer === 'function' && window.crossOriginIsolated
)

const isStartShareScreenWithVideoElement = computed(() => {
  return zoomSession?.isStartShareScreenWithVideoElement()
})

// SDK Initialization
const payload: InitOptions = {
  patchJsMedia: true,
  stayAwake: true,
  enforceMultipleVideos: !isSABEnabled.value,
  leaveOnPageUnload: true,
}

await zoomClient.init('en-US', 'Global', payload)

Hi @matthew.offshorly

9UY6cvowTxWQOwo89GZhlw==

After analyzing the logs, we identified some issues. I’d like to understand whether a new canvas element is created each time stream.startShareView or stream.startShareScreen is called.

Our screen share content is rendered on the WebGL context. Due to browser limitations, there is a limit to the number of WebGL contexts that can be created on the same page. Exceeding this limit will prevent rendering on them.

Therefore, if possible, please hide the element using CSS instead of constructing and destructing it repeatedly.

Thanks
Vic

Hi @vic.yang tried to use css when hiding screen element, instead of constructing and destructing it repeatedly, although after a switching share screen between users, startShareView times out

Session ID:mB30k7RHReSn7TLnucmV+A==

Steps to replicate:

  1. User 1 starts share screen then stops share screen using browser control
  2. User 2 starts share screen then screen doesn’t appear for User 1

Then this error appears for User 1

zoomSession.startShareView(canvasElement, userId)

{"type":"OPERATION_TIMEOUT","errorCode":1}

Hi @matthew.offshorly

mB30k7RHReSn7TLnucmV+A==

After analyzing the log, we found the the canvas is used by both stream. startShareScreen and stream.startShareView methods.

Currently, due to technical limitations, the self share view and the remote share view cannot use the same canvas. Could you try using different canvases and, when screen sharing stops, keep the canvas element and only hide it using CSS?

Thanks
Vic