From 70a7961681eb35ee47c40db04d314564921cc7bb Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Wed, 30 Nov 2022 08:47:29 +0100 Subject: [PATCH] Update voice broadcast time display (#9646) --- .../molecules/_VoiceBroadcastBody.pcss | 7 +- src/DateUtils.ts | 7 + .../molecules/VoiceBroadcastPlaybackBody.tsx | 12 +- .../hooks/useVoiceBroadcastPlayback.ts | 20 ++- .../models/VoiceBroadcastPlayback.ts | 31 +++- test/utils/DateUtils-test.ts | 2 + .../VoiceBroadcastPlaybackBody-test.tsx | 22 ++- .../VoiceBroadcastPlaybackBody-test.tsx.snap | 135 +++++++++++------- 8 files changed, 149 insertions(+), 87 deletions(-) diff --git a/res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss b/res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss index bf4118b806..3d463cbc9b 100644 --- a/res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss +++ b/res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss @@ -21,6 +21,10 @@ limitations under the License. display: inline-block; font-size: $font-12px; padding: $spacing-12; + + .mx_Clock { + line-height: 1; + } } .mx_VoiceBroadcastBody--pip { @@ -44,9 +48,8 @@ limitations under the License. } .mx_VoiceBroadcastBody_timerow { - align-items: center; display: flex; - gap: $spacing-4; + justify-content: space-between; } .mx_AccessibleButton.mx_VoiceBroadcastBody_blockButton { diff --git a/src/DateUtils.ts b/src/DateUtils.ts index e03dace213..b6fd8a0bee 100644 --- a/src/DateUtils.ts +++ b/src/DateUtils.ts @@ -125,6 +125,9 @@ export function formatTime(date: Date, showTwelveHour = false): string { } export function formatSeconds(inSeconds: number): string { + const isNegative = inSeconds < 0; + inSeconds = Math.abs(inSeconds); + const hours = Math.floor(inSeconds / (60 * 60)).toFixed(0).padStart(2, '0'); const minutes = Math.floor((inSeconds % (60 * 60)) / 60).toFixed(0).padStart(2, '0'); const seconds = Math.floor(((inSeconds % (60 * 60)) % 60)).toFixed(0).padStart(2, '0'); @@ -133,6 +136,10 @@ export function formatSeconds(inSeconds: number): string { if (hours !== "00") output += `${hours}:`; output += `${minutes}:${seconds}`; + if (isNegative) { + output = "-" + output; + } + return output; } diff --git a/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx b/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx index 6c16223388..7ba06a1501 100644 --- a/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx +++ b/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx @@ -46,10 +46,9 @@ export const VoiceBroadcastPlaybackBody: React.FC { const { - duration, + times, liveness, playbackState, - position, room, sender, toggle, @@ -94,7 +93,7 @@ export const VoiceBroadcastPlaybackBody: React.FC { - playback.skipTo(Math.max(0, position - SEEK_TIME)); + playback.skipTo(Math.max(0, times.position - SEEK_TIME)); }; seekBackwardButton = ; const onSeekForwardButtonClick = () => { - playback.skipTo(Math.min(duration, position + SEEK_TIME)); + playback.skipTo(Math.min(times.duration, times.position + SEEK_TIME)); }; seekForwardButton = +
- - + +
); diff --git a/src/voice-broadcast/hooks/useVoiceBroadcastPlayback.ts b/src/voice-broadcast/hooks/useVoiceBroadcastPlayback.ts index 1828b31d01..0b515c4437 100644 --- a/src/voice-broadcast/hooks/useVoiceBroadcastPlayback.ts +++ b/src/voice-broadcast/hooks/useVoiceBroadcastPlayback.ts @@ -40,18 +40,15 @@ export const useVoiceBroadcastPlayback = (playback: VoiceBroadcastPlayback) => { }, ); - const [duration, setDuration] = useState(playback.durationSeconds); + const [times, setTimes] = useState({ + duration: playback.durationSeconds, + position: playback.timeSeconds, + timeLeft: playback.timeLeftSeconds, + }); useTypedEventEmitter( playback, - VoiceBroadcastPlaybackEvent.LengthChanged, - d => setDuration(d / 1000), - ); - - const [position, setPosition] = useState(playback.timeSeconds); - useTypedEventEmitter( - playback, - VoiceBroadcastPlaybackEvent.PositionChanged, - p => setPosition(p / 1000), + VoiceBroadcastPlaybackEvent.TimesChanged, + t => setTimes(t), ); const [liveness, setLiveness] = useState(playback.getLiveness()); @@ -62,10 +59,9 @@ export const useVoiceBroadcastPlayback = (playback: VoiceBroadcastPlayback) => { ); return { - duration, + times, liveness: liveness, playbackState, - position, room: room, sender: playback.infoEvent.sender, toggle: playbackToggle, diff --git a/src/voice-broadcast/models/VoiceBroadcastPlayback.ts b/src/voice-broadcast/models/VoiceBroadcastPlayback.ts index 2c4054a825..70c7a4d82f 100644 --- a/src/voice-broadcast/models/VoiceBroadcastPlayback.ts +++ b/src/voice-broadcast/models/VoiceBroadcastPlayback.ts @@ -43,16 +43,20 @@ export enum VoiceBroadcastPlaybackState { } export enum VoiceBroadcastPlaybackEvent { - PositionChanged = "position_changed", - LengthChanged = "length_changed", + TimesChanged = "times_changed", LivenessChanged = "liveness_changed", StateChanged = "state_changed", InfoStateChanged = "info_state_changed", } +type VoiceBroadcastPlaybackTimes = { + duration: number; + position: number; + timeLeft: number; +}; + interface EventMap { - [VoiceBroadcastPlaybackEvent.PositionChanged]: (position: number) => void; - [VoiceBroadcastPlaybackEvent.LengthChanged]: (length: number) => void; + [VoiceBroadcastPlaybackEvent.TimesChanged]: (times: VoiceBroadcastPlaybackTimes) => void; [VoiceBroadcastPlaybackEvent.LivenessChanged]: (liveness: VoiceBroadcastLiveness) => void; [VoiceBroadcastPlaybackEvent.StateChanged]: ( state: VoiceBroadcastPlaybackState, @@ -229,7 +233,7 @@ export class VoiceBroadcastPlayback if (this.duration === duration) return; this.duration = duration; - this.emit(VoiceBroadcastPlaybackEvent.LengthChanged, this.duration); + this.emitTimesChanged(); this.liveData.update([this.timeSeconds, this.durationSeconds]); } @@ -237,10 +241,21 @@ export class VoiceBroadcastPlayback if (this.position === position) return; this.position = position; - this.emit(VoiceBroadcastPlaybackEvent.PositionChanged, this.position); + this.emitTimesChanged(); this.liveData.update([this.timeSeconds, this.durationSeconds]); } + private emitTimesChanged(): void { + this.emit( + VoiceBroadcastPlaybackEvent.TimesChanged, + { + duration: this.durationSeconds, + position: this.timeSeconds, + timeLeft: this.timeLeftSeconds, + }, + ); + } + private onPlaybackStateChange = async (event: MatrixEvent, newState: PlaybackState): Promise => { if (event !== this.currentlyPlaying) return; if (newState !== PlaybackState.Stopped) return; @@ -337,6 +352,10 @@ export class VoiceBroadcastPlayback return this.duration / 1000; } + public get timeLeftSeconds(): number { + return Math.round(this.durationSeconds) - this.timeSeconds; + } + public async skipTo(timeSeconds: number): Promise { const time = timeSeconds * 1000; const event = this.chunkEvents.findByTime(time); diff --git a/test/utils/DateUtils-test.ts b/test/utils/DateUtils-test.ts index 55893e48d8..9cb020571e 100644 --- a/test/utils/DateUtils-test.ts +++ b/test/utils/DateUtils-test.ts @@ -29,12 +29,14 @@ describe("formatSeconds", () => { expect(formatSeconds((60 * 60 * 3) + (60 * 31) + (55))).toBe("03:31:55"); expect(formatSeconds((60 * 60 * 3) + (60 * 0) + (55))).toBe("03:00:55"); expect(formatSeconds((60 * 60 * 3) + (60 * 31) + (0))).toBe("03:31:00"); + expect(formatSeconds(-((60 * 60 * 3) + (60 * 31) + (0)))).toBe("-03:31:00"); }); it("correctly formats time without hours", () => { expect(formatSeconds((60 * 60 * 0) + (60 * 31) + (55))).toBe("31:55"); expect(formatSeconds((60 * 60 * 0) + (60 * 0) + (55))).toBe("00:55"); expect(formatSeconds((60 * 60 * 0) + (60 * 31) + (0))).toBe("31:00"); + expect(formatSeconds(-((60 * 60 * 0) + (60 * 31) + (0)))).toBe("-31:00"); }); }); diff --git a/test/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody-test.tsx b/test/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody-test.tsx index a2e95a856e..901a4feb82 100644 --- a/test/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody-test.tsx +++ b/test/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody-test.tsx @@ -42,6 +42,7 @@ jest.mock("../../../../src/components/views/avatars/RoomAvatar", () => ({ describe("VoiceBroadcastPlaybackBody", () => { const userId = "@user:example.com"; const roomId = "!room:example.com"; + const duration = 23 * 60 + 42; // 23:42 let client: MatrixClient; let infoEvent: MatrixEvent; let playback: VoiceBroadcastPlayback; @@ -66,7 +67,7 @@ describe("VoiceBroadcastPlaybackBody", () => { jest.spyOn(playback, "getLiveness"); jest.spyOn(playback, "getState"); jest.spyOn(playback, "skipTo"); - jest.spyOn(playback, "durationSeconds", "get").mockReturnValue(23 * 60 + 42); // 23:42 + jest.spyOn(playback, "durationSeconds", "get").mockReturnValue(duration); }); describe("when rendering a buffering voice broadcast", () => { @@ -95,7 +96,11 @@ describe("VoiceBroadcastPlaybackBody", () => { describe("and being in the middle of the playback", () => { beforeEach(() => { act(() => { - playback.emit(VoiceBroadcastPlaybackEvent.PositionChanged, 10 * 60 * 1000); // 10:00 + playback.emit(VoiceBroadcastPlaybackEvent.TimesChanged, { + duration, + position: 10 * 60, + timeLeft: duration - 10 * 60, + }); }); }); @@ -146,15 +151,20 @@ describe("VoiceBroadcastPlaybackBody", () => { }); }); - describe("and the length updated", () => { + describe("and the times update", () => { beforeEach(() => { act(() => { - playback.emit(VoiceBroadcastPlaybackEvent.LengthChanged, 42000); // 00:42 + playback.emit(VoiceBroadcastPlaybackEvent.TimesChanged, { + duration, + position: 5 * 60 + 13, + timeLeft: 7 * 60 + 5, + }); }); }); - it("should render the new length", async () => { - expect(await screen.findByText("00:42")).toBeInTheDocument(); + it("should render the times", async () => { + expect(await screen.findByText("05:13")).toBeInTheDocument(); + expect(await screen.findByText("-07:05")).toBeInTheDocument(); }); }); }); diff --git a/test/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap b/test/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap index c14cf94539..f5d3e90b3c 100644 --- a/test/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap +++ b/test/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap @@ -76,23 +76,28 @@ exports[`VoiceBroadcastPlaybackBody when rendering a 0/not-live broadcast should /> +
- - 23:42 + 00:00 + + + -23:42
@@ -183,23 +188,28 @@ exports[`VoiceBroadcastPlaybackBody when rendering a 1/live broadcast should ren /> +
- - 23:42 + 00:00 + + + -23:42
@@ -291,23 +301,28 @@ exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast s /> +
- - 23:42 + 00:00 + + + -23:42
@@ -390,23 +405,28 @@ exports[`VoiceBroadcastPlaybackBody when rendering a playing broadcast should re /> +
- - 23:42 + 00:00 + + + -23:42
@@ -469,23 +489,28 @@ exports[`VoiceBroadcastPlaybackBody when rendering a stopped broadcast should re /> +
- - 23:42 + 00:00 + + + -23:42