diff --git a/.changeset/fix-track-volume-silence.md b/.changeset/fix-track-volume-silence.md new file mode 100644 index 00000000..58f90a94 --- /dev/null +++ b/.changeset/fix-track-volume-silence.md @@ -0,0 +1,5 @@ +--- +'@livekit/react-native': patch +--- + +Fix `useTrackVolume` staying at the last non-zero value when the audio track goes silent diff --git a/package.json b/package.json index 33904f65..a45c9d62 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "@react-native/eslint-config": "0.83.0", "@types/fastestsmallesttextencoderdecoder": "^1.0.0", "@types/react": "^19.2.0", + "@types/react-test-renderer": "^19.1.0", "eslint": "^9.35.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.4", @@ -89,6 +90,7 @@ "react": "19.2.0", "react-native": "0.83.0", "react-native-builder-bob": "^0.40.17", + "react-test-renderer": "19.2.0", "typedoc": "^0.28.14", "typescript": "^5.9.2" }, @@ -120,6 +122,7 @@ "packageManager": "yarn@4.11.0", "jest": { "preset": "react-native", + "testEnvironment": "node", "modulePathIgnorePatterns": [ "/ci", "/example", diff --git a/src/hooks/__tests__/useTrackVolume.test.tsx b/src/hooks/__tests__/useTrackVolume.test.tsx new file mode 100644 index 00000000..8e8fb390 --- /dev/null +++ b/src/hooks/__tests__/useTrackVolume.test.tsx @@ -0,0 +1,58 @@ +import { act, create } from 'react-test-renderer'; + +type Handler = (event: unknown) => void; +const handlers: Record = {}; + +jest.mock('../../events/EventEmitter', () => ({ + addListener: (_listener: unknown, name: string, handler: Handler) => { + handlers[name] = handler; + }, + removeListener: jest.fn(), +})); + +jest.mock('../../LKNativeModule', () => ({ + __esModule: true, + default: { + createVolumeProcessor: jest.fn(() => 'tag-1'), + deleteVolumeProcessor: jest.fn(), + }, +})); + +import { useTrackVolume } from '../useTrackVolume'; + +const fakeTrack = () => + ({ + mediaStreamTrack: { id: 'mst-1', _peerConnectionId: 1 }, + }) as any; + +// `Track` is checked with instanceof, so pass a track reference instead. +const fakeRef = () => ({ publication: { track: fakeTrack() } }) as any; + +function renderHook(useHook: () => T) { + const result: { current: T } = { current: undefined as unknown as T }; + const Probe = () => { + result.current = useHook(); + return null; + }; + act(() => { + create(); + }); + return result; +} + +describe('useTrackVolume', () => { + it('drops back to 0 when the native processor reports silence', () => { + const ref = fakeRef(); + const result = renderHook(() => useTrackVolume(ref)); + + act(() => { + handlers.LK_VOLUME_PROCESSED!({ id: 'tag-1', volume: 0.4 }); + }); + expect(result.current).toBe(0.4); + + act(() => { + handlers.LK_VOLUME_PROCESSED!({ id: 'tag-1', volume: 0 }); + }); + expect(result.current).toBe(0); + }); +}); diff --git a/src/hooks/useTrackVolume.ts b/src/hooks/useTrackVolume.ts index 768f1a3d..fc839087 100644 --- a/src/hooks/useTrackVolume.ts +++ b/src/hooks/useTrackVolume.ts @@ -45,7 +45,7 @@ export function useTrackVolume( mediaStreamTrackId ); addListener(listener, 'LK_VOLUME_PROCESSED', (event: any) => { - if (event.volume && reactTag && event.id === reactTag) { + if (event.volume != null && reactTag && event.id === reactTag) { setVolume(event.volume); } }); diff --git a/tsconfig.build.json b/tsconfig.build.json index 9adfc508..7a1a0170 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,5 +1,5 @@ { "extends": "./tsconfig", - "exclude": ["example", "ci"] + "exclude": ["example", "ci", "**/__tests__"] } diff --git a/yarn.lock b/yarn.lock index c91af92e..3b42e5da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3640,6 +3640,7 @@ __metadata: "@react-native/eslint-config": "npm:0.83.0" "@types/fastestsmallesttextencoderdecoder": "npm:^1.0.0" "@types/react": "npm:^19.2.0" + "@types/react-test-renderer": "npm:^19.1.0" array.prototype.at: "npm:^1.1.1" base64-js: "npm:1.5.1" eslint: "npm:^9.35.0" @@ -3656,6 +3657,7 @@ __metadata: react-native: "npm:0.83.0" react-native-builder-bob: "npm:^0.40.17" react-native-url-polyfill: "npm:^3.0.0" + react-test-renderer: "npm:19.2.0" typed-emitter: "npm:^2.1.0" typedoc: "npm:^0.28.14" typescript: "npm:^5.9.2" @@ -4660,6 +4662,24 @@ __metadata: languageName: node linkType: hard +"@types/react-test-renderer@npm:^19.1.0": + version: 19.3.0 + resolution: "@types/react-test-renderer@npm:19.3.0" + dependencies: + "@types/react": "npm:*" + checksum: 10/fbc49458dbb661d7565d60c07bca5284bac15bf88fc7fef9bb5ff0f58c4dbe87faf430443bac94b9a7a3dd2fa1e520435fe4d245928545756220b0fb76e0b080 + languageName: node + linkType: hard + +"@types/react@npm:*": + version: 19.3.0 + resolution: "@types/react@npm:19.3.0" + dependencies: + csstype: "npm:^3.2.2" + checksum: 10/2f4589222fd9c512e1ef78e65f334ade8777400add8ec0803fcc3a044761679b7984ba8410a890af24255383c3251a568bb692b60acfa22397ba0a64b2e032bf + languageName: node + linkType: hard + "@types/react@npm:^19.2.0": version: 19.2.16 resolution: "@types/react@npm:19.2.16" @@ -11297,6 +11317,13 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^19.2.0": + version: 19.3.0 + resolution: "react-is@npm:19.3.0" + checksum: 10/69cfafcab48caac03da35fb17e640a2a2de8299a3cd699da720e671a22231fc7179c77c82e379826728857f9cc45cb56ff1e7f5d607acdd48fe151417a2a388f + languageName: node + linkType: hard + "react-native-builder-bob@npm:^0.40.17": version: 0.40.17 resolution: "react-native-builder-bob@npm:0.40.17" @@ -11531,6 +11558,18 @@ __metadata: languageName: node linkType: hard +"react-test-renderer@npm:19.2.0": + version: 19.2.0 + resolution: "react-test-renderer@npm:19.2.0" + dependencies: + react-is: "npm:^19.2.0" + scheduler: "npm:^0.27.0" + peerDependencies: + react: ^19.2.0 + checksum: 10/1a072bf5c383ee9cec1eed5872114a25d8029e8fabe17a9154cbb7b5d6e5570711efc3e80e336d170bb1f60e29d395087147891fa198743d17c29ff86782cde3 + languageName: node + linkType: hard + "react@npm:19.1.1": version: 19.1.1 resolution: "react@npm:19.1.1" @@ -11902,7 +11941,7 @@ __metadata: languageName: node linkType: hard -"scheduler@npm:0.27.0": +"scheduler@npm:0.27.0, scheduler@npm:^0.27.0": version: 0.27.0 resolution: "scheduler@npm:0.27.0" checksum: 10/eab3c3a8373195173e59c147224fc30dabe6dd453f248f5e610e8458512a5a2ee3a06465dc400ebfe6d35c9f5b7f3bb6b2e41c88c86fd177c25a73e7286a1e06