Start playback for ongoing broadcast with the last chunk (#9434)

This commit is contained in:
Michael Weimann 2022-10-17 16:31:22 +02:00 committed by GitHub
parent 1b74782854
commit 631720b21b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 140 additions and 101 deletions

View file

@ -56,7 +56,6 @@ export class VoiceBroadcastPlayback
private state = VoiceBroadcastPlaybackState.Stopped; private state = VoiceBroadcastPlaybackState.Stopped;
private infoState: VoiceBroadcastInfoState; private infoState: VoiceBroadcastInfoState;
private chunkEvents = new Map<string, MatrixEvent>(); private chunkEvents = new Map<string, MatrixEvent>();
/** Holds the playback queue with a 1-based index (sequence number) */
private queue: Playback[] = []; private queue: Playback[] = [];
private currentlyPlaying: Playback; private currentlyPlaying: Playback;
private lastInfoEvent: MatrixEvent; private lastInfoEvent: MatrixEvent;
@ -138,7 +137,7 @@ export class VoiceBroadcastPlayback
private async enqueueChunk(chunkEvent: MatrixEvent) { private async enqueueChunk(chunkEvent: MatrixEvent) {
const sequenceNumber = parseInt(chunkEvent.getContent()?.[VoiceBroadcastChunkEventType]?.sequence, 10); const sequenceNumber = parseInt(chunkEvent.getContent()?.[VoiceBroadcastChunkEventType]?.sequence, 10);
if (isNaN(sequenceNumber)) return; if (isNaN(sequenceNumber) || sequenceNumber < 1) return;
const helper = new MediaEventHelper(chunkEvent); const helper = new MediaEventHelper(chunkEvent);
const blob = await helper.sourceBlob.value; const blob = await helper.sourceBlob.value;
@ -146,7 +145,7 @@ export class VoiceBroadcastPlayback
const playback = PlaybackManager.instance.createPlaybackInstance(buffer); const playback = PlaybackManager.instance.createPlaybackInstance(buffer);
await playback.prepare(); await playback.prepare();
playback.clockInfo.populatePlaceholdersFrom(chunkEvent); playback.clockInfo.populatePlaceholdersFrom(chunkEvent);
this.queue[sequenceNumber] = playback; this.queue[sequenceNumber - 1] = playback; // -1 because the sequence number starts at 1
playback.on(UPDATE_EVENT, (state) => this.onPlaybackStateChange(playback, state)); playback.on(UPDATE_EVENT, (state) => this.onPlaybackStateChange(playback, state));
} }
@ -171,17 +170,18 @@ export class VoiceBroadcastPlayback
await this.loadChunks(); await this.loadChunks();
} }
if (this.queue.length === 0 || !this.queue[1]) { const toPlayIndex = this.getInfoState() === VoiceBroadcastInfoState.Stopped
// set to stopped fi the queue is empty of the first chunk (sequence number: 1-based index) is missing ? 0 // start at the beginning for an ended voice broadcast
: this.queue.length - 1; // start at the current chunk for an ongoing voice broadcast
if (this.queue.length === 0 || !this.queue[toPlayIndex]) {
this.setState(VoiceBroadcastPlaybackState.Stopped); this.setState(VoiceBroadcastPlaybackState.Stopped);
return; return;
} }
this.setState(VoiceBroadcastPlaybackState.Playing); this.setState(VoiceBroadcastPlaybackState.Playing);
// index of the first chunk is the first sequence number this.currentlyPlaying = this.queue[toPlayIndex];
const first = this.queue[1]; await this.currentlyPlaying.play();
this.currentlyPlaying = first;
await first.play();
} }
public get length(): number { public get length(): number {

View file

@ -25,6 +25,7 @@ import { MediaEventHelper } from "../../../src/utils/MediaEventHelper";
import { import {
VoiceBroadcastChunkEventType, VoiceBroadcastChunkEventType,
VoiceBroadcastInfoEventType, VoiceBroadcastInfoEventType,
VoiceBroadcastInfoState,
VoiceBroadcastPlayback, VoiceBroadcastPlayback,
VoiceBroadcastPlaybackEvent, VoiceBroadcastPlaybackEvent,
VoiceBroadcastPlaybackState, VoiceBroadcastPlaybackState,
@ -100,15 +101,33 @@ describe("VoiceBroadcastPlayback", () => {
}; };
}; };
beforeAll(() => { const mkInfoEvent = (state: VoiceBroadcastInfoState) => {
client = stubClient(); return mkEvent({
infoEvent = mkEvent({
event: true, event: true,
type: VoiceBroadcastInfoEventType, type: VoiceBroadcastInfoEventType,
user: userId, user: userId,
room: roomId, room: roomId,
content: {}, content: {
state,
},
}); });
};
const mkPlayback = () => {
const playback = new VoiceBroadcastPlayback(infoEvent, client);
jest.spyOn(playback, "removeAllListeners");
playback.on(VoiceBroadcastPlaybackEvent.StateChanged, onStateChanged);
return playback;
};
const setUpChunkEvents = (chunkEvents: MatrixEvent[]) => {
const relations = new Relations(RelationType.Reference, EventType.RoomMessage, client);
jest.spyOn(relations, "getRelations").mockReturnValue(chunkEvents);
mocked(getReferenceRelationsForEvent).mockReturnValue(relations);
};
beforeAll(() => {
client = stubClient();
// crap event to test 0 as first sequence number // crap event to test 0 as first sequence number
chunk0Event = mkChunkEvent(0); chunk0Event = mkChunkEvent(0);
@ -139,21 +158,42 @@ describe("VoiceBroadcastPlayback", () => {
}); });
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks();
onStateChanged = jest.fn(); onStateChanged = jest.fn();
playback = new VoiceBroadcastPlayback(infoEvent, client);
jest.spyOn(playback, "removeAllListeners");
playback.on(VoiceBroadcastPlaybackEvent.StateChanged, onStateChanged);
}); });
describe("when there is only a 0 sequence event", () => { describe("when there is a running voice broadcast with some chunks", () => {
beforeEach(() => { beforeEach(() => {
const relations = new Relations(RelationType.Reference, EventType.RoomMessage, client); infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Running);
jest.spyOn(relations, "getRelations").mockReturnValue([chunk0Event]); playback = mkPlayback();
mocked(getReferenceRelationsForEvent).mockReturnValue(relations); setUpChunkEvents([chunk2Event, chunk0Event, chunk1Event]);
}); });
describe("when calling start", () => { describe("and calling start", () => {
beforeEach(async () => {
await playback.start();
});
it("should play the last chunk", () => {
// assert that the first chunk is being played
expect(chunk2Playback.play).toHaveBeenCalled();
expect(chunk1Playback.play).not.toHaveBeenCalled();
});
});
});
describe("when there is a stopped voice broadcast", () => {
beforeEach(() => {
infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Stopped);
playback = mkPlayback();
});
describe("and there is only a 0 sequence event", () => {
beforeEach(() => {
setUpChunkEvents([chunk0Event]);
});
describe("and calling start", () => {
beforeEach(async () => { beforeEach(async () => {
await playback.start(); await playback.start();
}); });
@ -162,11 +202,9 @@ describe("VoiceBroadcastPlayback", () => {
}); });
}); });
describe("when there are some chunks", () => { describe("and there are some chunks", () => {
beforeEach(() => { beforeEach(() => {
const relations = new Relations(RelationType.Reference, EventType.RoomMessage, client); setUpChunkEvents([chunk2Event, chunk0Event, chunk1Event]);
jest.spyOn(relations, "getRelations").mockReturnValue([chunk2Event, chunk1Event]);
mocked(getReferenceRelationsForEvent).mockReturnValue(relations);
}); });
it("should expose the info event", () => { it("should expose the info event", () => {
@ -177,14 +215,14 @@ describe("VoiceBroadcastPlayback", () => {
expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Stopped); expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Stopped);
}); });
describe("when calling start", () => { describe("and calling start", () => {
beforeEach(async () => { beforeEach(async () => {
await playback.start(); await playback.start();
}); });
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Playing); itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Playing);
it("should play the chunks", () => { it("should play the chunks beginning with the first one", () => {
// assert that the first chunk is being played // assert that the first chunk is being played
expect(chunk1Playback.play).toHaveBeenCalled(); expect(chunk1Playback.play).toHaveBeenCalled();
expect(chunk2Playback.play).not.toHaveBeenCalled(); expect(chunk2Playback.play).not.toHaveBeenCalled();
@ -212,7 +250,7 @@ describe("VoiceBroadcastPlayback", () => {
}); });
}); });
describe("when calling toggle for the first time", () => { describe("and calling toggle for the first time", () => {
beforeEach(async () => { beforeEach(async () => {
await playback.toggle(); await playback.toggle();
}); });
@ -236,7 +274,7 @@ describe("VoiceBroadcastPlayback", () => {
}); });
}); });
describe("when calling stop", () => { describe("and calling stop", () => {
beforeEach(() => { beforeEach(() => {
playback.stop(); playback.stop();
}); });
@ -254,7 +292,7 @@ describe("VoiceBroadcastPlayback", () => {
}); });
}); });
describe("when calling destroy", () => { describe("and calling destroy", () => {
beforeEach(() => { beforeEach(() => {
playback.destroy(); playback.destroy();
}); });
@ -264,4 +302,5 @@ describe("VoiceBroadcastPlayback", () => {
}); });
}); });
}); });
});
}); });