@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
@@ -33,6 +33,7 @@ import {
33
33
  ISetTrackDataEvent,
34
34
  TrackMetadataNotSetError,
35
35
  ActionBlockedError,
36
+ IRateChangeCommandEvent,
36
37
  } from "./internals";
37
38
  import { LiveMediaSessionCoordinatorSuspension } from "./LiveMediaSessionCoordinatorSuspension";
38
39
  import EventEmitter from "events";
@@ -87,6 +88,7 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
87
88
  private _playEvent?: LiveEventTarget<ITransportCommandEvent>;
88
89
  private _pauseEvent?: LiveEventTarget<ITransportCommandEvent>;
89
90
  private _seekToEvent?: LiveEventTarget<ITransportCommandEvent>;
91
+ private _rateChangeEvent?: LiveEventTarget<IRateChangeCommandEvent>;
90
92
  private _setTrackEvent?: LiveEventTarget<ISetTrackEvent>;
91
93
  private _setTrackDataEvent?: LiveEventTarget<ISetTrackDataEvent>;
92
94
  private _positionUpdateEvent?: LiveEventTarget<IPositionUpdateEvent>;
@@ -170,6 +172,20 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
170
172
  */
171
173
  public canSetTrackData: boolean = true;
172
174
 
175
+ /**
176
+ * Controls whether or not the local client is allowed to change the playback rate.
177
+ *
178
+ * @remarks
179
+ * This flag largely meant to influence decisions made by the coordinator and can be used by
180
+ * the UI to determine what controls should be shown to the user. It does not provide any
181
+ * security in itself.
182
+ *
183
+ * If your app is running in a semi-trusted environment where only some clients are allowed
184
+ * to change the tracks data object, you should use "role based verification" to enforce those
185
+ * policies.
186
+ */
187
+ public canSetPlaybackRate: boolean = true;
188
+
173
189
  /**
174
190
  * Controls whether or not the local client is allowed to send position updates to the group.
175
191
  *
@@ -346,6 +362,27 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
346
362
  }
347
363
  }
348
364
 
365
+ public async setPlaybackRate(playbackRate: number): Promise<void> {
366
+ LiveDataObjectNotInitializedError.assert(
367
+ "LiveMediaSessionCoordinator:setPlaybackRate",
368
+ "setPlaybackRate",
369
+ this.initializeState
370
+ );
371
+ ActionBlockedError.assert(
372
+ this.canSetPlaybackRate,
373
+ "LiveMediaSessionCoordinator:setPlaybackRate",
374
+ "setPlaybackRate",
375
+ "canSetPlaybackRate"
376
+ );
377
+
378
+ this._logger.sendTelemetryEvent(
379
+ TelemetryEvents.SessionCoordinator.SetPlaybackRateCalled,
380
+ null,
381
+ { playbackRate }
382
+ );
383
+ await this._rateChangeEvent!.sendEvent({ playbackRate });
384
+ }
385
+
349
386
  /**
350
387
  * Instructs the group to load a new track.
351
388
  *
@@ -624,6 +661,11 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
624
661
  this.emit(evt.name, evt)
625
662
  );
626
663
 
664
+ this._groupState.on(
665
+ GroupCoordinatorStateEvents.triggeractionignored,
666
+ (evt) => this.emit(evt.name, evt)
667
+ );
668
+
627
669
  // Listen for track changes
628
670
  this._setTrackEvent = new LiveEventTarget<ISetTrackEvent>(
629
671
  scope,
@@ -649,6 +691,13 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
649
691
  (event, local) =>
650
692
  this._groupState!.handleTransportCommand(event, local)
651
693
  );
694
+ this._rateChangeEvent = new LiveEventTarget(
695
+ scope,
696
+ "rateChange",
697
+ (event: ILiveEvent<IRateChangeCommandEvent>, local) => {
698
+ this._groupState!.handleRateChangeCommand(event, local);
699
+ }
700
+ );
652
701
 
653
702
  // Listen for position updates
654
703
  this._positionUpdateEvent = new LiveEventTarget(
@@ -20,7 +20,7 @@ import {
20
20
  import { VolumeManager } from "./VolumeManager";
21
21
  import { LiveMediaSession } from "./LiveMediaSession";
22
22
  import { IMediaPlayer } from "./IMediaPlayer";
23
- import { TelemetryEvents } from "./internals";
23
+ import { ITriggerActionEvent, TelemetryEvents } from "./internals";
24
24
  import { waitUntilConnected } from "@microsoft/live-share/bin/internals";
25
25
 
26
26
  /**
@@ -64,6 +64,7 @@ export class MediaPlayerSynchronizer extends EventEmitter {
64
64
  "pause",
65
65
  "seekto",
66
66
  "settrack",
67
+ "ratechange",
67
68
  "datachange",
68
69
  "catchup",
69
70
  "wait",
@@ -89,6 +90,7 @@ export class MediaPlayerSynchronizer extends EventEmitter {
89
90
  private _viewOnly = false;
90
91
  private _blockUnexpectedPlayerEvents = true;
91
92
  private _expectedPlaybackState: ExtendedMediaSessionPlaybackState = "none";
93
+ private _expectedPlaybackRate: number = 1.0;
92
94
  private _metadata: ExtendedMediaMetadata | null = null;
93
95
  private _trackData: object | null = null;
94
96
 
@@ -142,7 +144,7 @@ export class MediaPlayerSynchronizer extends EventEmitter {
142
144
  playbackState: playbackState,
143
145
  positionState: {
144
146
  position: this._player.currentTime,
145
- playbackRate: this._player.playbackRate,
147
+ playbackRate: this._player.playbackRate ?? 1,
146
148
  duration: this._player.duration,
147
149
  },
148
150
  trackData: this._trackData,
@@ -227,22 +229,19 @@ export class MediaPlayerSynchronizer extends EventEmitter {
227
229
  }
228
230
  break;
229
231
  case "ratechange":
230
- // Block rate changes unless suspended.
231
232
  if (
232
- this._player.playbackRate != 1.0 &&
233
+ this._blockUnexpectedPlayerEvents &&
234
+ this._expectedPlaybackRate !==
235
+ this._player.playbackRate &&
233
236
  !this._mediaSession.coordinator.isSuspended
234
237
  ) {
235
- this._logger.sendTelemetryEvent(
236
- TelemetryEvents.MediaPlayerSynchronizer
237
- .PlaybackRateChangeBlocked
238
- );
239
- this._player.playbackRate = 1.0;
238
+ this._player.playbackRate = this._expectedPlaybackRate;
240
239
  }
241
240
  break;
242
241
  case "blocked":
243
242
  this._logger.sendTelemetryEvent(
244
243
  TelemetryEvents.MediaPlayerSynchronizer
245
- .PlaybackRateChangeBlocked
244
+ .PlayerBlockedOperation
246
245
  );
247
246
  break;
248
247
  }
@@ -254,6 +253,13 @@ export class MediaPlayerSynchronizer extends EventEmitter {
254
253
  (evt) => this.emit(evt.type, evt)
255
254
  );
256
255
 
256
+ this._mediaSession.coordinator.on(
257
+ MediaSessionCoordinatorEvents.triggeractionignored,
258
+ (evt: ITriggerActionEvent) => {
259
+ this.dispatchGroupAction(evt.details, undefined);
260
+ }
261
+ );
262
+
257
263
  // Register media session actions
258
264
  for (const action of MediaPlayerSynchronizer.SESSION_ACTIONS) {
259
265
  this._mediaSession.setActionHandler(
@@ -344,6 +350,22 @@ export class MediaPlayerSynchronizer extends EventEmitter {
344
350
  .DataChangeAction
345
351
  );
346
352
  break;
353
+ case "ratechange":
354
+ if (!details.playbackRate) {
355
+ break;
356
+ }
357
+
358
+ this._logger.sendTelemetryEvent(
359
+ TelemetryEvents.MediaPlayerSynchronizer
360
+ .RateChangeAction,
361
+ null,
362
+ { playbackRate: details.playbackRate }
363
+ );
364
+ this._expectedPlaybackRate =
365
+ details.playbackRate;
366
+ this._player.playbackRate =
367
+ details.playbackRate;
368
+ break;
347
369
  case "catchup":
348
370
  if (typeof details.seekTime == "number") {
349
371
  this._logger.sendTelemetryEvent(
@@ -401,10 +423,10 @@ export class MediaPlayerSynchronizer extends EventEmitter {
401
423
  *
402
424
  * @remarks
403
425
  * Toggling this value to true results in `mediaSession.coordinator.canPlayPause`,
404
- * `mediaSession.coordinator.canSeek`, `mediaSession.coordinator.canSetTrack`, and
405
- * `mediaSession.coordinator.canSetTrackData` all being set to false. For more fine
406
- * grained control over the local clients policies, call the `mediaSession.coordinator`
407
- * directly.
426
+ * `mediaSession.coordinator.canSeek`, `mediaSession.coordinator.canSetTrack`,
427
+ * `mediaSession.coordinator.canSetTrackData`, and `mediaSession.coordinator.canSetPlaybackRate`
428
+ * all being set to false. For more fine grained control over the local clients policies,
429
+ * call the `mediaSession.coordinator` directly.
408
430
  */
