lodash-es#throttle TypeScript Examples

The following examples show how to use lodash-es#throttle. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Kplayer.ts    From agefans-enhance with MIT License 6 votes vote down vote up
jumpToLogTime = throttle(() => {
    if (this.isJumped) return
    if (this.currentTime < 3) {
      this.isJumped = true
      const logTime = this.getCurrentTimeLog()
      if (logTime && this.plyr.duration - logTime > 10) {
        this.message.info(`已自动跳转至历史播放位置 ${parseTime(logTime)}`)
        this.currentTime = logTime
      }
    }
  }, 1000)
Example #2
Source File: play.ts    From agefans-enhance with MIT License 5 votes vote down vote up
function addListener() {
  player.on('next', () => {
    gotoNextPart()
  })

  player.on('prev', () => {
    gotoPrevPart()
  })

  player.plyr.once('canplay', () => {
    resetVideoHeight()
  })

  player.on('error', () => {
    removeLocal(getActivedom().data('href'))
  })

  const update = throttle(() => {
    updateTime(player.currentTime)
  }, 1000)

  player.on('timeupdate', () => {
    update()
  })

  player.on('skiperror', (_, duration) => {
    if (duration === 0) {
      updateTime(0)
    } else {
      updateTime(player.currentTime + duration)
    }
    window.location.reload()
  })

  window.addEventListener('popstate', () => {
    const href = location.pathname + location.search
    const $dom = $(`[data-href='${href}']`)

    if ($dom.length) {
      switchPart(href, $dom, false)
    } else {
      window.location.reload()
    }
  })
}
Example #3
Source File: history.ts    From agefans-enhance with MIT License 5 votes vote down vote up
logHis = throttle(his.log.bind(his), 1000)
Example #4
Source File: Kplayer.ts    From agefans-enhance with MIT License 5 votes vote down vote up
setCurrentTimeLogThrottled = throttle(() => {
    this.setCurrentTimeLog()
  }, 1000)
