Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Handle voice broadcast last_chunk_sequence (#9812)
Browse files Browse the repository at this point in the history
  • Loading branch information
weeman1337 authored Dec 28, 2022
1 parent 539a50a commit 2b7d106
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 16 deletions.
33 changes: 30 additions & 3 deletions src/voice-broadcast/models/VoiceBroadcastPlayback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ import { PlaybackManager } from "../../audio/PlaybackManager";
import { UPDATE_EVENT } from "../../stores/AsyncStore";
import { MediaEventHelper } from "../../utils/MediaEventHelper";
import { IDestroyable } from "../../utils/IDestroyable";
import { VoiceBroadcastLiveness, VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "..";
import {
VoiceBroadcastLiveness,
VoiceBroadcastInfoEventType,
VoiceBroadcastInfoState,
VoiceBroadcastInfoEventContent,
} from "..";
import { RelationsHelper, RelationsHelperEvent } from "../../events/RelationsHelper";
import { VoiceBroadcastChunkEvents } from "../utils/VoiceBroadcastChunkEvents";
import { determineVoiceBroadcastLiveness } from "../utils/determineVoiceBroadcastLiveness";
Expand Down Expand Up @@ -151,12 +156,20 @@ export class VoiceBroadcastPlayback
this.setDuration(this.chunkEvents.getLength());

if (this.getState() === VoiceBroadcastPlaybackState.Buffering) {
await this.start();
await this.startOrPlayNext();
}

return true;
};

private startOrPlayNext = async (): Promise<void> => {
if (this.currentlyPlaying) {
return this.playNext();
}

return await this.start();
};

private addInfoEvent = (event: MatrixEvent): void => {
if (this.lastInfoEvent && this.lastInfoEvent.getTs() >= event.getTs()) {
// Only handle newer events
Expand Down Expand Up @@ -263,14 +276,28 @@ export class VoiceBroadcastPlayback
return this.playEvent(next);
}

if (this.getInfoState() === VoiceBroadcastInfoState.Stopped) {
if (
this.getInfoState() === VoiceBroadcastInfoState.Stopped &&
this.chunkEvents.getSequenceForEvent(this.currentlyPlaying) === this.lastChunkSequence
) {
this.stop();
} else {
// No more chunks available, although the broadcast is not finished → enter buffering state.
this.setState(VoiceBroadcastPlaybackState.Buffering);
}
}

/**
* @returns {number} The last chunk sequence from the latest info event.
* Falls back to the length of received chunks if the info event does not provide the number.
*/
private get lastChunkSequence(): number {
return (
this.lastInfoEvent.getContent<VoiceBroadcastInfoEventContent>()?.last_chunk_sequence ||
this.chunkEvents.getNumberOfEvents()
);
}

private async playEvent(event: MatrixEvent): Promise<void> {
this.setState(VoiceBroadcastPlaybackState.Playing);
this.currentlyPlaying = event;
Expand Down
13 changes: 13 additions & 0 deletions src/voice-broadcast/utils/VoiceBroadcastChunkEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,19 @@ export class VoiceBroadcastChunkEvents {
return this.events.indexOf(event) >= this.events.length - 1;
}

public getSequenceForEvent(event: MatrixEvent): number | null {
const sequence = parseInt(event.getContent()?.[VoiceBroadcastChunkEventType]?.sequence, 10);
if (!isNaN(sequence)) return sequence;

if (this.events.includes(event)) return this.events.indexOf(event) + 1;

return null;
}

public getNumberOfEvents(): number {
return this.events.length;
}

private calculateChunkLength(event: MatrixEvent): number {
return event.getContent()?.["org.matrix.msc1767.audio"]?.duration || event.getContent()?.info?.duration || 0;
}
Expand Down
62 changes: 49 additions & 13 deletions test/voice-broadcast/models/VoiceBroadcastPlayback-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,26 +279,62 @@ describe("VoiceBroadcastPlayback", () => {
expect(chunk1Playback.play).not.toHaveBeenCalled();
});

describe("and the playback of the last chunk ended", () => {
beforeEach(() => {
chunk2Playback.emit(PlaybackState.Stopped);
});
describe(
"and receiving a stop info event with last_chunk_sequence = 2 and " +
"the playback of the last available chunk ends",
() => {
beforeEach(() => {
const stoppedEvent = mkVoiceBroadcastInfoStateEvent(
roomId,
VoiceBroadcastInfoState.Stopped,
client.getSafeUserId(),
client.deviceId!,
infoEvent,
2,
);
room.addLiveEvents([stoppedEvent]);
room.relations.aggregateChildEvent(stoppedEvent);
chunk2Playback.emit(PlaybackState.Stopped);
});

itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Buffering);
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Stopped);
},
);

describe("and the next chunk arrived", () => {
describe(
"and receiving a stop info event with last_chunk_sequence = 3 and " +
"the playback of the last available chunk ends",
() => {
beforeEach(() => {
room.addLiveEvents([chunk3Event]);
room.relations.aggregateChildEvent(chunk3Event);
const stoppedEvent = mkVoiceBroadcastInfoStateEvent(
roomId,
VoiceBroadcastInfoState.Stopped,
client.getSafeUserId(),
client.deviceId!,
infoEvent,
3,
);
room.addLiveEvents([stoppedEvent]);
room.relations.aggregateChildEvent(stoppedEvent);
chunk2Playback.emit(PlaybackState.Stopped);
});

itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Playing);
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Buffering);

describe("and the next chunk arrives", () => {
beforeEach(() => {
room.addLiveEvents([chunk3Event]);
room.relations.aggregateChildEvent(chunk3Event);
});

itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Playing);

it("should play the next chunk", () => {
expect(chunk3Playback.play).toHaveBeenCalled();
it("should play the next chunk", () => {
expect(chunk3Playback.play).toHaveBeenCalled();
});
});
});
});
},
);