409
431
  public get viewOnly(): boolean {
410
432
  return this._viewOnly;
@@ -416,6 +438,7 @@ export class MediaPlayerSynchronizer extends EventEmitter {
416
438
  this.mediaSession.coordinator.canSeek = !value;
417
439
  this.mediaSession.coordinator.canSetTrack = !value;
418
440
  this.mediaSession.coordinator.canSetTrackData = !value;
441
+ this.mediaSession.coordinator.canSetPlaybackRate = !value;
419
442
  }
420
443
 
421
444
  /**
@@ -635,6 +658,24 @@ export class MediaPlayerSynchronizer extends EventEmitter {
635
658
  });
636
659
  }
637
660
 
661
+ public async setPlaybackRate(playbackRate: number): Promise<void> {
662
+ this._logger.sendTelemetryEvent(
663
+ TelemetryEvents.MediaPlayerSynchronizer.RateChangeCalled,
664
+ null,
665
+ { playbackRate }
666
+ );
667
+
668
+ await this._mediaSession.coordinator.setPlaybackRate(playbackRate);
669
+
670
+ this.dispatchUserAction({
671
+ action: "ratechange",
672
+ source: "user",
673
+ clientId: await waitUntilConnected(this._runtime),
674
+ local: true,
675
+ playbackRate,
676
+ });
677
+ }
678
+
638
679
  /**
639
680
  * Tells the group to change to a new track.
640
681
  *
@@ -687,7 +728,6 @@ export class MediaPlayerSynchronizer extends EventEmitter {
687
728
  });
688
729
  }
689
730
 
690
- // TODO: what is delay useful for? we do not use, delete?
691
731
  private dispatchGroupAction(
692
732
  details: ExtendedMediaSessionActionDetails,
693
733
  error: Error | undefined
@@ -59,6 +59,10 @@ export enum MediaSessionCoordinatorEvents {
59
59
  * Event emitted when an action was triggered.
60
60
  */
61
61
  triggeraction = "triggeraction",
62
+ /**
63
+ * Event emitted when an action would have been triggered, but a suspension is active for the local client.
64
+ */
65
+ triggeractionignored = "triggeractionignored",
62
66
  }
