@microsoft/live-share-media 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,340 +1,340 @@
1
- /**
2
- * Copyright (c) Microsoft Corporation. All rights reserved.
3
- * Licensed under the Microsoft Live Share SDK License.
4
- */
5
-
6
- import 'mocha';
7
- import { strict as assert } from 'assert';
8
- import { IParticipant, ParticipantRole, SimulatedBroadcastHub, SimulatedCollaborationSpace } from '@microsoft/teams-collaboration';
9
- import { GroupPlaybackTrack } from './GroupPlaybackTrack';
10
- import { GroupTransportState, ITransportState } from './GroupTransportState';
11
- import { GroupPlaybackPosition, ICurrentPlaybackPosition } from './GroupPlaybackPosition';
12
- import { CoordinationWaitPoint, ExtendedMediaMetadata, ExtendedMediaSessionPlaybackState } from '../MediaSessionExtensions';
13
- import { TimeInterval } from '../internals';
14
-
15
- function createTransportUpdate(space: SimulatedCollaborationSpace, playbackState: ExtendedMediaSessionPlaybackState, startPosition: number): ITransportState {
16
- return {
17
- playbackState: playbackState,
18
- startPosition: startPosition,
19
- timestamp: space.clock.getTime(),
20
- socketId: space.socket.id
21
- };
22
- }
23
-
24
- function createPositionUpdate(space: SimulatedCollaborationSpace, playbackState: ExtendedMediaSessionPlaybackState, position: number, waitPoint?: CoordinationWaitPoint, duration?: number): ICurrentPlaybackPosition {
25
- return {
26
- playbackState: playbackState,
27
- waitPoint: waitPoint,
28
- position: position,
29
- duration: duration,
30
- timestamp: space.clock.getTime(),
31
- socketId: space.socket.id
32
- };
33
- }
34
-
35
- function subtractSeconds<T extends { timestamp: number; }>(seconds: number, update: T): T {
36
- update.timestamp -= (seconds * 1000);
37
- return update;
38
- }
39
-
40
- function addSeconds<T extends { timestamp: number; }>(seconds: number, update: T): T {
41
- update.timestamp += (seconds * 1000);
42
- return update;
43
- }
44
-
45
- describe('GroupPlaybackPosition', () => {
46
- const hub = new SimulatedBroadcastHub();
47
-
48
- const user1: IParticipant = { participantId: 'user1', role: ParticipantRole.organizer };
49
- const space1 = new SimulatedCollaborationSpace(user1, hub);
50
- const user2: IParticipant = { participantId: 'user2', role: ParticipantRole.participant };
51
- const space2 = new SimulatedCollaborationSpace(user2, hub);
52
-
53
- const track1 = { trackIdentifier: 'track1', title: 'Test Track 1' } as ExtendedMediaMetadata;
54
- const track2 = { trackIdentifier: 'track2', title: 'Test Track 2' } as ExtendedMediaMetadata;
55
-
56
- const updateInterval = new TimeInterval(10);
57
-
58
- it('should start with 0 clients', async () => {
59
- const playbackTrack = new GroupPlaybackTrack(() => track1);
60
- const transportState = new GroupTransportState(playbackTrack);
61
- const playbackPosition = new GroupPlaybackPosition(transportState, space1, updateInterval);
62
-
63
- await space1.join();
64
- try {
65
- assert(playbackPosition.totalClients == 0, `wrong client count`);
66
- assert(playbackPosition.localPosition == undefined, `wrong position`);
67
- } finally {
68
- await space1.leave();
69
- }
70
- });
71
-
72
- it('should find local position', async () => {
73
- const playbackTrack = new GroupPlaybackTrack(() => track1);
74
- const transportState = new GroupTransportState(playbackTrack);
75
- const playbackPosition = new GroupPlaybackPosition(transportState, space1, updateInterval);
76
-
77
- await space1.join();
78
- try {
79
- await playbackPosition.UpdatePlaybackPosition(createPositionUpdate(space1, 'none', 0.0));
80
-
81
- assert(playbackPosition.totalClients == 1, `wrong client count`);
82
- assert(playbackPosition.localPosition != undefined, `local position not found`);
83
- } finally {
84
- await space1.leave();
85
- }
86
- });
87
-
88
- it('should update local position', async () => {
89
- const playbackTrack = new GroupPlaybackTrack(() => track1);
90
- const transportState = new GroupTransportState(playbackTrack);
91
- const playbackPosition = new GroupPlaybackPosition(transportState, space1, updateInterval);
92
-
93
- await space1.join();
94
- try {
95
- const position = createPositionUpdate(space1, 'none', 0.0);
96
- await playbackPosition.UpdatePlaybackPosition(position);
97
-
98
- const newPosition = addSeconds(1.0, createPositionUpdate(space1, 'none', 0.0));
99
- await playbackPosition.UpdatePlaybackPosition(newPosition);
100
-
101
- assert(playbackPosition.totalClients == 1, `wrong client count`);
102
- assert(playbackPosition.localPosition != undefined, `local position not found`);
103
- assert(playbackPosition.localPosition.timestamp == newPosition.timestamp, `position not updated`);
104
- } finally {
105
- await space1.leave();
106
- }
107
- });
108
-
109
- it('should ignore older position updates', async () => {
110
- const playbackTrack = new GroupPlaybackTrack(() => track1);
111
- const transportState = new GroupTransportState(playbackTrack);
112
- const playbackPosition = new GroupPlaybackPosition(transportState, space1, updateInterval);
113
-
114
- await space1.join();
115
- try {
116
- const position = createPositionUpdate(space1, 'none', 0.0);
117
- await playbackPosition.UpdatePlaybackPosition(position);
118
-
119
- const newPosition = subtractSeconds(1.0, createPositionUpdate(space1, 'none', 0.0));
120
- await playbackPosition.UpdatePlaybackPosition(newPosition);
121
-
122
- assert(playbackPosition.totalClients == 1, `wrong client count`);
123
- assert(playbackPosition.localPosition != undefined, `local position not found`);
124
- assert(playbackPosition.localPosition.timestamp == position.timestamp, `position not updated`);
125
- } finally {
126
- await space1.leave();
127
- }
128
- });
129
-
130
- it('should track other client positions', async () => {
131
- const playbackTrack = new GroupPlaybackTrack(() => track1);
132
- const transportState = new GroupTransportState(playbackTrack);
133
- const playbackPosition = new GroupPlaybackPosition(transportState, space1, updateInterval);
134
-
135
- await space1.join();
136
- await space2.join();
137
- try {
138
- const position1 = createPositionUpdate(space1, 'none', 0.0);
139
- await playbackPosition.UpdatePlaybackPosition(position1);
140
-
141
- const position2 = addSeconds(1.0, createPositionUpdate(space2, 'none', 0.0));
142
- await playbackPosition.UpdatePlaybackPosition(position2);
143
-
144
- assert(playbackPosition.totalClients == 2, `wrong client count`);
145
- assert(playbackPosition.localPosition != undefined, `local position not found`);
146
- assert(playbackPosition.localPosition.timestamp == position1.timestamp, `position not updated`);
147
- } finally {
148
- await space1.leave();
149
- await space2.leave();
150
- }
151
- });
152
-
153
- it('should enumerate client positions', async () => {
154
- const playbackTrack = new GroupPlaybackTrack(() => track1);
155
- const transportState = new GroupTransportState(playbackTrack);
156
- const playbackPosition = new GroupPlaybackPosition(transportState, space1, updateInterval);
157
-
158
- await space1.join();
159
- await space2.join();
160
- try {
161
- await playbackPosition.UpdatePlaybackPosition(createPositionUpdate(space1, 'none', 0.0));
162
- await playbackPosition.UpdatePlaybackPosition(subtractSeconds(updateInterval.seconds, createPositionUpdate(space2, 'none', 0.0)));
163
-
164
- let cnt = 0;
165
- playbackPosition.forEach((position, projectedPosition) => {
166
- assert(position.position == 0.0, `wrong position ${position.position}`);
167
- assert(projectedPosition == 0.0, `wrong projected position ${projectedPosition}`);
168
- cnt++;
169
- });
170
-
171
- assert(cnt == 2, `only enumerated ${cnt} positions`);
172
- } finally {
173
- await space1.leave();
174
- await space2.leave();
175
- }
176
- });
177
-
178
- it('should ignore stale positions', async () => {
179
- const playbackTrack = new GroupPlaybackTrack(() => track1);
180
- const transportState = new GroupTransportState(playbackTrack);
181
- const playbackPosition = new GroupPlaybackPosition(transportState, space1, updateInterval);
182
-
183
- await space1.join();
184
- await space2.join();
185
- try {
186
- await playbackPosition.UpdatePlaybackPosition(createPositionUpdate(space1, 'none', 0.0));
187
- await playbackPosition.UpdatePlaybackPosition(subtractSeconds(updateInterval.seconds * 3, createPositionUpdate(space2, 'none', 0.0)));
188
-
189
- let cnt = 0;
190
- playbackPosition.forEach((position, projectedPosition) => {
191
- assert(position.position == 0.0, `wrong position ${position.position}`);
192
- assert(projectedPosition == 0.0, `wrong projected position ${projectedPosition}`);
193
- cnt++;
194
- });
195
-
196
- assert(cnt == 1, `enumerated ${cnt} positions`);
197
- } finally {
198
- await space1.leave();
199
- await space2.leave();
200
- }
201
- });
202
-
203
- it('should project position when playing', async () => {
204
- const playbackTrack = new GroupPlaybackTrack(() => track1);
205
- const transportState = new GroupTransportState(playbackTrack);
206
- const playbackPosition = new GroupPlaybackPosition(transportState, space1, new TimeInterval(1000));
207
-
208
- await space1.join();
209
- try {
210
- await playbackPosition.UpdatePlaybackPosition(subtractSeconds(1.0, createPositionUpdate(space1, 'playing', 0.0)));
211
-
212
- let cnt = 0;
213
- playbackPosition.forEach((position, projectedPosition) => {
214
- assert(position.position == 0.0, `wrong position ${position.position}`);
215
- assert(projectedPosition >= 1.0, `wrong projected position ${projectedPosition}`);
216
- cnt++;
217
- });
218
-
219
- assert(cnt == 1, `enumerated ${cnt} positions`);
220
- } finally {
221
- await space1.leave();
222
- }
223
- });
224
-
225
- it('should compute max playback position relative to transport state', async () => {
226
- const playbackTrack = new GroupPlaybackTrack(() => track1);
227
- const transportState = new GroupTransportState(playbackTrack);
228
- const playbackPosition = new GroupPlaybackPosition(transportState, space1, new TimeInterval(1000));
229
-
230
- await space1.join();
231
- try {
232
- assert(playbackPosition.maxPosition == 0.0, `wrong starting position of ${playbackPosition.maxPosition}`);
233
-
234
- await transportState.updateState(subtractSeconds(2.0, createTransportUpdate(space1, 'playing', 0.0)));
235
-
236
- assert(playbackPosition.maxPosition >= 2.0, `wrong projected position of ${playbackPosition.maxPosition}`);
237
- } finally {
238
- await space1.leave();
239
- }
240
- });
241
-
242
- it('should compute target position relative to other client positions', async () => {
243
- const playbackTrack = new GroupPlaybackTrack(() => track1);
244
- const transportState = new GroupTransportState(playbackTrack);
245
- const playbackPosition = new GroupPlaybackPosition(transportState, space1, new TimeInterval(1000));
246
-
247
- await space1.join();
248
- await space2.join();
249
- try {
250
- assert(playbackPosition.targetPosition == 0.0, `wrong starting position of ${playbackPosition.targetPosition}`);
251
-
252
- await transportState.updateState(subtractSeconds(2.0, createTransportUpdate(space1, 'playing', 0.0)));
253
- await playbackPosition.UpdatePlaybackPosition(createPositionUpdate(space1, 'playing', 0.0));
254
- await playbackPosition.UpdatePlaybackPosition(subtractSeconds(1.0, createPositionUpdate(space2, 'playing', 0.0)));
255
-
256
- // We're sometimes getting back a target position of 0.999 instead of 1.0 (some sort of rounding error)
257
- assert(playbackPosition.targetPosition > 0.9 && playbackPosition.targetPosition < 2.0, `wrong target position of ${playbackPosition.targetPosition}`);
258
- } finally {
259
- await space1.leave();
260
- await space2.leave();
261
- }
262
- });
263
-
264
- it('should limit max and target position by media duration', async () => {
265
- const playbackTrack = new GroupPlaybackTrack(() => track1);
266
- const transportState = new GroupTransportState(playbackTrack);
267
- const playbackPosition = new GroupPlaybackPosition(transportState, space1, new TimeInterval(1000));
268
-
269
- await space1.join();
270
- await space2.join();
271
- try {
272
- await transportState.updateState(subtractSeconds(2.0, createTransportUpdate(space1, 'playing', 0.0)));
273
- await playbackPosition.UpdatePlaybackPosition(createPositionUpdate(space1, 'playing', 0.0, undefined, 0.5));
274
- await playbackPosition.UpdatePlaybackPosition(subtractSeconds(1.0, createPositionUpdate(space2, 'playing', 0.0, undefined, 0.5)));
275
-
276
- assert(playbackPosition.maxPosition == 0.5, `wrong max position ${playbackPosition.maxPosition}`);
277
- assert(playbackPosition.targetPosition == 0.5, `wrong target position ${playbackPosition.targetPosition}`);
278
- } finally {
279
- await space1.leave();
280
- await space2.leave();
281
- }
282
- });
283
-
284
- it('should count number of waiting clients', async () => {
285
- const playbackTrack = new GroupPlaybackTrack(() => track1);
286
- const transportState = new GroupTransportState(playbackTrack);
287
- const playbackPosition = new GroupPlaybackPosition(transportState, space1, new TimeInterval(1000));
288
-
289
- await space1.join();
290
- await space2.join();
291
- try {
292
- await transportState.updateState(subtractSeconds(2.0, createTransportUpdate(space1, 'playing', 0.0)));
293
- await playbackPosition.UpdatePlaybackPosition(createPositionUpdate(space1, 'suspended', 2.0, { position: 2.0 }));
294
- await playbackPosition.UpdatePlaybackPosition(subtractSeconds(1.0, createPositionUpdate(space2, 'playing', 0.0, undefined)));
295
-
296
- assert(playbackPosition.clientsWaiting == 2, `wrong count ${playbackPosition.clientsWaiting}`);
297
- } finally {
298
- await space1.leave();
299
- await space2.leave();
300
- }
301
- });
302
-
303
- it('should drop waiting count after suspension ends', async () => {
304
- const playbackTrack = new GroupPlaybackTrack(() => track1);
305
- const transportState = new GroupTransportState(playbackTrack);
306
- const playbackPosition = new GroupPlaybackPosition(transportState, space1, new TimeInterval(1000));
307
-
308
- await space1.join();
309
- await space2.join();
310
- try {
311
- await transportState.updateState(subtractSeconds(2.0, createTransportUpdate(space1, 'playing', 0.0)));
312
- await playbackPosition.UpdatePlaybackPosition(createPositionUpdate(space1, 'waiting', 2.0, { position: 2.0 }));
313
- await playbackPosition.UpdatePlaybackPosition(createPositionUpdate(space2, 'suspended', 2.0, { position: 2.0 }));
314
-
315
- assert(playbackPosition.clientsWaiting == 1, `wrong count ${playbackPosition.clientsWaiting}`);
316
- } finally {
317
- await space1.leave();
318
- await space2.leave();
319
- }
320
- });
321
-
322
- it('should drop waiting count to 0 after all clients reach wait point', async () => {
323
- const playbackTrack = new GroupPlaybackTrack(() => track1);
324
- const transportState = new GroupTransportState(playbackTrack);
325
- const playbackPosition = new GroupPlaybackPosition(transportState, space1, new TimeInterval(1000));
326
-
327
- await space1.join();
328
- await space2.join();
329
- try {
330
- await transportState.updateState(subtractSeconds(2.0, createTransportUpdate(space1, 'playing', 0.0)));
331
- await playbackPosition.UpdatePlaybackPosition(createPositionUpdate(space1, 'waiting', 2.0, { position: 2.0 }));
332
- await playbackPosition.UpdatePlaybackPosition(createPositionUpdate(space2, 'waiting', 2.0, { position: 2.0 }));
333
-
334
- assert(playbackPosition.clientsWaiting == 0, `wrong count ${playbackPosition.clientsWaiting}`);
335
- } finally {
336
- await space1.leave();
337
- await space2.leave();
338
- }
339
- });
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the Microsoft Live Share SDK License.
4
+ */
5
+
6
+ import 'mocha';
7
+ import { strict as assert } from 'assert';
8
+ import { IParticipant, ParticipantRole, SimulatedBroadcastHub, SimulatedCollaborationSpace } from '@microsoft/teams-collaboration';
9
+ import { GroupPlaybackTrack } from './GroupPlaybackTrack';
10
+ import { GroupTransportState, ITransportState } from './GroupTransportState';
11
+ import { GroupPlaybackPosition, ICurrentPlaybackPosition } from './GroupPlaybackPosition';
12
+ import { CoordinationWaitPoint, ExtendedMediaMetadata, ExtendedMediaSessionPlaybackState } from '../MediaSessionExtensions';
13
+ import { TimeInterval } from '../internals';
14
+
15
+ function createTransportUpdate(space: SimulatedCollaborationSpace, playbackState: ExtendedMediaSessionPlaybackState, startPosition: number): ITransportState {
16
+ return {
17
+ playbackState: playbackState,
18
+ startPosition: startPosition,
19
+ timestamp: space.clock.getTime(),
20
+ socketId: space.socket.id
21
+ };
22
+ }
23
+
24
+ function createPositionUpdate(space: SimulatedCollaborationSpace, playbackState: ExtendedMediaSessionPlaybackState, position: number, waitPoint?: CoordinationWaitPoint, duration?: number): ICurrentPlaybackPosition {
25
+ return {
26
+ playbackState: playbackState,
27
+ waitPoint: waitPoint,
28
+ position: position,
29
+ duration: duration,
30
+ timestamp: space.clock.getTime(),
31
+ socketId: space.socket.id
32
+ };
33
+ }
34
+
35
+ function subtractSeconds<T extends { timestamp: number; }>(seconds: number, update: T): T {
36
+ update.timestamp -= (seconds * 1000);
37
+ return update;
38
+ }
39
+
40
+ function addSeconds<T extends { timestamp: number; }>(seconds: number, update: T): T {
41
+ update.timestamp += (seconds * 1000);
42
+ return update;
43
+ }
44
+
45
+ describe('GroupPlaybackPosition', () => {
46
+ const hub = new SimulatedBroadcastHub();
47
+
48
+ const user1: IParticipant = { participantId: 'user1', role: ParticipantRole.organizer };
49
+ const space1 = new SimulatedCollaborationSpace(user1, hub);
50
+ const user2: IParticipant = { participantId: 'user2', role: ParticipantRole.participant };
51
+ const space2 = new SimulatedCollaborationSpace(user2, hub);
52
+
53
+ const track1 = { trackIdentifier: 'track1', title: 'Test Track 1' } as ExtendedMediaMetadata;
54
+ const track2 = { trackIdentifier: 'track2', title: 'Test Track 2' } as ExtendedMediaMetadata;
55
+
56
+ const updateInterval = new TimeInterval(10);
57
+
58
+ it('should start with 0 clients', async () => {
59
+ const playbackTrack = new GroupPlaybackTrack(() => track1);
60
+ const transportState = new GroupTransportState(playbackTrack);
61
+ const playbackPosition = new GroupPlaybackPosition(transportState, space1, updateInterval);
62
+
63
+ await space1.join();
64
+ try {
65
+ assert(playbackPosition.totalClients == 0, `wrong client count`);
66
+ assert(playbackPosition.localPosition == undefined, `wrong position`);
67
+ } finally {
68
+ await space1.leave();
69
+ }
70
+ });
71
+
72
+ it('should find local position', async () => {
73
+ const playbackTrack = new GroupPlaybackTrack(() => track1);
74
+ const transportState = new GroupTransportState(playbackTrack);
75
+ const playbackPosition = new GroupPlaybackPosition(transportState, space1, updateInterval);
76
+
77
+ await space1.join();
78
+ try {
79
+ await playbackPosition.UpdatePlaybackPosition(createPositionUpdate(space1, 'none', 0.0));
80
+
81
+ assert(playbackPosition.totalClients == 1, `wrong client count`);
82
+ assert(playbackPosition.localPosition != undefined, `local position not found`);
83
+ } finally {
84
+ await space1.leave();
85
+ }
86
+ });
87
+
88
+ it('should update local position', async () => {
89
+ const playbackTrack = new GroupPlaybackTrack(() => track1);
90
+ const transportState = new GroupTransportState(playbackTrack);
91
+ const playbackPosition = new GroupPlaybackPosition(transportState, space1, updateInterval);
92
+
93
+ await space1.join();
94
+ try {
95
+ const position = createPositionUpdate(space1, 'none', 0.0);
96
+ await playbackPosition.UpdatePlaybackPosition(position);
97
+
98
+ const newPosition = addSeconds(1.0, createPositionUpdate(space1, 'none', 0.0));
99
+ await playbackPosition.UpdatePlaybackPosition(newPosition);
100
+
101
+ assert(playbackPosition.totalClients == 1, `wrong client count`);
102
+ assert(playbackPosition.localPosition != undefined, `local position not found`);
103
+ assert(playbackPosition.localPosition.timestamp == newPosition.timestamp, `position not updated`);
104
+ } finally {
105
+ await space1.leave();
106
+ }
107
+ });
108
+
109
+ it('should ignore older position updates', async () => {
110
+ const playbackTrack = new GroupPlaybackTrack(() => track1);
111
+ const transportState = new GroupTransportState(playbackTrack);
112
+ const playbackPosition = new GroupPlaybackPosition(transportState, space1, updateInterval);
113
+
114
+ await space1.join();
115
+ try {
116
+ const position = createPositionUpdate(space1, 'none', 0.0);
117
+ await playbackPosition.UpdatePlaybackPosition(position);
118
+
119
+ const newPosition = subtractSeconds(1.0, createPositionUpdate(space1, 'none', 0.0));
120
+ await playbackPosition.UpdatePlaybackPosition(newPosition);
121
+
122
+ assert(playbackPosition.totalClients == 1, `wrong client count`);
123
+ assert(playbackPosition.localPosition != undefined, `local position not found`);
124
+ assert(playbackPosition.localPosition.timestamp == position.timestamp, `position not updated`);
125
+ } finally {
126
+ await space1.leave();
127
+ }
128
+ });
129
+
130
+ it('should track other client positions', async () => {
131
+ const playbackTrack = new GroupPlaybackTrack(() => track1);
132
+ const transportState = new GroupTransportState(playbackTrack);
133
+ const playbackPosition = new GroupPlaybackPosition(transportState, space1, updateInterval);
134
+
135
+ await space1.join();
136
+ await space2.join();
137
+ try {
138
+ const position1 = createPositionUpdate(space1, 'none', 0.0);
139
+ await playbackPosition.UpdatePlaybackPosition(position1);
140
+
141
+ const position2 = addSeconds(1.0, createPositionUpdate(space2, 'none', 0.0));
142
+ await playbackPosition.UpdatePlaybackPosition(position2);
143
+
144
+ assert(playbackPosition.totalClients == 2, `wrong client count`);
145
+ assert(playbackPosition.localPosition != undefined, `local position not found`);
146
+ assert(playbackPosition.localPosition.timestamp == position1.timestamp, `position not updated`);
147
+ } finally {
148
+ await space1.leave();
149
+ await space2.leave();
150
+ }
151
+ });
152
+
153
+ it('should enumerate client positions', async () => {
154
+ const playbackTrack = new GroupPlaybackTrack(() => track1);
155
+ const transportState = new GroupTransportState(playbackTrack);
156
+ const playbackPosition = new GroupPlaybackPosition(transportState, space1, updateInterval);
157
+
158
+ await space1.join();
159
+ await space2.join();
160
+ try {
161
+ await playbackPosition.UpdatePlaybackPosition(createPositionUpdate(space1, 'none', 0.0));
162
+ await playbackPosition.UpdatePlaybackPosition(subtractSeconds(updateInterval.seconds, createPositionUpdate(space2, 'none', 0.0)));
163
+
164
+ let cnt = 0;
165
+ playbackPosition.forEach((position, projectedPosition) => {
166
+ assert(position.position == 0.0, `wrong position ${position.position}`);
167
+ assert(projectedPosition == 0.0, `wrong projected position ${projectedPosition}`);
168
+ cnt++;
169
+ });
170
+
171
+ assert(cnt == 2, `only enumerated ${cnt} positions`);
172
+ } finally {
173
+ await space1.leave();
174
+ await space2.leave();
175
+ }
176
+ });
177
+
178
+ it('should ignore stale positions', async () => {
179
+ const playbackTrack = new GroupPlaybackTrack(() => track1);
180
+ const transportState = new GroupTransportState(playbackTrack);
181
+ const playbackPosition = new GroupPlaybackPosition(transportState, space1, updateInterval);
182
+
183
+ await space1.join();
184
+ await space2.join();
185
+ try {
186
+ await playbackPosition.UpdatePlaybackPosition(createPositionUpdate(space1, 'none', 0.0));
187
+ await playbackPosition.UpdatePlaybackPosition(subtractSeconds(updateInterval.seconds * 3, createPositionUpdate(space2, 'none', 0.0)));
188
+
189
+ let cnt = 0;
190
+ playbackPosition.forEach((position, projectedPosition) => {
191
+ assert(position.position == 0.0, `wrong position ${position.position}`);
192
+ assert(projectedPosition == 0.0, `wrong projected position ${projectedPosition}`);
193
+ cnt++;
194
+ });
195
+
196
+ assert(cnt == 1, `enumerated ${cnt} positions`);
197
+ } finally {
198
+ await space1.leave();
199
+ await space2.leave();
200
+ }
201
+ });
202
+
203
+ it('should project position when playing', async () => {
204
+ const playbackTrack = new GroupPlaybackTrack(() => track1);
205
+ const transportState = new GroupTransportState(playbackTrack);
206
+ const playbackPosition = new GroupPlaybackPosition(transportState, space1, new TimeInterval(1000));
207
+
208
+ await space1.join();
209
+ try {
210
+ await playbackPosition.UpdatePlaybackPosition(subtractSeconds(1.0, createPositionUpdate(space1, 'playing', 0.0)));
211
+
212
+ let cnt = 0;
213
+ playbackPosition.forEach((position, projectedPosition) => {
214
+ assert(position.position == 0.0, `wrong position ${position.position}`);
215
+ assert(projectedPosition >= 1.0, `wrong projected position ${projectedPosition}`);
216
+ cnt++;
217
+ });
218
+
219
+ assert(cnt == 1, `enumerated ${cnt} positions`);
220
+ } finally {
221
+ await space1.leave();
222
+ }
223
+ });
224
+
225
+ it('should compute max playback position relative to transport state', async () => {
226
+ const playbackTrack = new GroupPlaybackTrack(() => track1);
227
+ const transportState = new GroupTransportState(playbackTrack);
228
+ const playbackPosition = new GroupPlaybackPosition(transportState, space1, new TimeInterval(1000));
229
+
230
+ await space1.join();
231
+ try {
232
+ assert(playbackPosition.maxPosition == 0.0, `wrong starting position of ${playbackPosition.maxPosition}`);
233
+
234
+ await transportState.updateState(subtractSeconds(2.0, createTransportUpdate(space1, 'playing', 0.0)));
235
+
236
+ assert(playbackPosition.maxPosition >= 2.0, `wrong projected position of ${playbackPosition.maxPosition}`);
237
+ } finally {
238
+ await space1.leave();
239
+ }
240
+ });
241
+
242
+ it('should compute target position relative to other client positions', async () => {
243
+ const playbackTrack = new GroupPlaybackTrack(() => track1);
244
+ const transportState = new GroupTransportState(playbackTrack);
245
+ const playbackPosition = new GroupPlaybackPosition(transportState, space1, new TimeInterval(1000));
246
+
247
+ await space1.join();
248
+ await space2.join();
249
+ try {
250
+ assert(playbackPosition.targetPosition == 0.0, `wrong starting position of ${playbackPosition.targetPosition}`);
251
+
252
+ await transportState.updateState(subtractSeconds(2.0, createTransportUpdate(space1, 'playing', 0.0)));
253
+ await playbackPosition.UpdatePlaybackPosition(createPositionUpdate(space1, 'playing', 0.0));
254
+ await playbackPosition.UpdatePlaybackPosition(subtractSeconds(1.0, createPositionUpdate(space2, 'playing', 0.0)));
255
+
256
+ // We're sometimes getting back a target position of 0.999 instead of 1.0 (some sort of rounding error)
257
+ assert(playbackPosition.targetPosition > 0.9 && playbackPosition.targetPosition < 2.0, `wrong target position of ${playbackPosition.targetPosition}`);
258
+ } finally {
259
+ await space1.leave();
260
+ await space2.leave();
261
+ }
262
+ });
263
+
264
+ it('should limit max and target position by media duration', async () => {
265
+ const playbackTrack = new GroupPlaybackTrack(() => track1);
266
+ const transportState = new GroupTransportState(playbackTrack);
267
+ const playbackPosition = new GroupPlaybackPosition(transportState, space1, new TimeInterval(1000));
268
+
269
+ await space1.join();
270
+ await space2.join();
271
+ try {
272
+ await transportState.updateState(subtractSeconds(2.0, createTransportUpdate(space1, 'playing', 0.0)));
273
+ await playbackPosition.UpdatePlaybackPosition(createPositionUpdate(space1, 'playing', 0.0, undefined, 0.5));
274
+ await playbackPosition.UpdatePlaybackPosition(subtractSeconds(1.0, createPositionUpdate(space2, 'playing', 0.0, undefined, 0.5)));
275
+
276
+ assert(playbackPosition.maxPosition == 0.5, `wrong max position ${playbackPosition.maxPosition}`);
277
+ assert(playbackPosition.targetPosition == 0.5, `wrong target position ${playbackPosition.targetPosition}`);
278
+ } finally {
279
+ await space1.leave();
280
+ await space2.leave();
281
+ }
282
+ });
283
+
284
+ it('should count number of waiting clients', async () => {
285
+ const playbackTrack = new GroupPlaybackTrack(() => track1);
286
+ const transportState = new GroupTransportState(playbackTrack);
287
+ const playbackPosition = new GroupPlaybackPosition(transportState, space1, new TimeInterval(1000));
288
+
289
+ await space1.join();
290
+ await space2.join();
291
+ try {
292
+ await transportState.updateState(subtractSeconds(2.0, createTransportUpdate(space1, 'playing', 0.0)));
293
+ await playbackPosition.UpdatePlaybackPosition(createPositionUpdate(space1, 'suspended', 2.0, { position: 2.0 }));
294
+ await playbackPosition.UpdatePlaybackPosition(subtractSeconds(1.0, createPositionUpdate(space2, 'playing', 0.0, undefined)));
295
+
296
+ assert(playbackPosition.clientsWaiting == 2, `wrong count ${playbackPosition.clientsWaiting}`);
297
+ } finally {
298
+ await space1.leave();
299
+ await space2.leave();
300
+ }
301
+ });
302
+
303
+ it('should drop waiting count after suspension ends', async () => {
304
+ const playbackTrack = new GroupPlaybackTrack(() => track1);
305
+ const transportState = new GroupTransportState(playbackTrack);
306
+ const playbackPosition = new GroupPlaybackPosition(transportState, space1, new TimeInterval(1000));
307
+
308
+ await space1.join();
309
+ await space2.join();
310
+ try {
311
+ await transportState.updateState(subtractSeconds(2.0, createTransportUpdate(space1, 'playing', 0.0)));
312
+ await playbackPosition.UpdatePlaybackPosition(createPositionUpdate(space1, 'waiting', 2.0, { position: 2.0 }));
313
+ await playbackPosition.UpdatePlaybackPosition(createPositionUpdate(space2, 'suspended', 2.0, { position: 2.0 }));
314
+
315
+ assert(playbackPosition.clientsWaiting == 1, `wrong count ${playbackPosition.clientsWaiting}`);
316
+ } finally {
317
+ await space1.leave();
318
+ await space2.leave();
319
+ }
320
+ });
321
+
322
+ it('should drop waiting count to 0 after all clients reach wait point', async () => {
323
+ const playbackTrack = new GroupPlaybackTrack(() => track1);
324
+ const transportState = new GroupTransportState(playbackTrack);
325
+ const playbackPosition = new GroupPlaybackPosition(transportState, space1, new TimeInterval(1000));
326
+
327
+ await space1.join();
328
+ await space2.join();
329
+ try {
330
+ await transportState.updateState(subtractSeconds(2.0, createTransportUpdate(space1, 'playing', 0.0)));
331
+ await playbackPosition.UpdatePlaybackPosition(createPositionUpdate(space1, 'waiting', 2.0, { position: 2.0 }));
332
+ await playbackPosition.UpdatePlaybackPosition(createPositionUpdate(space2, 'waiting', 2.0, { position: 2.0 }));
333
+
334
+ assert(playbackPosition.clientsWaiting == 0, `wrong count ${playbackPosition.clientsWaiting}`);
335
+ } finally {
336
+ await space1.leave();
337
+ await space2.leave();
338
+ }
339
+ });
340
340
  });