diff --git a/src/QrReader/hooks.ts b/src/QrReader/hooks.ts index 0d26fdc..c136329 100644 --- a/src/QrReader/hooks.ts +++ b/src/QrReader/hooks.ts @@ -1,7 +1,7 @@ import { MutableRefObject, useEffect, useRef } from 'react'; import { BrowserQRCodeReader, IScannerControls } from '@zxing/browser'; -import { UseQrReaderHook } from '../types'; +import { StopRequestedException, UseQrReaderHook } from '../types'; import { isMediaDevicesSupported, isValidType } from './utils'; @@ -13,6 +13,7 @@ export const useQrReader: UseQrReaderHook = ({ videoId, }) => { const controlsRef: MutableRefObject = useRef(null); + const stopRequested = useRef(false); useEffect(() => { const codeReader = new BrowserQRCodeReader(null, { @@ -32,6 +33,11 @@ export const useQrReader: UseQrReaderHook = ({ if (isValidType(video, 'constraints', 'object')) { codeReader .decodeFromConstraints({ video }, videoId, (result, error) => { + if (stopRequested.current) { + //Callback to parent function continuously runs after destruction of Video Element // Stream Video Tracks + //Intentionally throwing exception to step out of parent function. + throw new StopRequestedException(); + } if (isValidType(onResult, 'onResult', 'function')) { onResult(result, error, codeReader); } @@ -48,4 +54,5 @@ export const useQrReader: UseQrReaderHook = ({ controlsRef.current?.stop(); }; }, []); + return [stopRequested]; }; diff --git a/src/QrReader/index.tsx b/src/QrReader/index.tsx index 16fd99a..719d0e1 100644 --- a/src/QrReader/index.tsx +++ b/src/QrReader/index.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { useEffect, useRef } from 'react'; import { styles } from './styles'; import { useQrReader } from './hooks'; @@ -16,12 +17,35 @@ export const QrReader: React.FC = ({ onResult, videoId, }) => { - useQrReader({ + const [stopRequested] = useQrReader({ constraints, scanDelay, onResult, videoId, }); + const isUnmounting = useRef(false); + + useEffect( + () => () => { + if (isUnmounting.current) { + //Stop and remove all video based tracks from video media streams + navigator.mediaDevices + .getUserMedia({ + video: true, + audio: false, + }) + .then((mediaStream) => + mediaStream.getTracks().forEach((track) => { + track.stop(); + mediaStream.removeTrack(track); + }) + ); + stopRequested.current = true; + } + isUnmounting.current = true; + }, + [] + ); return (
diff --git a/src/types/index.ts b/src/types/index.ts index 3ca0146..8b719b4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,5 +1,14 @@ import { BrowserQRCodeReader } from '@zxing/browser'; import { Result } from '@zxing/library'; +import { Exception } from '@zxing/library'; +import { MutableRefObject } from 'react'; + +export class StopRequestedException extends Exception { + constructor() { + super(); + Object.setPrototypeOf(this, new.target.prototype); + } +} export type QrReaderProps = { /** @@ -74,4 +83,6 @@ export type UseQrReaderHookProps = { videoId?: string; }; -export type UseQrReaderHook = (props: UseQrReaderHookProps) => void; +export type UseQrReaderHook = ( + props: UseQrReaderHookProps +) => [MutableRefObject];