Update voice broadcast time display (#9646)

This commit is contained in:
Michael Weimann 2022-11-30 08:47:29 +01:00 committed by GitHub
parent 5f6b1dda8d
commit 70a7961681
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 149 additions and 87 deletions

View file

@ -21,6 +21,10 @@ limitations under the License.
display: inline-block; display: inline-block;
font-size: $font-12px; font-size: $font-12px;
padding: $spacing-12; padding: $spacing-12;
.mx_Clock {
line-height: 1;
}
} }
.mx_VoiceBroadcastBody--pip { .mx_VoiceBroadcastBody--pip {
@ -44,9 +48,8 @@ limitations under the License.
} }
.mx_VoiceBroadcastBody_timerow { .mx_VoiceBroadcastBody_timerow {
align-items: center;
display: flex; display: flex;
gap: $spacing-4; justify-content: space-between;
} }
.mx_AccessibleButton.mx_VoiceBroadcastBody_blockButton { .mx_AccessibleButton.mx_VoiceBroadcastBody_blockButton {

View file

@ -125,6 +125,9 @@ export function formatTime(date: Date, showTwelveHour = false): string {
} }
export function formatSeconds(inSeconds: number): 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 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 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'); 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}:`; if (hours !== "00") output += `${hours}:`;
output += `${minutes}:${seconds}`; output += `${minutes}:${seconds}`;
if (isNegative) {
output = "-" + output;
}
return output; return output;
} }

View file

