@microsoft/live-share-media 1.3.1 → 2.0.0-internal.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.
Files changed (69) hide show
  1. package/bin/LiveMediaSession.d.ts.map +1 -1
  2. package/bin/LiveMediaSession.js +9 -1
  3. package/bin/LiveMediaSession.js.map +1 -1
  4. package/bin/LiveMediaSessionCoordinator.d.ts +15 -0
  5. package/bin/LiveMediaSessionCoordinator.d.ts.map +1 -1
  6. package/bin/LiveMediaSessionCoordinator.js +25 -0
  7. package/bin/LiveMediaSessionCoordinator.js.map +1 -1
  8. package/bin/MediaPlayerSynchronizer.d.ts +6 -4
  9. package/bin/MediaPlayerSynchronizer.d.ts.map +1 -1
  10. package/bin/MediaPlayerSynchronizer.js +43 -14
  11. package/bin/MediaPlayerSynchronizer.js.map +1 -1
  12. package/bin/MediaSessionExtensions.d.ts +17 -4
  13. package/bin/MediaSessionExtensions.d.ts.map +1 -1
  14. package/bin/MediaSessionExtensions.js +5 -1
  15. package/bin/MediaSessionExtensions.js.map +1 -1
  16. package/bin/RepeatedActionThrottler.js +1 -1
  17. package/bin/RepeatedActionThrottler.js.map +1 -1
  18. package/bin/VolumeManager.js +1 -1
  19. package/bin/VolumeManager.js.map +1 -1
  20. package/bin/internals/GroupCoordinatorState.d.ts +15 -1
  21. package/bin/internals/GroupCoordinatorState.d.ts.map +1 -1
  22. package/bin/internals/GroupCoordinatorState.js +124 -58
  23. package/bin/internals/GroupCoordinatorState.js.map +1 -1
  24. package/bin/internals/GroupPlaybackPosition.d.ts +3 -1
  25. package/bin/internals/GroupPlaybackPosition.d.ts.map +1 -1
  26. package/bin/internals/GroupPlaybackPosition.js +7 -3
  27. package/bin/internals/GroupPlaybackPosition.js.map +1 -1
  28. package/bin/internals/GroupPlaybackRate.d.ts +39 -0
  29. package/bin/internals/GroupPlaybackRate.d.ts.map +1 -0
  30. package/bin/internals/GroupPlaybackRate.js +62 -0
  31. package/bin/internals/GroupPlaybackRate.js.map +1 -0
  32. package/bin/internals/GroupPlaybackTrack.d.ts +2 -2
  33. package/bin/internals/GroupPlaybackTrack.d.ts.map +1 -1
  34. package/bin/internals/GroupPlaybackTrack.js +4 -3
  35. package/bin/internals/GroupPlaybackTrack.js.map +1 -1
  36. package/bin/internals/GroupPlaybackTrackData.d.ts +3 -3
  37. package/bin/internals/GroupPlaybackTrackData.d.ts.map +1 -1
  38. package/bin/internals/GroupPlaybackTrackData.js +13 -12
  39. package/bin/internals/GroupPlaybackTrackData.js.map +1 -1
  40. package/bin/internals/GroupTransportState.d.ts +6 -3
  41. package/bin/internals/GroupTransportState.d.ts.map +1 -1
  42. package/bin/internals/GroupTransportState.js +17 -12
  43. package/bin/internals/GroupTransportState.js.map +1 -1
  44. package/bin/internals/consts.d.ts +8 -1
  45. package/bin/internals/consts.d.ts.map +1 -1
  46. package/bin/internals/consts.js +8 -1
  47. package/bin/internals/consts.js.map +1 -1
  48. package/bin/internals/index.d.ts +1 -0
  49. package/bin/internals/index.d.ts.map +1 -1
  50. package/bin/internals/index.js +1 -0
  51. package/bin/internals/index.js.map +1 -1
  52. package/bin/internals/interfaces.d.ts +10 -0
  53. package/bin/internals/interfaces.d.ts.map +1 -0
  54. package/bin/internals/interfaces.js +3 -0
  55. package/bin/internals/interfaces.js.map +1 -0
  56. package/package.json +6 -9
  57. package/src/LiveMediaSession.ts +12 -0
  58. package/src/LiveMediaSessionCoordinator.ts +49 -0
  59. package/src/MediaPlayerSynchronizer.ts +55 -15
  60. package/src/MediaSessionExtensions.ts +18 -2
  61. package/src/internals/GroupCoordinatorState.ts +196 -79
  62. package/src/internals/GroupPlaybackPosition.ts +9 -2
  63. package/src/internals/GroupPlaybackRate.ts +90 -0
  64. package/src/internals/GroupPlaybackTrack.ts +5 -4
  65. package/src/internals/GroupPlaybackTrackData.ts +14 -13
  66. package/src/internals/GroupTransportState.ts +21 -12
  67. package/src/internals/consts.ts +14 -2
  68. package/src/internals/index.ts +1 -0
  69. package/src/internals/interfaces.ts +10 -0