describe("and the info event is deleted", () => {
beforeEach(() => {
Expand Down
16 changes: 16 additions & 0 deletions test/voice-broadcast/utils/VoiceBroadcastChunkEvents-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ describe("VoiceBroadcastChunkEvents", () => {
]);
});

it("getNumberOfEvents should return 4", () => {
expect(chunkEvents.getNumberOfEvents()).toBe(4);
});

it("getLength should return the total length of all chunks", () => {
expect(chunkEvents.getLength()).toBe(3259);
});
Expand Down Expand Up @@ -110,6 +114,7 @@ describe("VoiceBroadcastChunkEvents", () => {
eventSeq3Time2T,
eventSeq4Time1,
]);
expect(chunkEvents.getNumberOfEvents()).toBe(4);
});
});
});
Expand All @@ -129,6 +134,17 @@ describe("VoiceBroadcastChunkEvents", () => {
eventSeqUTime3,
eventSeq2Time4Dup,
]);
expect(chunkEvents.getNumberOfEvents()).toBe(5);
});

describe("getSequenceForEvent", () => {
it("should return the sequence if provided by the event", () => {
expect(chunkEvents.getSequenceForEvent(eventSeq3Time2)).toBe(3);
});

it("should return the index if no sequence provided by event", () => {
expect(chunkEvents.getSequenceForEvent(eventSeqUTime3)).toBe(4);
});
});
});
});
8 changes: 8 additions & 0 deletions test/voice-broadcast/utils/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,16 @@ import {
} from "../../../src/voice-broadcast";
import { mkEvent } from "../../test-utils";

// timestamp incremented on each call to prevent duplicate timestamp
let timestamp = new Date().getTime();

export const mkVoiceBroadcastInfoStateEvent = (
roomId: Optional<string>,
state: Optional<VoiceBroadcastInfoState>,
senderId: Optional<string>,
senderDeviceId: Optional<string>,
startedInfoEvent?: MatrixEvent,
lastChunkSequence?: number,
): MatrixEvent => {
const relationContent = {};

Expand All @@ -40,6 +44,8 @@ export const mkVoiceBroadcastInfoStateEvent = (
};
}

const lastChunkSequenceContent = lastChunkSequence ? { last_chunk_sequence: lastChunkSequence } : {};

return mkEvent({
event: true,
// @ts-ignore allow everything here for edge test cases
Expand All @@ -53,7 +59,9 @@ export const mkVoiceBroadcastInfoStateEvent = (
state,
device_id: senderDeviceId,
...relationContent,
...lastChunkSequenceContent,
},
ts: timestamp++,
});
};

Expand Down

0 comments on commit 2b7d106

Please sign in to comment.