@ -46,10 +46,9 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
playback, playback,
}) => { }) => {
const { const {
duration, times,
liveness, liveness,
playbackState, playbackState,
position,
room, room,
sender, sender,
toggle, toggle,
@ -94,7 +93,7 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
if (playbackState !== VoiceBroadcastPlaybackState.Stopped) { if (playbackState !== VoiceBroadcastPlaybackState.Stopped) {
const onSeekBackwardButtonClick = () => { const onSeekBackwardButtonClick = () => {
playback.skipTo(Math.max(0, position - SEEK_TIME)); playback.skipTo(Math.max(0, times.position - SEEK_TIME));
}; };
seekBackwardButton = <SeekButton seekBackwardButton = <SeekButton
@ -104,7 +103,7 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
/>; />;
const onSeekForwardButtonClick = () => { const onSeekForwardButtonClick = () => {
playback.skipTo(Math.min(duration, position + SEEK_TIME)); playback.skipTo(Math.min(times.duration, times.position + SEEK_TIME));
}; };
seekForwardButton = <SeekButton seekForwardButton = <SeekButton
@ -132,9 +131,10 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
{ control } { control }
{ seekForwardButton } { seekForwardButton }
</div> </div>
<SeekBar playback={playback} />
<div className="mx_VoiceBroadcastBody_timerow"> <div className="mx_VoiceBroadcastBody_timerow">
<SeekBar playback={playback} /> <Clock seconds={times.position} />
<Clock seconds={duration} /> <Clock seconds={-times.timeLeft} />
</div> </div>
</div> </div>
); );

View file

@ -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( useTypedEventEmitter(
playback, playback,
VoiceBroadcastPlaybackEvent.LengthChanged, VoiceBroadcastPlaybackEvent.TimesChanged,
d => setDuration(d / 1000), t => setTimes(t),
);
const [position, setPosition] = useState(playback.timeSeconds);
useTypedEventEmitter(
playback,
VoiceBroadcastPlaybackEvent.PositionChanged,
p => setPosition(p / 1000),
); );
const [liveness, setLiveness] = useState(playback.getLiveness()); const [liveness, setLiveness] = useState(playback.getLiveness());
@ -62,10 +59,9 @@ export const useVoiceBroadcastPlayback = (playback: VoiceBroadcastPlayback) => {
); );
return { return {
duration, times,
liveness: liveness, liveness: liveness,
playbackState, playbackState,
position,
room: room, room: room,
sender: playback.infoEvent.sender, sender: playback.infoEvent.sender,
toggle: playbackToggle, toggle: playbackToggle,

View file

@ -43,16 +43,20 @@ export enum VoiceBroadcastPlaybackState {
} }
export enum VoiceBroadcastPlaybackEvent { export enum VoiceBroadcastPlaybackEvent {
PositionChanged = "position_changed", TimesChanged = "times_changed",
LengthChanged = "length_changed",
LivenessChanged = "liveness_changed", LivenessChanged = "liveness_changed",
StateChanged = "state_changed", StateChanged = "state_changed",
InfoStateChanged = "info_state_changed", InfoStateChanged = "info_state_changed",
} }
type VoiceBroadcastPlaybackTimes = {
duration: number;
position: number;
timeLeft: number;
};
interface EventMap { interface EventMap {
[VoiceBroadcastPlaybackEvent.PositionChanged]: (position: number) => void; [VoiceBroadcastPlaybackEvent.TimesChanged]: (times: VoiceBroadcastPlaybackTimes) => void;
[VoiceBroadcastPlaybackEvent.LengthChanged]: (length: number) => void;
[VoiceBroadcastPlaybackEvent.LivenessChanged]: (liveness: VoiceBroadcastLiveness) => void; [VoiceBroadcastPlaybackEvent.LivenessChanged]: (liveness: VoiceBroadcastLiveness) => void;
[VoiceBroadcastPlaybackEvent.StateChanged]: ( [VoiceBroadcastPlaybackEvent.StateChanged]: (
state: VoiceBroadcastPlaybackState, state: VoiceBroadcastPlaybackState,
@ -229,7 +233,7 @@ export class VoiceBroadcastPlayback
if (this.duration === duration) return; if (this.duration === duration) return;
this.duration = duration; this.duration = duration;
this.emit(VoiceBroadcastPlaybackEvent.LengthChanged, this.duration); this.emitTimesChanged();
this.liveData.update([this.timeSeconds, this.durationSeconds]); this.liveData.update([this.timeSeconds, this.durationSeconds]);
} }
@ -237,10 +241,21 @@ export class VoiceBroadcastPlayback
if (this.position === position) return; if (this.position === position) return;
this.position = position; this.position = position;
this.emit(VoiceBroadcastPlaybackEvent.PositionChanged, this.position); this.emitTimesChanged();
this.liveData.update([this.timeSeconds, this.durationSeconds]); 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<void> => { private onPlaybackStateChange = async (event: MatrixEvent, newState: PlaybackState): Promise<void> => {
if (event !== this.currentlyPlaying) return; if (event !== this.currentlyPlaying) return;
if (newState !== PlaybackState.Stopped) return; if (newState !== PlaybackState.Stopped) return;
@ -337,6 +352,10 @@ export class VoiceBroadcastPlayback
return this.duration / 1000; return this.duration / 1000;
} }
public get timeLeftSeconds(): number {
return Math.round(this.durationSeconds) - this.timeSeconds;
}
public async skipTo(timeSeconds: number): Promise<void> { public async skipTo(timeSeconds: number): Promise<void> {
const time = timeSeconds * 1000; const time = timeSeconds * 1000;
const event = this.chunkEvents.findByTime(time); const event = this.chunkEvents.findByTime(time);

View file

@ -29,12 +29,14 @@ describe("formatSeconds", () => {
expect(formatSeconds((60 * 60 * 3) + (60 * 31) + (55))).toBe("03:31:55"); 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 * 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");
expect(formatSeconds(-((60 * 60 * 3) + (60 * 31) + (0)))).toBe("-03:31:00");
}); });
it("correctly formats time without hours", () => { it("correctly formats time without hours", () => {
expect(formatSeconds((60 * 60 * 0) + (60 * 31) + (55))).toBe("31:55"); 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 * 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");
expect(formatSeconds(-((60 * 60 * 0) + (60 * 31) + (0)))).toBe("-31:00");
}); });
}); });

View file

@ -42,6 +42,7 @@ jest.mock("../../../../src/components/views/avatars/RoomAvatar", () => ({
describe("VoiceBroadcastPlaybackBody", () => { describe("VoiceBroadcastPlaybackBody", () => {
const userId = "@user:example.com"; const userId = "@user:example.com";
const roomId = "!room:example.com"; const roomId = "!room:example.com";
const duration = 23 * 60 + 42; // 23:42
let client: MatrixClient; let client: MatrixClient;
let infoEvent: MatrixEvent; let infoEvent: MatrixEvent;
let playback: VoiceBroadcastPlayback; let playback: VoiceBroadcastPlayback;
@ -66,7 +67,7 @@ describe("VoiceBroadcastPlaybackBody", () => {
jest.spyOn(playback, "getLiveness"); jest.spyOn(playback, "getLiveness");
jest.spyOn(playback, "getState"); jest.spyOn(playback, "getState");
jest.spyOn(playback, "skipTo"); 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", () => { describe("when rendering a buffering voice broadcast", () => {
@ -95,7 +96,11 @@ describe("VoiceBroadcastPlaybackBody", () => {
describe("and being in the middle of the playback", () => { describe("and being in the middle of the playback", () => {
beforeEach(() => { beforeEach(() => {
act(() => { 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(() => { beforeEach(() => {
act(() => { 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 () => { it("should render the times", async () => {
expect(await screen.findByText("00:42")).toBeInTheDocument(); expect(await screen.findByText("05:13")).toBeInTheDocument();
expect(await screen.findByText("-07:05")).toBeInTheDocument();
}); });
}); });
}); });

View file

@ -76,23 +76,28 @@ exports[`VoiceBroadcastPlaybackBody when rendering a 0/not-live broadcast should
/> />
</div> </div>
</div> </div>
<input
class="mx_SeekBar"
max="1"
min="0"
step="0.001"
style="--fillTo: 0;"
tabindex="0"
type="range"
value="0"
/>
<div <div
class="mx_VoiceBroadcastBody_timerow" class="mx_VoiceBroadcastBody_timerow"
> >
<input
class="mx_SeekBar"
max="1"
min="0"
step="0.001"
style="--fillTo: 0;"
tabindex="0"
type="range"
value="0"
/>
<span <span
class="mx_Clock" class="mx_Clock"
> >
23:42 00:00
</span>
<span
class="mx_Clock"
>
-23:42
</span> </span>
</div> </div>
</div> </div>
@ -183,23 +188,28 @@ exports[`VoiceBroadcastPlaybackBody when rendering a 1/live broadcast should ren
/> />
</div> </div>
</div> </div>
<input
class="mx_SeekBar"
max="1"
min="0"
step="0.001"
style="--fillTo: 0;"
tabindex="0"
type="range"
value="0"
/>
<div <div
class="mx_VoiceBroadcastBody_timerow" class="mx_VoiceBroadcastBody_timerow"
> >
<input
class="mx_SeekBar"
max="1"
min="0"
step="0.001"
style="--fillTo: 0;"
tabindex="0"
type="range"
value="0"
/>
<span <span
class="mx_Clock" class="mx_Clock"
> >
23:42 00:00
</span>
<span
class="mx_Clock"
>
-23:42
</span> </span>
</div> </div>
</div> </div>
@ -291,23 +301,28 @@ exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast s
/> />
</div> </div>
</div> </div>
<input
class="mx_SeekBar"
max="1"
min="0"
step="0.001"
style="--fillTo: 0;"
tabindex="0"
type="range"
value="0"
/>
<div <div
class="mx_VoiceBroadcastBody_timerow" class="mx_VoiceBroadcastBody_timerow"
> >
<input
class="mx_SeekBar"
max="1"
min="0"
step="0.001"
style="--fillTo: 0;"
tabindex="0"
type="range"
value="0"
/>
<span <span
class="mx_Clock" class="mx_Clock"
> >
23:42 00:00
</span>
<span
class="mx_Clock"
>
-23:42
</span> </span>
</div> </div>
</div> </div>
@ -390,23 +405,28 @@ exports[`VoiceBroadcastPlaybackBody when rendering a playing broadcast should re
/> />
</div> </div>
</div> </div>
<input
class="mx_SeekBar"
max="1"
min="0"
step="0.001"
style="--fillTo: 0;"
tabindex="0"
type="range"
value="0"
/>
<div <div
class="mx_VoiceBroadcastBody_timerow" class="mx_VoiceBroadcastBody_timerow"
> >
<input
class="mx_SeekBar"
max="1"
min="0"
step="0.001"
style="--fillTo: 0;"
tabindex="0"
type="range"
value="0"
/>
<span <span
class="mx_Clock" class="mx_Clock"
> >
23:42 00:00
</span>
<span
class="mx_Clock"
>
-23:42
</span> </span>
</div> </div>
</div> </div>
@ -469,23 +489,28 @@ exports[`VoiceBroadcastPlaybackBody when rendering a stopped broadcast should re
/> />
</div> </div>
</div> </div>
<input
class="mx_SeekBar"
max="1"
min="0"
step="0.001"
style="--fillTo: 0;"
tabindex="0"
type="range"
value="0"
/>
<div <div
class="mx_VoiceBroadcastBody_timerow" class="mx_VoiceBroadcastBody_timerow"
> >
<input
class="mx_SeekBar"
max="1"
min="0"
step="0.001"
style="--fillTo: 0;"
tabindex="0"
type="range"
value="0"
/>
<span <span
class="mx_Clock" class="mx_Clock"
> >
23:42 00:00
</span>
<span
class="mx_Clock"
>
-23:42
</span> </span>
</div> </div>
</div> </div>