@@ -23,8 +23,13 @@ import {
23
23
  GroupPlaybackTrack,
24
24
  GroupPlaybackTrackEvents,
25
25
  IPlaybackTrack,
26
+ IPlaybackTrackChangeEvent,
26
27
  } from "./GroupPlaybackTrack";
27
- import { GroupTransportState, ITransportState } from "./GroupTransportState";
28
+ import {
29
+ GroupTransportState,
30
+ ITransportState,
31
+ ITransportStateChangeEvent,
32
+ } from "./GroupTransportState";
28
33
  import {
29
34
  GroupPlaybackPosition,
30
35
  ICurrentPlaybackPosition,
@@ -35,9 +40,16 @@ import {
35
40
  GroupPlaybackTrackData,
36
41
  PlaybackTrackDataEvents,
37
42
  IPlaybackTrackData,
43
+ IPlaybackTrackDataChangeEvent,
38
44
  } from "./GroupPlaybackTrackData";
39
45
  import { TelemetryEvents } from "./consts";
40
46
  import { waitUntilConnected } from "@microsoft/live-share/bin/internals";
47
+ import {
48
+ GroupPlaybackRate,
49
+ IPlaybackRate,
50
+ IPlaybackRateChangeEvent,
51
+ PlaybackRateEvents,
52
+ } from "./GroupPlaybackRate";
41
53
 
42
54
  /**
43
55
  * @hidden
@@ -46,6 +58,7 @@ export interface IPositionUpdateEvent {
46
58
  track: IPlaybackTrack;
47
59
  trackData: IPlaybackTrackData;
48
60
  transport: ITransportState;
61
+ playbackRate: IPlaybackRate;
49
62
  playbackState: ExtendedMediaSessionPlaybackState;
50
63
  position: number;
51
64
  waitPoint?: CoordinationWaitPoint;
@@ -59,6 +72,13 @@ export interface ITransportCommandEvent {
59
72
  position: number;
60
73
  }
61
74
 
75
+ /**
76
+ * @hidden
77
+ */
78
+ export interface IRateChangeCommandEvent {
79
+ playbackRate: number;
80
+ }
81
+
62
82
  /**
63
83
  * @hidden
64
84
  */
@@ -87,6 +107,7 @@ export interface ITriggerActionEvent extends IEvent {
87
107
  export enum GroupCoordinatorStateEvents {
88
108
  newwaitpoint = "newwaitpoint",
89
109
  triggeraction = "triggeraction",
110
+ triggeractionignored = "triggeractionignored",
90
111
  }
91
112
 
92
113
  /**
@@ -104,6 +125,7 @@ export class GroupCoordinatorState extends EventEmitter {
104
125
  private _playbackTrackData: GroupPlaybackTrackData;
105
126
  private _transportState: GroupTransportState;
106
127
  private _playbackPosition: GroupPlaybackPosition;
128
+ private _playbackRate: GroupPlaybackRate;
107
129
 
108
130
  // Suspension tracking
109
131
  private _suspensionCnt: number = 0;
@@ -132,13 +154,16 @@ export class GroupCoordinatorState extends EventEmitter {
132
154
  this._playbackTrackData = new GroupPlaybackTrackData(
133
155
  this._playbackTrack
134
156
  );
157
+ this._playbackRate = new GroupPlaybackRate();
135
158
  this._transportState = new GroupTransportState(
136
159
  this._playbackTrack,
160
+ this._playbackRate,
137
161
  getMediaPlayerState,
138
162
  this._liveRuntime
139
163
  );
140
164
  this._playbackPosition = new GroupPlaybackPosition(
141
165
  this._transportState,
166
+ this._playbackRate,
142
167
  this._runtime,
143
168
  this._liveRuntime,
144
169
  positionUpdateInterval
@@ -146,104 +171,122 @@ export class GroupCoordinatorState extends EventEmitter {
146
171
  this._getMediaPlayerState = getMediaPlayerState;
147
172
 
148
173
  // Listen track related events
149
- this._playbackTrack.on(GroupPlaybackTrackEvents.trackChange, (evt) => {
150
- if (!this.isSuspended) {
151
- this._logger.sendTelemetryEvent(
152
- TelemetryEvents.GroupCoordinator.TrackChanged
153
- );
154
- this.emitSetTrack(evt.clientId, evt.metadata!, evt.source);
155
- } else {
156
- this._logger.sendTelemetryEvent(
157
- TelemetryEvents.GroupCoordinator.TrackChangeDelayed
158
- );
174
+ this._playbackTrack.on(
175
+ GroupPlaybackTrackEvents.trackChange,
176
+ (evt: IPlaybackTrackChangeEvent) => {
177
+ if (!this.isSuspended) {
178
+ this._logger.sendTelemetryEvent(
179
+ TelemetryEvents.GroupCoordinator.TrackChanged
180
+ );
181
+ this.emitSetTrack(evt.clientId, evt.metadata!, evt.source);
182
+ } else {
183
+ this._logger.sendTelemetryEvent(
184
+ TelemetryEvents.GroupCoordinator.TrackChangeDelayed
185
+ );
186
+ }
159
187
  }
160
- });
188
+ );
161
189
 
162
190
  // Listen for track data changes
163
191
  this._playbackTrackData.on(
164
192
  PlaybackTrackDataEvents.dataChange,
165
- async (evt) => {
193
+ async (evt: IPlaybackTrackDataChangeEvent) => {
166
194
  if (!this.isSuspended && !this.isWaiting) {
167
195
  this._logger.sendTelemetryEvent(
168
196
  TelemetryEvents.GroupCoordinator.TrackDataChanged
169
197
  );
170
- const clientId = await waitUntilConnected(this._runtime);
171
- this.emitTriggerAction({
172
- action: "datachange",
173
- source: evt.source,
174
- data: evt.data,
175
- clientId: evt.clientId,
176
- local: evt.clientId === clientId,
177
- });
178
198
  } else {
179
199
  this._logger.sendTelemetryEvent(
180
200
  TelemetryEvents.GroupCoordinator.TrackDataChangeDelayed
181
201
  );
182
202
  }
203
+ const clientId = await waitUntilConnected(this._runtime);
204
+ this.emitTriggerActionOrIgnored({
205
+ action: "datachange",
206
+ source: evt.source,
207
+ data: evt.data,
208
+ clientId: evt.clientId,
209
+ local: evt.clientId === clientId,
210
+ });
183
211
  }
184
212
  );
185
213
 
186
- // Listen to transport related events
187
- this._transportState.on(
188
- GroupTransportStateEvents.transportStateChange,
189
- async (evt) => {
214
+ this._playbackRate.on(
215
+ PlaybackRateEvents.rateChange,
216
+ async (evt: IPlaybackRateChangeEvent) => {
190
217
  if (!this.isSuspended && !this.isWaiting) {
191
218
  this._logger.sendTelemetryEvent(
192
- TelemetryEvents.GroupCoordinator.TransportStateChanged,
193
- null,
194
- {
195
- action: evt.action,
196
- seekTime: evt.seekTime,
197
- }
219
+ TelemetryEvents.GroupCoordinator.PlaybackRateChanged
198
220
  );
199
-
200
- const localClientId = await waitUntilConnected(
201
- this._runtime
202
- );
203
- const local = evt.clientId === localClientId;
204
- // Trigger action
205
- switch (evt.action) {
206
- case "play":
207
- this.emitTriggerAction({
208
- action: "play",
209
- source: evt.source,
210
- clientId: evt.clientId,
211
- local,
212
- seekTime: evt.seekTime,
213
- });
214
- break;
215
-
216
- case "pause":
217
- this.emitTriggerAction({
218
- action: "pause",
219
- source: evt.source,
220
- clientId: evt.clientId,
221
- local,
222
- seekTime: evt.seekTime,
223
- });
224
- break;
225
-
226
- case "seekto":
227
- this.emitTriggerAction({
228
- action: "seekto",
229
- source: evt.source,
230
- clientId: evt.clientId,
231
- local,
232
- seekTime: evt.seekTime,
233
- });
234
- break;
235
- }
236
221
  } else {
237
222
  this._logger.sendTelemetryEvent(
238
223
  TelemetryEvents.GroupCoordinator
239
- .TransportStateChangeDelayed,
240
- null,
241
- {
242
- action: evt.action,
243
- seekTime: evt.seekTime,
244
- }
224
+ .PlaybackRateChangedDelayed
245
225
  );
246
226
  }
227
+
228
+ const localClientId = await waitUntilConnected(this._runtime);
229
+ const local = evt.clientId === localClientId;
230
+ this.emitTriggerActionOrIgnored({
231
+ action: "ratechange",
232
+ source: evt.source,
233
+ clientId: evt.clientId,
234
+ local,
235
+ playbackRate: evt.playbackRate,
236
+ });
237
+ }
238
+ );
239
+
240
+ // Listen to transport related events
241
+ this._transportState.on(
242
+ GroupTransportStateEvents.transportStateChange,
243
+ async (evt: ITransportStateChangeEvent) => {
244
+ const tag =
245
+ this.isSuspended || this.isWaiting
246
+ ? TelemetryEvents.GroupCoordinator
247
+ .TransportStateChangeDelayed
248
+ : TelemetryEvents.GroupCoordinator
249
+ .TransportStateChanged;
250
+
251
+ this._logger.sendTelemetryEvent(tag, null, {
252
+ action: evt.action,
253
+ seekTime: evt.seekTime,
254
+ });
255
+
256
+ const localClientId = await waitUntilConnected(this._runtime);
257
+ const local = evt.clientId === localClientId;
258
+ // Trigger action
259
+ switch (evt.action) {
260
+ case "play":
261
+ this.emitTriggerActionOrIgnored({
262
+ action: "play",
263
+ source: evt.source,
264
+ clientId: evt.clientId,
265
+ local,
266
+ seekTime: evt.seekTime,
267
+ });
268
+ break;
269
+
270
+ case "pause":
271
+ this.emitTriggerActionOrIgnored({
272
+ action: "pause",
273
+ source: evt.source,
274
+ clientId: evt.clientId,
275
+ local,
276
+ seekTime: evt.seekTime,
277
+ });
278
+ break;
279
+
280
+ case "seekto":
281
+ this.emitTriggerActionOrIgnored({
282
+ action: "seekto",
283
+ source: evt.source,
284
+ clientId: evt.clientId,
285
+ local,
286
+ seekTime: evt.seekTime,
287
+ });
288
+ break;
289
+ }
247
290
  }
248
291
  );
249
292
  }
@@ -264,6 +307,10 @@ export class GroupCoordinatorState extends EventEmitter {
264
307
  return this._playbackPosition;
265
308
  }
266
309
 
310
+ public get playbackRate(): GroupPlaybackRate {
311
+ return this._playbackRate;
312
+ }
313
+
267
314
  public get waitingAt(): CoordinationWaitPoint | undefined {
268
315
  return this._waitPoint;
269
316
  }
@@ -312,6 +359,7 @@ export class GroupCoordinatorState extends EventEmitter {
312
359
  track: this.playbackTrack.current,
313
360
  trackData: this.playbackTrackData.current,
314
361
  transport: this.transportState.current,
362
+ playbackRate: this.playbackRate.current,
315
363
  playbackState: "suspended",
316
364
  position: positionState?.position || 0.0,
317
365
  waitPoint: this._waitPoint,
@@ -322,6 +370,7 @@ export class GroupCoordinatorState extends EventEmitter {
322
370
  track: this.playbackTrack.current,
323
371
  trackData: this.playbackTrackData.current,
324
372
  transport: this.transportState.current,
373
+ playbackRate: this.playbackRate.current,
325
374
  playbackState: this._waitPoint ? "waiting" : playbackState,
326
375
  position: positionState?.position || 0.0,
327
376
  waitPoint: this._waitPoint,
@@ -388,6 +437,32 @@ export class GroupCoordinatorState extends EventEmitter {
388
437
  }
389
438
  }
390
439
 
440
+ public handleRateChangeCommand(
441
+ event: ILiveEvent<IRateChangeCommandEvent>,
442
+ local: boolean
443
+ ): void {
444
+ const newState: IPlaybackRate = {
445
+ playbackRate: event.data.playbackRate,
446
+ clientId: event.clientId,
447
+ timestamp: event.timestamp,
448
+ };
449
+ const updated = this.playbackRate.updatePlaybackRate(newState, "user");
450
+
451
+ if (updated) {
452
+ const correlationId = LiveTelemetryLogger.formatCorrelationId(
453
+ event.clientId,
454
+ event.timestamp
455
+ );
456
+ this._logger.sendTelemetryEvent(
457
+ TelemetryEvents.SessionCoordinator.RemoteRateChangeReceived,
458
+ null,
459
+ {
460
+ correlationId: correlationId,
461
+ }
462
+ );
463
+ }
464
+ }
465
+
391
466
  public handleTransportCommand(
392
467
  event: ILiveEvent<ITransportCommandEvent>,
393
468
  local: boolean
@@ -579,7 +654,11 @@ export class GroupCoordinatorState extends EventEmitter {
579
654
  if (
580
655
  target >= 0.0 &&
581
656
  Math.abs(target - position) >=
582
- this._maxPlaybackDrift.seconds
657
+ Math.max(
658
+ this._maxPlaybackDrift.seconds,
659
+ this._maxPlaybackDrift.seconds *
660
+ this._playbackRate.rate
661
+ )
583
662
  ) {
584
663
  this._logger.sendTelemetryEvent(
585
664
  TelemetryEvents.GroupCoordinator.PositionOutOfSync,
@@ -589,7 +668,7 @@ export class GroupCoordinatorState extends EventEmitter {
589
668
  target: target,
590
669
  }
591
670
  );
592
- await this.emitTriggerAction({
671
+ this.emitTriggerAction({
593
672
  action: "catchup",
594
673
  source: "system",
595
674
  clientId: await waitUntilConnected(this._runtime),
@@ -613,7 +692,7 @@ export class GroupCoordinatorState extends EventEmitter {
613
692
  localClientId === this.transportState.current.clientId;
614
693
  switch (this.transportState.playbackState) {
615
694
  case "playing":
616
- await this.emitTriggerAction({
695
+ this.emitTriggerAction({
617
696
  action: "play",
618
697
  source: "system",
619
698
  clientId: this.transportState.current.clientId,
@@ -621,7 +700,7 @@ export class GroupCoordinatorState extends EventEmitter {
621
700
  });
622
701
  break;
623
702
  case "paused":
624
- await this.emitTriggerAction({
703
+ this.emitTriggerAction({
625
704
  action: "pause",
626
705
  source: "system",
627
706
  clientId: this.transportState.current.clientId,
@@ -642,7 +721,7 @@ export class GroupCoordinatorState extends EventEmitter {
642
721
  const local =
643
722
  localClientId ===
644
723
  this.playbackTrackData.current.clientId;
645
- await this.emitTriggerAction({
724
+ this.emitTriggerAction({
646
725
  action: "datachange",
647
726
  source: "system",
648
727
  clientId: this.playbackTrackData.current.clientId,
@@ -650,6 +729,23 @@ export class GroupCoordinatorState extends EventEmitter {
650
729
  data: this.playbackTrackData.data,
651
730
  });
652
731
  }
732
+
733
+ // Sync playback rate
734
+ if (this.playbackRate.rate != positionState?.playbackRate) {
735
+ this._logger.sendTelemetryEvent(
736
+ TelemetryEvents.GroupCoordinator.PlaybackRateOutOfSync
737
+ );
738
+ const local =
739
+ localClientId ===
740
+ this.playbackTrackData.current.clientId;
741
+ this.emitTriggerAction({
742
+ action: "ratechange",
743
+ source: "system",
744
+ clientId: this.playbackTrackData.current.clientId,
745
+ local,
746
+ playbackRate: this.playbackRate.rate,
747
+ });
748
+ }
653
749
  }
654
750
  }
655
751
  }
@@ -673,6 +769,16 @@ export class GroupCoordinatorState extends EventEmitter {
673
769
  });
674
770
  }
675
771
 
772
+ private emitTriggerActionOrIgnored(
773
+ details: ExtendedMediaSessionActionDetails
774
+ ): void {
775
+ if (this.isSuspended || this.isWaiting) {
776
+ return this.emitTriggerActionIgnored(details);
777
+ } else {
778
+ return this.emitTriggerAction(details);
779
+ }
780
+ }
781
+
676
782
  private emitTriggerAction(
677
783
  details: ExtendedMediaSessionActionDetails
678
784
  ): void {
@@ -682,4 +788,15 @@ export class GroupCoordinatorState extends EventEmitter {
682
788
  };
683
789
  this.emit(GroupCoordinatorStateEvents.triggeraction, evt);
684
790
  }
791
+
792
+ private emitTriggerActionIgnored(
793
+ details: ExtendedMediaSessionActionDetails
794
+ ): void {
795
+ details.ignoreReason = "localusersuspended";
796
+ const evt: ITriggerActionEvent = {
797
+ name: GroupCoordinatorStateEvents.triggeractionignored,
798
+ details: details,
799
+ };
800
+ this.emit(GroupCoordinatorStateEvents.triggeractionignored, evt);
801
+ }
685
802
  }
@@ -14,6 +14,7 @@ import {
14
14
  CoordinationWaitPoint,
15
15
  ExtendedMediaSessionPlaybackState,
16
16
  } from "../MediaSessionExtensions";
17
+ import { GroupPlaybackRate } from "./GroupPlaybackRate";
17
18
 
18
19
  /**
19
20
  * Per client position
@@ -33,6 +34,7 @@ export interface ICurrentPlaybackPosition {
33
34
  */
34
35
  export class GroupPlaybackPosition {
35
36
  private _transportState: GroupTransportState;
37
+ private _playbackRate: GroupPlaybackRate;
36
38
  private _runtime: IRuntimeSignaler;
37
39
  private _liveRuntime: LiveShareRuntime;
38
40
  private _updateInterval: TimeInterval;
@@ -40,11 +42,13 @@ export class GroupPlaybackPosition {
40
42
 
41
43
  constructor(
42
44
  transportState: GroupTransportState,
45
+ playbackRate: GroupPlaybackRate,
43
46
  runtime: IRuntimeSignaler,
44
47
  liveRuntime: LiveShareRuntime,
45
48
  updateInterval: TimeInterval
46
49
  ) {
47
50
  this._transportState = transportState;
51
+ this._playbackRate = playbackRate;
48
52
  this._runtime = runtime;
49
53
  this._liveRuntime = liveRuntime;
50
54
  this._updateInterval = updateInterval;
@@ -135,7 +139,8 @@ export class GroupPlaybackPosition {
135
139
  const now = this._liveRuntime.getTimestamp();
136
140
  const projected =
137
141
  this._transportState.startPosition +
138
- (now - this._transportState.timestamp) / 1000;
142
+ ((now - this._transportState.timestamp) / 1000) *
143
+ this._playbackRate.rate;
139
144
  return this.limitProjectedPosition(projected);
140
145
  } else {
141
146
  return this._transportState.startPosition;
@@ -175,7 +180,9 @@ export class GroupPlaybackPosition {
175
180
  // - This computation does not take into account future wait points.
176
181
  const projected =
177
182
  position.playbackState == "playing" && shouldProject
178
- ? position.position + (now - position.timestamp) / 1000
183
+ ? position.position +
184
+ ((now - position.timestamp) / 1000) *
185
+ this._playbackRate.rate
179
186
  : position.position;
180
187
  callbackFn(position, this.limitProjectedPosition(projected));
181
188
  }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the Microsoft Live Share SDK License.
4
+ */
5
+
6
+ import EventEmitter from "events";
7
+ import { ExtendedMediaSessionActionSource } from "../MediaSessionExtensions";
8
+ import { IGroupStateEvent } from "./interfaces";
9
+
10
+ /**
11
+ * @hidden
12
+ */
13
+ export interface IPlaybackRate {
14
+ playbackRate: number;
15
+ timestamp: number;
16
+ clientId: string;
17
+ }
18
+
19
+ /**
20
+ * @hidden
21
+ */
22
+ export enum PlaybackRateEvents {
23
+ rateChange = "rateChange",
24
+ }
25
+
26
+ /**
27
+ * @hidden
28
+ */
29
+ export interface IPlaybackRateChangeEvent extends IGroupStateEvent {
30
+ playbackRate: number;
31
+ }
32
+
33
+ /**
34
+ * @hidden
35
+ */
36
+ export class GroupPlaybackRate extends EventEmitter {
37
+ private _current: IPlaybackRate;
38
+
39
+ constructor() {
40
+ super();
41
+ this._current = { playbackRate: 1.0, timestamp: 0, clientId: "" };
42
+ }
43
+
44
+ public get current(): Readonly<IPlaybackRate> {
45
+ return this._current;
46
+ }
47
+
48
+ public get rate(): number {
49
+ return this.current.playbackRate;
50
+ }
51
+
52
+ public updatePlaybackRate(
53
+ newRate: IPlaybackRate,
54
+ source: ExtendedMediaSessionActionSource
55
+ ): boolean {
56
+ // Ignore state changes that are older
57
+ const current = this.current;
58
+ if (newRate.timestamp < current.timestamp) {
59
+ return false;
60
+ }
61
+
62
+ // Ignore state changes that have the same timestamp and the clientId sorts higher.
63
+ if (
64
+ newRate.timestamp == current.timestamp &&
65
+ newRate.clientId.localeCompare(current.clientId) > 0
66
+ ) {
67
+ return false;
68
+ }
69
+
70
+ // Ignore state changes for same data object
71
+ if (current.playbackRate == newRate.playbackRate) {
72
+ return false;
73
+ }
74
+
75
+ // Update current data
76
+ this._current = newRate;
77
+
78
+ const event: IPlaybackRateChangeEvent = {
79
+ name: PlaybackRateEvents.rateChange,
80
+ clientId: newRate.clientId,
81
+ playbackRate: newRate.playbackRate,
82
+ source,
83
+ };
84
+
85
+ // Notify listeners
86
+ this.emit(PlaybackRateEvents.rateChange, event);
87
+
88
+ return true;
89
+ }
90
+ }
@@ -3,7 +3,6 @@
3
3
  * Licensed under the Microsoft Live Share SDK License.
4
4
  */
5
5
 
6
- import { ILiveEvent } from "@microsoft/live-share";
7
6
  import EventEmitter from "events";
8
7
  import { IMediaPlayerState } from "../LiveMediaSessionCoordinator";
9
8
  import {
@@ -11,6 +10,7 @@ import {
11
10
  ExtendedMediaMetadata,
12
11
  ExtendedMediaSessionActionSource,
13
12
  } from "../MediaSessionExtensions";
13
+ import { IGroupStateEvent } from "./interfaces";
14
14
 
15
15
  /**
16
16
  * @hidden
@@ -32,7 +32,7 @@ export enum GroupPlaybackTrackEvents {
32
32
  /**
33
33
  * @hidden
34
34
  */
35
- export interface IPlaybackTrackChangeEvent extends ILiveEvent {
35
+ export interface IPlaybackTrackChangeEvent extends IGroupStateEvent {
36
36
  metadata: ExtendedMediaMetadata | null;
37
37
  }
38
38
 
@@ -149,12 +149,13 @@ export class GroupPlaybackTrack extends EventEmitter {
149
149
  this._current = track;
150
150
 
151
151
  // Notify listeners
152
- this.emit(GroupPlaybackTrackEvents.trackChange, {
152
+ const event: IPlaybackTrackChangeEvent = {
153
153
  name: GroupPlaybackTrackEvents.trackChange,
154
154
  clientId: track.clientId,
155
155
  metadata: track.metadata,
156
156
  source,
157
- });
157
+ };
158
+ this.emit(GroupPlaybackTrackEvents.trackChange, event);
158
159
 
159
160
  return true;
160
161
  }
@@ -3,13 +3,13 @@
3
3
  * Licensed under the Microsoft Live Share SDK License.
4
4
  */
5
5
 
6
- import { ILiveEvent } from "@microsoft/live-share";
7
6
  import EventEmitter from "events";
8
7
  import {
9
8
  GroupPlaybackTrack,
10
9
  GroupPlaybackTrackEvents,
11
10
  } from "./GroupPlaybackTrack";
12
11
  import { ExtendedMediaSessionActionSource } from "../MediaSessionExtensions";
12
+ import { IGroupStateEvent } from "./interfaces";
13
13
 
14
14
  /**
15
15
  * @hidden
@@ -30,7 +30,7 @@ export enum PlaybackTrackDataEvents {
30
30
  /**
31
31
  * @hidden
32
32
  */
33
- export interface IPlaybackTrackDataChangeEvent extends ILiveEvent {
33
+ export interface IPlaybackTrackDataChangeEvent extends IGroupStateEvent {
34
34
  data: object | null;
35
35
  }
36
36
 
@@ -66,38 +66,39 @@ export class GroupPlaybackTrackData extends EventEmitter {
66
66
  }
67
67
 
68
68
  public updateData(
69
- event: IPlaybackTrackData,
69
+ trackData: IPlaybackTrackData,
70
70
  source: ExtendedMediaSessionActionSource
71
71
  ): boolean {
72
72
  // Ignore state changes that are older
73
73
  const current = this.current;
74
- if (event.timestamp < current.timestamp) {
74
+ if (trackData.timestamp < current.timestamp) {
75
75
  return false;
76
76
  }
77
77
 
78
78
  // Ignore state changes that have the same timestamp and the clientId sorts higher.
79
79
  if (
80
- event.timestamp == current.timestamp &&
81
- event.clientId.localeCompare(current.clientId) > 0
80
+ trackData.timestamp == current.timestamp &&
81
+ trackData.clientId.localeCompare(current.clientId) > 0
82
82
  ) {
83
83
  return false;
84
84
  }
85
85
 
86
86
  // Ignore state changes for same data object
87
- if (JSON.stringify(current.data) == JSON.stringify(event.data)) {
87
+ if (JSON.stringify(current.data) == JSON.stringify(trackData.data)) {
88
88
  return false;
89
89
  }
90
90
 
91
91
  // Update current data
92
- this._current = event;
92
+ this._current = trackData;
93
93
 
94
94
  // Notify listeners
95
- this.emit(PlaybackTrackDataEvents.dataChange, {
96
- type: PlaybackTrackDataEvents.dataChange,
97
- clientId: event.clientId,
98
- data: event.data,
95
+ const event: IPlaybackTrackDataChangeEvent = {
96
+ name: PlaybackTrackDataEvents.dataChange,
97
+ clientId: trackData.clientId,
98
+ data: trackData.data,
99
99
  source,
100
- });
100
+ };
101
+ this.emit(PlaybackTrackDataEvents.dataChange, event);
101
102
 
102
103
  return true;
103
104
  }