63
67
 
64
68
  /**
@@ -71,6 +75,7 @@ export enum MediaSessionCoordinatorEvents {
71
75
  export type ExtendedMediaSessionAction =
72
76
  | MediaSessionAction
73
77
  | "settrack"
78
+ | "ratechange"
74
79
  | "wait"
75
80
  | "catchup"
76
81
  | "datachange"
@@ -116,6 +121,15 @@ export interface ExtendedMediaMetadata extends MediaMetadata {
116
121
  liveStream?: boolean;
117
122
  }
118
123
 
124
+ /**
125
+ * The reasons why an action is ignored.
126
+ *
127
+ * @remarks
128
+ * Is `localusersuspended` when the action updated the group state, but ignored locally due to the local client being suspended.
129
+
130
+ */
131
+ export type ExtendedMediaSessionActionIgnore = "localusersuspended";
132
+
119
133
  /**
120
134
  * Details for emitted actions from `LiveMediaSession`.
121
135
  *
@@ -169,9 +183,11 @@ export interface ExtendedMediaSessionActionDetails {
169
183
  */
170
184
  data?: object | null;
171
185
  /**
172
- * Action type that was blocked.
186
+ * Reason an action is ignored by the local client, undefined if not ignored.
173
187
  */
174
- blocked?: ExtendedMediaSessionAction | null;
188
+ ignoreReason?: ExtendedMediaSessionActionIgnore | null;
189
+
190
+ playbackRate?: number | null;
175
191
  }
176
192
 
177
193
  /**