Example #5
Source File: Tabs.tsx    From atlas with GNU General Public License v3.0 4 votes vote down vote up
Tabs: React.FC<TabsProps> = React.memo(
  ({ tabs, onSelectTab, initialIndex = -1, selected: paramsSelected, className }) => {
    const [_selected, setSelected] = useState(initialIndex)
    const selected = paramsSelected ?? _selected
    const [isContentOverflown, setIsContentOverflown] = useState(false)
    const tabsGroupRef = useRef<HTMLDivElement>(null)
    const tabRef = useRef<HTMLDivElement>(null)
    const [shadowsVisible, setShadowsVisible] = useState({
      left: false,
      right: true,
    })

    useEffect(() => {
      const tabsGroup = tabsGroupRef.current
      if (!tabsGroup) {
        return
      }
      setIsContentOverflown(tabsGroup.scrollWidth > tabsGroup.clientWidth)
    }, [])

    useEffect(() => {
      const tabsGroup = tabsGroupRef.current
      const tab = tabRef.current
      if (!tabsGroup || !isContentOverflown || !tab) {
        return
      }
      const { clientWidth, scrollWidth } = tabsGroup
      const tabWidth = tab.offsetWidth

      const middleTabPosition = clientWidth / 2 - tabWidth / 2

      tabsGroup.scrollLeft = tab.offsetLeft - middleTabPosition

      const touchHandler = throttle(() => {
        setShadowsVisible({
          left: tabsGroup.scrollLeft > SCROLL_SHADOW_OFFSET,
          right: tabsGroup.scrollLeft < scrollWidth - clientWidth - SCROLL_SHADOW_OFFSET,
        })
      }, 100)

      tabsGroup.addEventListener('touchmove', touchHandler)
      tabsGroup.addEventListener('scroll', touchHandler)
      return () => {
        touchHandler.cancel()
        tabsGroup.removeEventListener('touchmove', touchHandler)
        tabsGroup.removeEventListener('scroll', touchHandler)
      }
    }, [isContentOverflown, selected])

    const createClickHandler = (idx?: number) => () => {
      if (idx !== undefined) {
        onSelectTab(idx)
        setSelected(idx)
      }
    }

    return (
      <TabsWrapper className={className}>
        <CSSTransition
          in={shadowsVisible.left && isContentOverflown}
          timeout={100}
          classNames={transitions.names.fade}
          unmountOnExit
        >
          <BackgroundGradient direction="prev" />
        </CSSTransition>
        <CSSTransition
          in={shadowsVisible.right && isContentOverflown}
          timeout={100}
          classNames={transitions.names.fade}
          unmountOnExit
        >
          <BackgroundGradient direction="next" />
        </CSSTransition>
        <TabsGroup ref={tabsGroupRef}>
          {tabs.map((tab, idx) => (
            <Tab
              onClick={createClickHandler(idx)}
              key={`${tab.name}-${idx}`}
              selected={selected === idx}
              ref={selected === idx ? tabRef : null}
            >
              <span data-badge={tab.badgeNumber}>
                {tab.name}
                {tab.pillText && <StyledPill size="small" label={tab.pillText} />}
              </span>
            </Tab>
          ))}
        </TabsGroup>
      </TabsWrapper>
    )
  }
)
Example #6
Source File: EmbeddedView.tsx    From atlas with GNU General Public License v3.0 4 votes vote down vote up
EmbeddedView: React.FC = () => {
  useRedirectMigratedContent({ type: 'embedded-video' })
  const { id } = useParams()
  const { loading, video, error } = useVideo(id ?? '', {
    onError: (error) => SentryLogger.error('Failed to load video data', 'VideoView', error),
  })
  const { addVideoView } = useAddVideoView()

  const { url: mediaUrl, isLoadingAsset: isMediaLoading } = useAsset(video?.media)
  const { url: thumbnailUrl, isLoadingAsset: isThumbnailLoading } = useAsset(video?.thumbnailPhoto)

  const { startTimestamp } = useVideoStartTimestamp(video?.duration)

  const channelId = video?.channel?.id
  const videoId = video?.id
  const categoryId = video?.category?.id

  useEffect(() => {
    if (!videoId || !channelId) {
      return
    }
    addVideoView({
      variables: {
        videoId,
        channelId,
        categoryId,
      },
    }).catch((error) => {
      SentryLogger.error('Failed to increase video views', 'VideoView', error)
    })
  }, [addVideoView, videoId, channelId, categoryId])

  const handleVideoEnded = () => {
    if (window.top) {
      window.top.postMessage('atlas_video_ended', '*')
    }
  }

  const throttledSendTimeUpdate = useRef(
    throttle((time: number) => {
      if (window.top) {
        window.top.postMessage(`atlas_video_progress:${time}`, '*')
      }
    }, 1000)
  )

  const handleTimeUpdated = (time: number) => {
    if (!video?.duration) {
      return
    }

    const progress = time / video.duration
    // make sure progress is in 0..1 range
    const normalizedProgress = Math.min(Math.max(0, progress), 1)
    throttledSendTimeUpdate.current(normalizedProgress)
  }

  if (error) {
    return <ViewErrorFallback />
  }

  if (!loading && !video) {
    return (
      <NotFoundVideoContainer>
        <EmptyFallback
          title="Video not found"
          button={
            <Button variant="secondary" size="large" to={absoluteRoutes.viewer.index()}>
              Go back to home page
            </Button>
          }
        />
      </NotFoundVideoContainer>
    )
  }

  return (
    <>
      <EmbeddedGlobalStyles />
      <Container>
        {!isMediaLoading && !isThumbnailLoading && video ? (
          <VideoPlayer
            isVideoPending={!video?.media?.isAccepted}
            channelId={video.channel?.id}
            videoId={video.id}
            src={mediaUrl}
            posterUrl={thumbnailUrl}
            fill
            startTime={startTimestamp}
            onEnd={handleVideoEnded}
            onTimeUpdated={handleTimeUpdated}
            isEmbedded
          />
        ) : (
          <PlayerSkeletonLoader />
        )}
      </Container>
    </>
  )
}
Example #7
Source File: VideoView.tsx    From atlas with GNU General Public License v3.0 4 votes vote down vote up
VideoView: React.FC = () => {
  useRedirectMigratedContent({ type: 'video' })
  const { id } = useParams()
  const { openNftPutOnSale, cancelNftSale, openNftAcceptBid, openNftChangePrice, openNftPurchase, openNftSettlement } =
    useNftActions()
  const { withdrawBid } = useNftTransactions()
  const { loading, video, error } = useVideo(id ?? '', {
    onError: (error) => SentryLogger.error('Failed to load video data', 'VideoView', error),
  })
  const nftWidgetProps = useNftWidget(id)

  const mdMatch = useMediaMatch('md')
  const { addVideoView } = useAddVideoView()
  const {
    watchedVideos,
    cinematicView,
    actions: { updateWatchedVideos },
  } = usePersonalDataStore((state) => state)
  const category = useCategoryMatch(video?.category?.id)

  const { anyOverlaysOpen } = useOverlayManager()
  const { ref: playerRef, inView: isPlayerInView } = useInView()
  const pausePlayNext = anyOverlaysOpen || !isPlayerInView

  const { url: mediaUrl, isLoadingAsset: isMediaLoading } = useAsset(video?.media)
  const { url: thumbnailUrl } = useAsset(video?.thumbnailPhoto)

  const videoMetaTags = useMemo(() => {
    if (!video || !thumbnailUrl) return {}
    return generateVideoMetaTags(video, thumbnailUrl)
  }, [video, thumbnailUrl])
  const headTags = useHeadTags(video?.title, videoMetaTags)

  const { startTimestamp, setStartTimestamp } = useVideoStartTimestamp(video?.duration)

  // Restore an interrupted video state
  useEffect(() => {
    if (startTimestamp != null || !video) {
      return
    }
    const currentVideo = watchedVideos.find((v) => v.id === video?.id)
    setStartTimestamp(currentVideo?.__typename === 'INTERRUPTED' ? currentVideo.timestamp : 0)
  }, [watchedVideos, startTimestamp, video, setStartTimestamp])

  const channelId = video?.channel?.id
  const channelName = video?.channel?.title
  const videoId = video?.id
  const categoryId = video?.category?.id

  useEffect(() => {
    if (!videoId || !channelId) {
      return
    }
    addVideoView({
      variables: {
        videoId,
        channelId,
        categoryId,
      },
    }).catch((error) => {
      SentryLogger.error('Failed to increase video views', 'VideoView', error)
    })
  }, [addVideoView, videoId, channelId, categoryId])

  // Save the video timestamp
  // disabling eslint for this line since debounce is an external fn and eslint can't figure out its args, so it will complain.
  // eslint-disable-next-line react-hooks/exhaustive-deps
  const handleTimeUpdate = useCallback(
    throttle((time) => {
      if (video?.id) {
        updateWatchedVideos('INTERRUPTED', video.id, time)
      }
    }, 5000),
    [video?.id]
  )

  const handleVideoEnd = useCallback(() => {
    if (video?.id) {
      handleTimeUpdate.cancel()
      updateWatchedVideos('COMPLETED', video?.id)
    }
  }, [video?.id, handleTimeUpdate, updateWatchedVideos])

  // use Media Session API to provide rich metadata to the browser
  useEffect(() => {
    const supported = 'mediaSession' in navigator
    if (!supported || !video) {
      return
    }

    const artwork: MediaImage[] = thumbnailUrl ? [{ src: thumbnailUrl, type: 'image/webp', sizes: '640x360' }] : []

    navigator.mediaSession.metadata = new MediaMetadata({
      title: video.title || '',
      artist: video.channel.title || '',
      album: '',
      artwork: artwork,
    })

    return () => {
      navigator.mediaSession.metadata = null
    }
  }, [thumbnailUrl, video])

  if (error) {
    return <ViewErrorFallback />
  }

  if (!loading && !video) {
    return (
      <NotFoundVideoContainer>
        <EmptyFallback
          title="Video not found"
          button={
            <Button variant="secondary" size="large" to={absoluteRoutes.viewer.index()}>
              Go back to home page
            </Button>
          }
        />
      </NotFoundVideoContainer>
    )
  }

  const isCinematic = cinematicView || !mdMatch
  const sideItems = (
    <GridItem colSpan={{ xxs: 12, md: 4 }}>
      {!!nftWidgetProps && (
        <NftWidget
          {...nftWidgetProps}
          onNftPutOnSale={() => id && openNftPutOnSale(id)}
          onNftCancelSale={() => id && nftWidgetProps.saleType && cancelNftSale(id, nftWidgetProps.saleType)}
          onNftAcceptBid={() => id && openNftAcceptBid(id)}
          onNftChangePrice={() => id && openNftChangePrice(id)}
          onNftPurchase={() => id && openNftPurchase(id)}
          onNftSettlement={() => id && openNftSettlement(id)}
          onNftBuyNow={() => id && openNftPurchase(id, { fixedPrice: true })}
          onWithdrawBid={() => id && withdrawBid(id)}
        />
      )}
      <MoreVideos channelId={channelId} channelName={channelName} videoId={id} type="channel" />
      <MoreVideos categoryId={category?.id} categoryName={category?.name} videoId={id} type="category" />
    </GridItem>
  )

  const detailsItems = (
    <>
      {headTags}
      <TitleContainer>
        {video ? (
          <TitleText variant={mdMatch ? 'h600' : 'h400'}>{video.title}</TitleText>
        ) : (
          <SkeletonLoader height={mdMatch ? 56 : 32} width={400} />
        )}
        <Meta variant={mdMatch ? 't300' : 't100'} secondary>
          {video ? (
            formatVideoViewsAndDate(video.views || null, video.createdAt, { fullViews: true })
          ) : (
            <SkeletonLoader height={24} width={200} />
          )}
        </Meta>
      </TitleContainer>
      <ChannelContainer>
        <ChannelLink followButton id={channelId} textVariant="h300" avatarSize="small" />
      </ChannelContainer>
      <VideoDetails video={video} category={category} />
    </>
  )

  return (
    <>
      <PlayerGridWrapper cinematicView={isCinematic}>
        <PlayerWrapper cinematicView={isCinematic}>
          <PlayerGridItem colSpan={{ xxs: 12, md: cinematicView ? 12 : 8 }}>
            <PlayerContainer className={transitions.names.slide} cinematicView={cinematicView}>
              {!isMediaLoading && video ? (
                <VideoPlayer
                  isVideoPending={!video?.media?.isAccepted}
                  channelId={video?.channel?.id}
                  videoId={video?.id}
                  autoplay
                  src={mediaUrl}
                  onEnd={handleVideoEnd}
                  onTimeUpdated={handleTimeUpdate}
                  startTime={startTimestamp}
                  isPlayNextDisabled={pausePlayNext}
                  ref={playerRef}
                />
              ) : (
                <PlayerSkeletonLoader />
              )}
            </PlayerContainer>
            {!isCinematic && detailsItems}
          </PlayerGridItem>
          {!isCinematic && sideItems}
        </PlayerWrapper>
      </PlayerGridWrapper>
      <LimitedWidthContainer>
        {isCinematic && (
          <LayoutGrid>
            <GridItem className={transitions.names.slide} colSpan={{ xxs: 12, md: cinematicView ? 8 : 12 }}>
              {detailsItems}
            </GridItem>
            {sideItems}
          </LayoutGrid>
        )}
        <StyledCallToActionWrapper>
          {['popular', 'new', 'discover'].map((item, idx) => (
            <CallToActionButton key={`cta-${idx}`} {...CTA_MAP[item]} />
          ))}
        </StyledCallToActionWrapper>
      </LimitedWidthContainer>
    </>
  )
}