-
Notifications
You must be signed in to change notification settings - Fork 13.9k
fix: Stop audio playback when the playing message is deleted #42074
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jeanfbrito
wants to merge
22
commits into
develop
Choose a base branch
from
fix/stop-audio-on-message-delete
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
9113bbb
fix: stop shared audio player when its message is deleted
jeanfbrito 5a70ded
fix: carry drid and quote metadata into the audio track delete criteria
jeanfbrito 251291f
fix: keep the quoting message timestamp on quoted audio tracks
jeanfbrito 49a9c42
fix: honor explicit bulk-delete ids and pass message identity from qu…
jeanfbrito 6d99164
fix: use a Date timestamp in moderation audio sources and pass messag…
jeanfbrito 877b67d
fix: stop quoted audio playback when the original quoted message is d…
jeanfbrito a68669f
fix: skip the quoted-origin prune fallback when filters cannot be eva…
jeanfbrito 9e94202
chore: scope the quoted-audio changeset to same-room deletion
jeanfbrito 7aca696
fix: stop cross-room quoted audio playback when the original is deleted
jeanfbrito d18e024
fix: keep the quoted-origin prune guard, persist only the origin room
jeanfbrito 72157af
fix: do not match audio-player delete criteria on stale pinned state
jeanfbrito ed2690d
fix: close the audio player when a moderator deletes the message
jeanfbrito a028d7e
fix: keep matching on drid, which a message cannot gain or lose
jeanfbrito bf955e9
fix: refresh the audio player's track when the playing message changes
jeanfbrito e8f8383
chore: fold the quoted-audio changesets into one
jeanfbrito 88e2c59
feat: close the audio player when the listener loses the track's room
jeanfbrito 203ca6f
fix: give the composer reply preview its quoted message's identity
jeanfbrito aa50cda
fix: keep refreshed track state and restore the ids short-circuit test
jeanfbrito f97e3ab
fix: give the forward and pin modals their quoted message's identity
jeanfbrito c408d56
Merge remote-tracking branch 'origin/fix/stop-audio-on-message-delete…
jeanfbrito a8b196b
test: cover the audio player's stop paths end to end
jeanfbrito ce71121
test: stop the audio player spec from leaking rooms, and cut a page r…
jeanfbrito File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@rocket.chat/meteor': patch | ||
| --- | ||
|
|
||
| Stops the shared audio player and hides the Now Playing card when the message that owns the playing audio attachment is deleted |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| '@rocket.chat/core-typings': patch | ||
| '@rocket.chat/meteor': patch | ||
| --- | ||
|
|
||
| Stops the shared audio player and hides the Now Playing card when the audio is no longer the listener's to hear: when the original message of a quoted audio attachment is deleted, when the listener leaves or is removed from the room the audio belongs to, and — for quotes created from now on — when the original is deleted in another room it was quoted from. Pinning a message while its audio plays no longer stops playback on a later bulk delete that excludes pinned messages, for as long as that message stays rendered |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
26
apps/meteor/client/lib/utils/getMessageIdFromPermalink.spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import { getMessageIdFromPermalink } from './getMessageIdFromPermalink'; | ||
|
|
||
| describe('getMessageIdFromPermalink', () => { | ||
| it('returns the msg query parameter from an absolute permalink', () => { | ||
| expect(getMessageIdFromPermalink('https://open.rocket.chat/channel/general?msg=abc123')).toBe('abc123'); | ||
| }); | ||
|
|
||
| it('returns the msg query parameter when other parameters are present', () => { | ||
| expect(getMessageIdFromPermalink('https://open.rocket.chat/group/team?tab=thread&msg=xyz789&foo=bar')).toBe('xyz789'); | ||
| }); | ||
|
|
||
| it('returns the msg query parameter from a relative permalink', () => { | ||
| expect(getMessageIdFromPermalink('/direct/abc?msg=def456')).toBe('def456'); | ||
| }); | ||
|
|
||
| it('returns undefined when there is no msg query parameter', () => { | ||
| expect(getMessageIdFromPermalink('https://open.rocket.chat/channel/general')).toBeUndefined(); | ||
| expect(getMessageIdFromPermalink('https://open.rocket.chat/channel/general?msg=')).toBeUndefined(); | ||
| }); | ||
|
|
||
| it('returns undefined for empty or unparsable input', () => { | ||
| expect(getMessageIdFromPermalink(undefined)).toBeUndefined(); | ||
| expect(getMessageIdFromPermalink('')).toBeUndefined(); | ||
| expect(getMessageIdFromPermalink('http://[invalid')).toBeUndefined(); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| /** | ||
| * Extracts the message id from a message permalink such as | ||
| * `https://open.rocket.chat/channel/general?msg=abc123`. | ||
| * Returns `undefined` when the link has no `msg` query parameter or cannot be parsed. | ||
| */ | ||
| export const getMessageIdFromPermalink = (permalink: string | undefined): string | undefined => { | ||
| if (!permalink) { | ||
| return undefined; | ||
| } | ||
|
|
||
| try { | ||
| const msgId = new URL(permalink, 'http://localhost').searchParams.get('msg'); | ||
| return msgId || undefined; | ||
| } catch { | ||
| return undefined; | ||
| } | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
103 changes: 103 additions & 0 deletions
103
apps/meteor/client/providers/MediaPlayerProvider/MediaPlayerProvider.spec.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| import { mockAppRoot } from '@rocket.chat/mock-providers'; | ||
| import { act, renderHook } from '@testing-library/react'; | ||
| import type { ReactNode } from 'react'; | ||
|
|
||
| import type { PersistentAudioTrack } from './MediaPlayerContext'; | ||
| import { useMediaPlayer } from './MediaPlayerContext'; | ||
| import MediaPlayerProvider from './MediaPlayerProvider'; | ||
|
|
||
| const buildTrack = (overrides: Partial<PersistentAudioTrack> = {}): PersistentAudioTrack => ({ | ||
| id: 'mid1:url', | ||
| url: 'https://example.com/audio.mp3', | ||
| title: 'audio.mp3', | ||
| rid: 'room1', | ||
| mid: 'mid1', | ||
| username: 'john.doe', | ||
| ts: new Date('2024-01-01T00:00:00.000Z'), | ||
| pinned: false, | ||
| ...overrides, | ||
| }); | ||
|
|
||
| const wrapper = ({ children }: { children: ReactNode }) => { | ||
| const AppRoot = mockAppRoot().build(); | ||
| return ( | ||
| <AppRoot> | ||
| <MediaPlayerProvider>{children}</MediaPlayerProvider> | ||
| </AppRoot> | ||
| ); | ||
| }; | ||
|
|
||
| // jsdom does not implement media playback. | ||
| beforeAll(() => { | ||
| Object.defineProperty(HTMLMediaElement.prototype, 'play', { configurable: true, value: jest.fn().mockResolvedValue(undefined) }); | ||
| Object.defineProperty(HTMLMediaElement.prototype, 'load', { configurable: true, value: jest.fn() }); | ||
| }); | ||
|
|
||
| describe('MediaPlayerProvider updateTrack', () => { | ||
| it('adopts new mutable state for the active track', () => { | ||
| const { result } = renderHook(() => useMediaPlayer(), { wrapper }); | ||
|
|
||
| act(() => result.current.play(buildTrack({ pinned: false }))); | ||
|
|
||
| expect(result.current.track?.pinned).toBe(false); | ||
|
|
||
| act(() => result.current.updateTrack(buildTrack({ pinned: true }))); | ||
|
|
||
| expect(result.current.track?.pinned).toBe(true); | ||
| }); | ||
|
|
||
| // A message is not expected to gain a discussion id after it exists, so this is defensive: | ||
| // should the descriptors ever disagree, the player takes the one the message currently renders | ||
| // rather than keeping a value it can no longer justify. | ||
| it('refreshes the discussion id when the supplied descriptor differs', () => { | ||
| const { result } = renderHook(() => useMediaPlayer(), { wrapper }); | ||
|
|
||
| act(() => result.current.play(buildTrack())); | ||
|
|
||
| expect(result.current.track?.drid).toBeUndefined(); | ||
|
|
||
| act(() => result.current.updateTrack(buildTrack({ drid: 'disc1' }))); | ||
|
|
||
| expect(result.current.track?.drid).toBe('disc1'); | ||
| }); | ||
|
|
||
| it('ignores an update describing a different track', () => { | ||
| const { result } = renderHook(() => useMediaPlayer(), { wrapper }); | ||
|
|
||
| act(() => result.current.play(buildTrack({ pinned: false }))); | ||
| act(() => result.current.updateTrack(buildTrack({ id: 'mid2:url', mid: 'mid2', pinned: true }))); | ||
|
|
||
| expect(result.current.track?.id).toBe('mid1:url'); | ||
| expect(result.current.track?.pinned).toBe(false); | ||
| }); | ||
|
|
||
| it('ignores an update when no track is active', () => { | ||
| const { result } = renderHook(() => useMediaPlayer(), { wrapper }); | ||
|
|
||
| act(() => result.current.updateTrack(buildTrack({ pinned: true }))); | ||
|
|
||
| expect(result.current.track).toBeNull(); | ||
| }); | ||
|
|
||
| it('keeps the same track object when nothing mutable changed, so the player does not re-render', () => { | ||
| const { result } = renderHook(() => useMediaPlayer(), { wrapper }); | ||
|
|
||
| act(() => result.current.play(buildTrack())); | ||
|
|
||
| const before = result.current.track; | ||
|
|
||
| act(() => result.current.updateTrack(buildTrack())); | ||
|
|
||
| expect(result.current.track).toBe(before); | ||
| }); | ||
|
|
||
| it('does not disturb the url or identity of the active track', () => { | ||
| const { result } = renderHook(() => useMediaPlayer(), { wrapper }); | ||
|
|
||
| act(() => result.current.play(buildTrack())); | ||
| act(() => result.current.updateTrack(buildTrack({ pinned: true }))); | ||
|
|
||
| expect(result.current.track?.url).toBe('https://example.com/audio.mp3'); | ||
| expect(result.current.track?.mid).toBe('mid1'); | ||
| }); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.