Skip to content

Commit b45488f

Browse files
authored
Fix duration of voice message in timeline (#30973)
* fix: duration of voice message in timeline * Revert "Fix clocks rendering at 00:00 when playback had not begun." This reverts commit 68bcfbe. * refactor: cleaner clock states check * refactor: cleaner `onPlaybackStateChange` condition * fix: `timeSeconds` is always a number * refactor: allow playing and paused state to update clock state * test: add test * test: add moar test * refactor: use `currentClockState`
1 parent 4db6ff5 commit b45488f

File tree

3 files changed

+50
-37
lines changed

3 files changed

+50
-37
lines changed

src/audio/PlaybackClock.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export class PlaybackClock implements IDestroyable {
7474
// remainder of the division operation, we're assuming that playback is
7575
// incomplete or stopped, thus giving an accurate position within the active
7676
// clip segment.
77-
return (this.context.currentTime - this.clipStart) % this.clipDuration;
77+
return (this.context.currentTime - this.clipStart) % this.clipDuration || 0;
7878
}
7979

8080
public get liveData(): SimpleObservable<number[]> {

src/audio/PlaybackQueue.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
66
Please see LICENSE files in the repository root for full details.
77
*/
88

9-
import { type MatrixEvent, type Room, EventType } from "matrix-js-sdk/src/matrix";
9+
import { EventType, type MatrixEvent, type Room } from "matrix-js-sdk/src/matrix";
1010
import { logger } from "matrix-js-sdk/src/logger";
1111

1212
import { type Playback, PlaybackState } from "./Playback";
@@ -76,6 +76,12 @@ export class PlaybackQueue {
7676
const val = localStorage.getItem(`mx_voice_message_clocks_${this.room.roomId}`);
7777
if (!!val) {
7878
this.clockStates = new Map<string, number>(JSON.parse(val));
79+
// Clean out any null values (from older versions)
80+
for (const [key, value] of this.clockStates.entries()) {
81+
if (value == null) {
82+
this.clockStates.delete(key);
83+
}
84+
}
7985
}
8086
}
8187

@@ -90,14 +96,10 @@ export class PlaybackQueue {
9096
// Remember where the user got to in playback
9197
const wasLastPlaying = this.currentPlaybackId === mxEvent.getId();
9298
const currentClockState = this.clockStates.get(mxEvent.getId()!);
99+
93100
if (newState === PlaybackState.Stopped && currentClockState !== undefined && !wasLastPlaying) {
94-
if (currentClockState > 0) {
95-
// skipTo will pause playback, which causes the clock to render the current
96-
// playback seconds. If the clock state is 0, then we can just ignore
97-
// skipping entirely.
98-
// noinspection JSIgnoredPromiseFromCall
99-
playback.skipTo(currentClockState);
100-
}
101+
// noinspection JSIgnoredPromiseFromCall
102+
playback.skipTo(currentClockState);
101103
} else if (newState === PlaybackState.Stopped) {
102104
// Remove the now-useless clock for some space savings
103105
this.clockStates.delete(mxEvent.getId()!);
@@ -207,10 +209,8 @@ export class PlaybackQueue {
207209
}
208210

209211
private onPlaybackClock(playback: Playback, mxEvent: MatrixEvent, clocks: number[]): void {
210-
if (playback.currentState === PlaybackState.Decoding) return; // ignore pre-ready values
212+
if (playback.currentState !== PlaybackState.Playing && playback.currentState !== PlaybackState.Paused) return; // ignore pre-ready values
211213

212-
if (playback.currentState !== PlaybackState.Stopped) {
213-
this.clockStates.set(mxEvent.getId()!, clocks[0]); // [0] is the current seek position
214-
}
214+
this.clockStates.set(mxEvent.getId()!, clocks[0]); // [0] is the current seek position
215215
}
216216
}

test/unit-tests/audio/PlaybackQueue-test.ts

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,57 +7,50 @@ Please see LICENSE files in the repository root for full details.
77

88
import { type Mocked } from "jest-mock";
99
import { type MatrixEvent, type Room } from "matrix-js-sdk/src/matrix";
10-
import { SimpleObservable } from "matrix-widget-api";
1110

1211
import { PlaybackQueue } from "../../../src/audio/PlaybackQueue";
13-
import { PlaybackState, type Playback } from "../../../src/audio/Playback";
14-
import { MockEventEmitter } from "../../test-utils";
12+
import { type Playback, PlaybackState } from "../../../src/audio/Playback";
1513
import { UPDATE_EVENT } from "../../../src/stores/AsyncStore";
14+
import { MockedPlayback } from "./MockedPlayback";
1615

1716
describe("PlaybackQueue", () => {
1817
let playbackQueue: PlaybackQueue;
18+
let mockRoom: Mocked<Room>;
1919

2020
beforeEach(() => {
21-
const mockRoom = {
21+
mockRoom = {
2222
getMember: jest.fn(),
2323
} as unknown as Mocked<Room>;
2424
playbackQueue = new PlaybackQueue(mockRoom);
2525
});
2626

27-
it("does not call skipTo on playback if clock advances to 0s", () => {
27+
it.each([
28+
[PlaybackState.Playing, true],
29+
[PlaybackState.Paused, true],
30+
[PlaybackState.Preparing, false],
31+
[PlaybackState.Decoding, false],
32+
[PlaybackState.Stopped, false],
33+
])("should save (or not) the clock PlayBackState=%s expected=%s", (playbackState, expected) => {
2834
const mockEvent = {
2935
getId: jest.fn().mockReturnValue("$foo:bar"),
3036
} as unknown as Mocked<MatrixEvent>;
31-
const mockPlayback = new MockEventEmitter({
32-
clockInfo: {
33-
liveData: new SimpleObservable<number[]>(),
34-
},
35-
skipTo: jest.fn(),
36-
}) as unknown as Mocked<Playback>;
37+
const mockPlayback = new MockedPlayback(playbackState, 0, 0) as unknown as Mocked<Playback>;
3738

3839
// Enqueue
3940
playbackQueue.unsortedEnqueue(mockEvent, mockPlayback);
4041

4142
// Emit our clockInfo of 0, which will playbackQueue to save the state.
42-
mockPlayback.clockInfo.liveData.update([0]);
43-
44-
// Fire an update event to say that we have stopped.
45-
// Note that Playback really emits an UPDATE_EVENT whenever state changes, the types are lies.
46-
mockPlayback.emit(UPDATE_EVENT as any, PlaybackState.Stopped);
43+
mockPlayback.clockInfo.liveData.update([1]);
4744

48-
expect(mockPlayback.skipTo).not.toHaveBeenCalled();
45+
// @ts-ignore
46+
expect(playbackQueue.clockStates.has(mockEvent.getId()!)).toBe(expected);
4947
});
5048

51-
it("does call skipTo on playback if clock advances to 0s", () => {
49+
it("does call skipTo on playback if clock advances to 1s", () => {
5250
const mockEvent = {
5351
getId: jest.fn().mockReturnValue("$foo:bar"),
5452
} as unknown as Mocked<MatrixEvent>;
55-
const mockPlayback = new MockEventEmitter({
56-
clockInfo: {
57-
liveData: new SimpleObservable<number[]>(),
58-
},
59-
skipTo: jest.fn(),
60-
}) as unknown as Mocked<Playback>;
53+
const mockPlayback = new MockedPlayback(PlaybackState.Playing, 0, 0) as unknown as Mocked<Playback>;
6154

6255
// Enqueue
6356
playbackQueue.unsortedEnqueue(mockEvent, mockPlayback);
@@ -71,4 +64,24 @@ describe("PlaybackQueue", () => {
7164

7265
expect(mockPlayback.skipTo).toHaveBeenCalledWith(1);
7366
});
67+
68+
it("should ignore the nullish clock state when loading", () => {
69+
const clockStates = new Map([
70+
["a", 1],
71+
["b", null],
72+
["c", 3],
73+
]);
74+
localStorage.setItem(
75+
`mx_voice_message_clocks_${mockRoom.roomId}`,
76+
JSON.stringify(Array.from(clockStates.entries())),
77+
);
78+
playbackQueue = new PlaybackQueue(mockRoom);
79+
80+
// @ts-ignore
81+
expect(playbackQueue.clockStates.has("a")).toBe(true);
82+
// @ts-ignore
83+
expect(playbackQueue.clockStates.has("b")).toBe(false);
84+
// @ts-ignore
85+
expect(playbackQueue.clockStates.has("c")).toBe(true);
86+
});
7487
});

0 commit comments

Comments
 (0)