@microsoft/live-share-media 1.0.0-preview.9 → 1.0.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 (89) hide show
  1. package/LICENSE +64 -61
  2. package/README.md +7 -13
  3. package/bin/IMediaPlayer.d.ts +20 -20
  4. package/bin/IMediaPlayer.js +6 -6
  5. package/bin/LiveMediaSession.d.ts +90 -94
  6. package/bin/LiveMediaSession.d.ts.map +1 -1
  7. package/bin/LiveMediaSession.js +228 -231
  8. package/bin/LiveMediaSession.js.map +1 -1
  9. package/bin/LiveMediaSessionCoordinator.d.ts +238 -227
  10. package/bin/LiveMediaSessionCoordinator.d.ts.map +1 -1
  11. package/bin/LiveMediaSessionCoordinator.js +476 -407
  12. package/bin/LiveMediaSessionCoordinator.js.map +1 -1
  13. package/bin/LiveMediaSessionCoordinatorSuspension.d.ts +19 -19
  14. package/bin/LiveMediaSessionCoordinatorSuspension.js +27 -27
  15. package/bin/MediaPlayerSynchronizer.d.ts +174 -163
  16. package/bin/MediaPlayerSynchronizer.d.ts.map +1 -1
  17. package/bin/MediaPlayerSynchronizer.js +503 -468
  18. package/bin/MediaPlayerSynchronizer.js.map +1 -1
  19. package/bin/MediaSessionActionThrottler.d.ts +11 -11
  20. package/bin/MediaSessionActionThrottler.js +13 -13
  21. package/bin/MediaSessionExtensions.d.ts +34 -34
  22. package/bin/MediaSessionExtensions.d.ts.map +1 -1
  23. package/bin/MediaSessionExtensions.js +12 -12
  24. package/bin/RepeatedActionThrottler.d.ts +19 -19
  25. package/bin/RepeatedActionThrottler.js +62 -62
  26. package/bin/RepeatedActionThrottler.js.map +1 -1
  27. package/bin/VolumeManager.d.ts +83 -83
  28. package/bin/VolumeManager.d.ts.map +1 -1
  29. package/bin/VolumeManager.js +174 -174
  30. package/bin/VolumeManager.js.map +1 -1
  31. package/bin/index.d.ts +13 -13
  32. package/bin/index.js +29 -29
  33. package/bin/internals/GroupCoordinatorState.d.ts +93 -92
  34. package/bin/internals/GroupCoordinatorState.d.ts.map +1 -1
  35. package/bin/internals/GroupCoordinatorState.js +407 -404
  36. package/bin/internals/GroupCoordinatorState.js.map +1 -1
  37. package/bin/internals/GroupPlaybackPosition.d.ts +67 -66
  38. package/bin/internals/GroupPlaybackPosition.d.ts.map +1 -1
  39. package/bin/internals/GroupPlaybackPosition.js +165 -166
  40. package/bin/internals/GroupPlaybackPosition.js.map +1 -1
  41. package/bin/internals/GroupPlaybackTrack.d.ts +45 -45
  42. package/bin/internals/GroupPlaybackTrack.d.ts.map +1 -1
  43. package/bin/internals/GroupPlaybackTrack.js +121 -121
  44. package/bin/internals/GroupPlaybackTrackData.d.ts +39 -39
  45. package/bin/internals/GroupPlaybackTrackData.d.ts.map +1 -1
  46. package/bin/internals/GroupPlaybackTrackData.js +69 -69
  47. package/bin/internals/GroupTransportState.d.ts +49 -48
  48. package/bin/internals/GroupTransportState.d.ts.map +1 -1
  49. package/bin/internals/GroupTransportState.js +111 -111
  50. package/bin/internals/GroupTransportState.js.map +1 -1
  51. package/bin/internals/consts.d.ts +75 -72
  52. package/bin/internals/consts.d.ts.map +1 -1
  53. package/bin/internals/consts.js +84 -81
  54. package/bin/internals/consts.js.map +1 -1
  55. package/bin/internals/index.d.ts +10 -10
  56. package/bin/internals/index.js +26 -26
  57. package/package.json +16 -9
  58. package/src/LiveMediaSession.ts +24 -20
  59. package/src/LiveMediaSessionCoordinator.ts +101 -29
  60. package/src/MediaPlayerSynchronizer.ts +47 -15
  61. package/src/VolumeManager.ts +4 -1
  62. package/src/internals/GroupCoordinatorState.ts +50 -35
  63. package/src/internals/GroupPlaybackPosition.ts +8 -7
  64. package/src/internals/GroupPlaybackTrack.ts +2 -2
  65. package/src/internals/GroupPlaybackTrackData.ts +2 -2
  66. package/src/internals/GroupTransportState.ts +11 -4
  67. package/src/internals/consts.ts +4 -0
  68. package/typedoc.json +10 -0
  69. package/bin/EphemeralMediaSession.d.ts +0 -102
  70. package/bin/EphemeralMediaSession.d.ts.map +0 -1
  71. package/bin/EphemeralMediaSession.js +0 -226
  72. package/bin/EphemeralMediaSession.js.map +0 -1
  73. package/bin/EphemeralMediaSessionCoordinator.d.ts +0 -228
  74. package/bin/EphemeralMediaSessionCoordinator.d.ts.map +0 -1
  75. package/bin/EphemeralMediaSessionCoordinator.js +0 -396
  76. package/bin/EphemeralMediaSessionCoordinator.js.map +0 -1
  77. package/bin/EphemeralMediaSessionCoordinatorSuspension.d.ts +0 -20
  78. package/bin/EphemeralMediaSessionCoordinatorSuspension.d.ts.map +0 -1
  79. package/bin/EphemeralMediaSessionCoordinatorSuspension.js +0 -28
  80. package/bin/EphemeralMediaSessionCoordinatorSuspension.js.map +0 -1
  81. package/bin/VolumeLimiter.d.ts +0 -70
  82. package/bin/VolumeLimiter.d.ts.map +0 -1
  83. package/bin/VolumeLimiter.js +0 -170
  84. package/bin/VolumeLimiter.js.map +0 -1
  85. package/src/test/GroupPlaybackPosition.spec.ts +0 -600
  86. package/src/test/GroupPlaybackTrack.spec.ts +0 -122
  87. package/src/test/SharedMediaSession.spec.ts +0 -149
  88. package/src/test/SharedMediaSessionCoordinator.spec.ts +0 -271
  89. package/src/test/VolumeManager.spec.ts +0 -219
@@ -7,18 +7,15 @@ import {
7
7
  LiveEventScope,
8
8
  LiveTelemetryLogger,
9
9
  LiveEventTarget,
10
- ILiveEvent,
11
10
  IRuntimeSignaler,
12
11
  TimeInterval,
13
12
  UserMeetingRole,
13
+ LiveShareRuntime,
14
14
  } from "@microsoft/live-share";
15
15
  import {
16
16
  CoordinationWaitPoint,
17
- ExtendedMediaSessionActionDetails,
18
17
  ExtendedMediaMetadata,
19
18
  ExtendedMediaSessionPlaybackState,
20
- MediaSessionCoordinatorEvents,
21
- MediaSessionCoordinatorState,
22
19
  MediaSessionCoordinatorSuspension,
23
20
  } from "./MediaSessionExtensions";
24
21
  import {
@@ -28,7 +25,6 @@ import {
28
25
  IPositionUpdateEvent,
29
26
  GroupCoordinatorState,
30
27
  GroupCoordinatorStateEvents,
31
- ITriggerActionEvent,
32
28
  ISetTrackDataEvent,
33
29
  } from "./internals";
34
30
  import { LiveMediaSessionCoordinatorSuspension } from "./LiveMediaSessionCoordinatorSuspension";
@@ -69,6 +65,7 @@ export interface IMediaPlayerState {
69
65
  */
70
66
  export class LiveMediaSessionCoordinator extends EventEmitter {
71
67
  private readonly _runtime: IRuntimeSignaler;
68
+ private readonly _liveRuntime: LiveShareRuntime;
72
69
  private readonly _logger: LiveTelemetryLogger;
73
70
  private readonly _getPlayerState: () => IMediaPlayerState;
74
71
  private _positionUpdateInterval = new TimeInterval(2000);
@@ -83,22 +80,26 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
83
80
  private _setTrackEvent?: LiveEventTarget<ISetTrackEvent>;
84
81
  private _setTrackDataEvent?: LiveEventTarget<ISetTrackDataEvent>;
85
82
  private _positionUpdateEvent?: LiveEventTarget<IPositionUpdateEvent>;
86
- private _joinedEvent?: LiveEventTarget<ILiveEvent>;
83
+ private _joinedEvent?: LiveEventTarget<undefined>;
87
84
 
88
85
  // Distributed state
89
86
  private _groupState?: GroupCoordinatorState;
90
87
 
88
+ private _allowedRoles?: UserMeetingRole[];
89
+
91
90
  /**
92
91
  * @hidden
93
92
  * Applications shouldn't directly create new coordinator instances.
94
93
  */
95
94
  constructor(
96
95
  runtime: IRuntimeSignaler,
96
+ liveRuntime: LiveShareRuntime,
97
97
  getPlayerState: () => IMediaPlayerState
98
98
  ) {
99
99
  super();
100
100
  this._runtime = runtime;
101
- this._logger = new LiveTelemetryLogger(runtime);
101
+ this._liveRuntime = liveRuntime;
102
+ this._logger = new LiveTelemetryLogger(runtime, liveRuntime);
102
103
  this._getPlayerState = getPlayerState;
103
104
  }
104
105
 
@@ -201,8 +202,10 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
201
202
  * @remarks
202
203
  * Throws an exception if the session/coordinator hasn't been initialized, no track has been
203
204
  * loaded, or `canPlayPause` is false.
205
+ *
206
+ * @returns a void promise that resolves once complete, throws if user does not have proper roles
204
207
  */
205
- public play(): void {
208
+ public async play(): Promise<void> {
206
209
  if (!this._hasInitialized) {
207
210
  throw new Error(
208
211
  `LiveMediaSessionCoordinator.play() called before initialize() called.`
@@ -230,7 +233,7 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
230
233
  null,
231
234
  { position: position }
232
235
  );
233
- this._playEvent!.sendEvent({
236
+ await this._playEvent!.sendEvent({
234
237
  track: this._groupState.playbackTrack.current,
235
238
  position: position,
236
239
  });
@@ -242,8 +245,10 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
242
245
  * @remarks
243
246
  * Throws an exception if the session/coordinator hasn't been initialized, no track has been
244
247
  * loaded, or `canPlayPause` is false.
248
+ *
249
+ * @returns a void promise that resolves once complete, throws if user does not have proper roles
245
250
  */
246
- public pause(): void {
251
+ public async pause(): Promise<void> {
247
252
  if (!this._hasInitialized) {
248
253
  throw new Error(
249
254
  `LiveMediaSessionCoordinator.pause() called before initialize() called.`
@@ -271,7 +276,7 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
271
276
  null,
272
277
  { position: position }
273
278
  );
274
- this._pauseEvent!.sendEvent({
279
+ await this._pauseEvent!.sendEvent({
275
280
  track: this._groupState.playbackTrack.current,
276
281
  position: position,
277
282
  });
@@ -284,8 +289,9 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
284
289
  * Throws an exception if the session/coordinator hasn't been initialized, no track has been
285
290
  * loaded, or `canSeek` is false.
286
291
  * @param time Playback position in seconds to seek to.
292
+ * @returns a void promise that resolves once complete, throws if user does not have proper roles
287
293
  */
288
- public seekTo(time: number): void {
294
+ public async seekTo(time: number): Promise<void> {
289
295
  if (!this._hasInitialized) {
290
296
  throw new Error(
291
297
  `LiveMediaSessionCoordinator.seekTo() called before initialize() called.`
@@ -310,10 +316,15 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
310
316
  null,
311
317
  { position: time }
312
318
  );
313
- this._seekToEvent!.sendEvent({
314
- track: this._groupState.playbackTrack.current,
315
- position: time,
316
- });
319
+ try {
320
+ await this._seekToEvent!.sendEvent({
321
+ track: this._groupState.playbackTrack.current,
322
+ position: time,
323
+ });
324
+ } catch (err) {
325
+ await this._groupState!.syncLocalMediaSession();
326
+ throw err;
327
+ }
317
328
  }
318
329
 
319
330
  /**
@@ -324,11 +335,12 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
324
335
  * false.
325
336
  * @param metadata The track to load or `null` to indicate that the end of the track is reached.
326
337
  * @param waitPoints Optional. List of static wait points to configure for the track. Dynamic wait points can be added via the `beginSuspension()` call.
338
+ * @returns a void promise that resolves once complete, throws if user does not have proper roles
327
339
  */
328
- public setTrack(
340
+ public async setTrack(
329
341
  metadata: ExtendedMediaMetadata | null,
330
342
  waitPoints?: CoordinationWaitPoint[]
331
- ): void {
343
+ ): Promise<void> {
332
344
  if (!this._hasInitialized) {
333
345
  throw new Error(
334
346
  `LiveMediaSessionCoordinator.setTrack() called before initialize() called.`
@@ -345,7 +357,7 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
345
357
  this._logger.sendTelemetryEvent(
346
358
  TelemetryEvents.SessionCoordinator.SetTrackCalled
347
359
  );
348
- this._setTrackEvent!.sendEvent({
360
+ await this._setTrackEvent!.sendEvent({
349
361
  metadata: metadata,
350
362
  waitPoints: waitPoints || [],
351
363
  });
@@ -361,8 +373,9 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
361
373
  * Throws an exception if the session/coordinator hasn't been initialized or `canSetTrackData` is
362
374
  * false.
363
375
  * @param data New data object to sync with the group. This value will be synchronized using a last writer wins strategy.
376
+ * @returns a void promise that resolves once complete, throws if user does not have proper roles
364
377
  */
365
- public setTrackData(data: object | null): void {
378
+ public async setTrackData(data: object | null): Promise<void> {
366
379
  if (!this._hasInitialized) {
367
380
  throw new Error(
368
381
  `LiveMediaSessionCoordinator.setTrackData() called before initialize() called.`
@@ -379,7 +392,7 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
379
392
  this._logger.sendTelemetryEvent(
380
393
  TelemetryEvents.SessionCoordinator.SetTrackDataCalled
381
394
  );
382
- this._setTrackDataEvent!.sendEvent({
395
+ await this._setTrackDataEvent!.sendEvent({
383
396
  data: data,
384
397
  });
385
398
  }
@@ -520,23 +533,51 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
520
533
  }
521
534
 
522
535
  // Send position update event
523
- const evt = this._groupState!.createPositionUpdateEvent(state);
524
- this._positionUpdateEvent?.sendEvent(evt);
536
+ this.verifyLocalUserRoles()
537
+ .then((valid) => {
538
+ const evt = this._groupState!.createPositionUpdateEvent(state);
539
+ if (!valid) {
540
+ // We still need to update _groupState with the local user's position.
541
+ // So we do this here rather than on the receiving end.
542
+ this._groupState!.handlePositionUpdate(
543
+ {
544
+ clientId: this._runtime.clientId ?? "",
545
+ timestamp: this._liveRuntime.getTimestamp(),
546
+ data: evt,
547
+ },
548
+ true
549
+ );
550
+ return;
551
+ }
552
+ return this._positionUpdateEvent?.sendEvent(evt);
553
+ })
554
+ .catch((err) => {
555
+ this._logger.sendErrorEvent(
556
+ TelemetryEvents.SessionCoordinator.PositionUpdateEventError,
557
+ err
558
+ );
559
+ });
525
560
  }
526
561
 
527
562
  protected async createChildren(
528
563
  acceptTransportChangesFrom?: UserMeetingRole[]
529
564
  ): Promise<void> {
565
+ this._allowedRoles = acceptTransportChangesFrom;
530
566
  // Create event scopes
531
567
  const scope = new LiveEventScope(
532
568
  this._runtime,
569
+ this._liveRuntime,
533
570
  acceptTransportChangesFrom
534
571
  );
535
- const unrestrictedScope = new LiveEventScope(this._runtime);
572
+ const unrestrictedScope = new LiveEventScope(
573
+ this._runtime,
574
+ this._liveRuntime
575
+ );
536
576
 
537
577
  // Initialize internal coordinator state
538
578
  this._groupState = new GroupCoordinatorState(
539
579
  this._runtime,
580
+ this._liveRuntime,
540
581
  this._maxPlaybackDrift,
541
582
  this._positionUpdateInterval,
542
583
  this._getPlayerState
@@ -596,14 +637,22 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
596
637
  }
597
638
  );
598
639
  const state = this._getPlayerState();
599
- const update =
600
- this._groupState!.createPositionUpdateEvent(state);
601
- this._positionUpdateEvent?.sendEvent(update);
640
+ this.sendPositionUpdate(state);
602
641
  }
603
642
  );
604
643
 
605
- // Send initial joined event
606
- this._joinedEvent.sendEvent({});
644
+ this.verifyLocalUserRoles()
645
+ .then((verified) => {
646
+ if (!verified) return;
647
+ // Send initial joined event
648
+ return this._joinedEvent?.sendEvent(undefined);
649
+ })
650
+ .catch((err) => {
651
+ this._logger.sendErrorEvent(
652
+ TelemetryEvents.SessionCoordinator.SendJoinedEventError,
653
+ err
654
+ );
655
+ });
607
656
  }
608
657
 
609
658
  private getPlayerPosition(): number {
@@ -618,4 +667,27 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
618
667
  : 0.0;
619
668
  }
620
669
  }
670
+
671
+ private async verifyLocalUserRoles(): Promise<boolean> {
672
+ const clientId = await this.waitUntilConnected();
673
+ return this._liveRuntime.verifyRolesAllowed(
674
+ clientId,
675
+ this._allowedRoles ?? []
676
+ );
677
+ }
678
+
679
+ private waitUntilConnected(): Promise<string> {
680
+ return new Promise((resolve) => {
681
+ const onConnected = (clientId: string) => {
682
+ this._runtime.off("connected", onConnected);
683
+ resolve(clientId);
684
+ };
685
+
686
+ if (this._runtime.clientId && this._runtime.connected) {
687
+ resolve(this._runtime.clientId);
688
+ } else {
689
+ this._runtime.on("connected", onConnected);
690
+ }
691
+ });
692
+ }
621
693
  }
@@ -2,7 +2,7 @@
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  * Licensed under the Microsoft Live Share SDK License.
4
4
  */
5
- import { LiveTelemetryLogger, IEvent } from "@microsoft/live-share";
5
+ import { LiveTelemetryLogger, ILiveEvent } from "@microsoft/live-share";
6
6
  import EventEmitter from "events";
7
7
  import {
8
8
  ExtendedMediaSessionAction,
@@ -21,7 +21,7 @@ import { TelemetryEvents } from "./internals";
21
21
  /**
22
22
  * Event data returned by `MediaPlayerSynchronizer` object.
23
23
  */
24
- export interface IMediaPlayerSynchronizerEvent extends IEvent {
24
+ export interface IMediaPlayerSynchronizerEvent extends ILiveEvent {
25
25
  /**
26
26
  * Event details.
27
27
  */
@@ -82,6 +82,7 @@ export class MediaPlayerSynchronizer extends EventEmitter {
82
82
  private _seekSuspension?: MediaSessionCoordinatorSuspension;
83
83
  private _viewOnly = false;
84
84
  private _expectedPlaybackState: ExtendedMediaSessionPlaybackState = "none";
85
+ private _metadata: ExtendedMediaMetadata | null = null;
85
86
  private _trackData: object | null = null;
86
87
 
87
88
  /**
@@ -120,8 +121,15 @@ export class MediaPlayerSynchronizer extends EventEmitter {
120
121
  playbackState = "playing";
121
122
  }
122
123
 
124
+ // Metadata equality check in `GroupPlaybackTrack` would return false if only providing src.
125
+ // Use full metadata if this._metadata has same src
126
+ const metadata =
127
+ this._metadata && this._metadata.trackIdentifier === src
128
+ ? this._metadata
129
+ : ({ trackIdentifier: src } as ExtendedMediaMetadata);
130
+
123
131
  const state = {
124
- metadata: { trackIdentifier: src } as ExtendedMediaMetadata,
132
+ metadata: metadata,
125
133
  playbackState: playbackState,
126
134
  positionState: {
127
135
  position: this._player.currentTime,
@@ -164,7 +172,13 @@ export class MediaPlayerSynchronizer extends EventEmitter {
164
172
  TelemetryEvents.MediaPlayerSynchronizer
165
173
  .UserTappedVideoToPlay
166
174
  );
167
- this.play();
175
+ this.play().catch((err) => {
176
+ this._logger.sendErrorEvent(
177
+ TelemetryEvents.MediaPlayerSynchronizer
178
+ .UserTappedVideoToPlayError,
179
+ err
180
+ );
181
+ });
168
182
  }
169
183
  }
170
184
  // block play if player state is playing when expected synced state is paused and coordinator is not suspended.
@@ -295,6 +309,14 @@ export class MediaPlayerSynchronizer extends EventEmitter {
295
309
  .SetTrackAction
296
310
  );
297
311
  this._expectedPlaybackState = "none";
312
+ this._metadata = details.metadata ?? null;
313
+ if (
314
+ this._player.src ===
315
+ details.metadata?.trackIdentifier
316
+ ) {
317
+ // Don't set duplicate src and load again.
318
+ break;
319
+ }
298
320
  this._player.src =
299
321
  details.metadata!.trackIdentifier;
300
322
  this._player.load();
@@ -498,13 +520,15 @@ export class MediaPlayerSynchronizer extends EventEmitter {
498
520
  * @remarks
499
521
  * For proper operation apps should avoid calling `mediaSession.coordinator.play()` directly
500
522
  * and instead use the synchronizers `play()` method.
523
+ *
524
+ * @returns a void promise that resolves once complete, throws if user does not have proper roles
501
525
  */
502
- public play(): void {
526
+ public async play(): Promise<void> {
503
527
  this._logger.sendTelemetryEvent(
504
528
  TelemetryEvents.MediaPlayerSynchronizer.PlayCalled
505
529
  );
506
530
  this._expectedPlaybackState = "playing";
507
- this._mediaSession.coordinator.play();
531
+ await this._mediaSession.coordinator.play();
508
532
 
509
533
  this.dispatchUserAction({ action: "play" });
510
534
  }
@@ -515,13 +539,15 @@ export class MediaPlayerSynchronizer extends EventEmitter {
515
539
  * @remarks
516
540
  * For proper operation apps should avoid calling `mediaSession.coordinator.pause()` directly
517
541
  * and instead use the synchronizers `pause()` method.
542
+ *
543
+ * @returns a void promise that resolves once complete, throws if user does not have proper roles
518
544
  */
519
- public pause(): void {
545
+ public async pause(): Promise<void> {
520
546
  this._logger.sendTelemetryEvent(
521
547
  TelemetryEvents.MediaPlayerSynchronizer.PauseCalled
522
548
  );
523
549
  this._expectedPlaybackState = "paused";
524
- this._mediaSession.coordinator.pause();
550
+ await this._mediaSession.coordinator.pause();
525
551
 
526
552
  this.dispatchUserAction({ action: "pause" });
527
553
  }
@@ -532,8 +558,10 @@ export class MediaPlayerSynchronizer extends EventEmitter {
532
558
  * @remarks
533
559
  * For proper operation apps should avoid calling `mediaSession.coordinator.seekTo()` directly
534
560
  * and instead use the synchronizers `seekTo()` method.
561
+ *
562
+ * @returns a void promise that resolves once complete, throws if user does not have proper roles
535
563
  */
536
- public seekTo(time: number): void {
564
+ public async seekTo(time: number): Promise<void> {
537
565
  // Always seek player to new time.
538
566
  // - This resolves an issue where the timeline scrubber can temporarily snap back to the original
539
567
  // location.
@@ -544,7 +572,7 @@ export class MediaPlayerSynchronizer extends EventEmitter {
544
572
  null,
545
573
  { position: time }
546
574
  );
547
- this._mediaSession.coordinator.seekTo(time);
575
+ await this._mediaSession.coordinator.seekTo(time);
548
576
 
549
577
  this.dispatchUserAction({ action: "seekto", seekTime: time });
550
578
  }
@@ -555,15 +583,17 @@ export class MediaPlayerSynchronizer extends EventEmitter {
555
583
  * @remarks
556
584
  * For proper operation apps should avoid calling `mediaSession.coordinator.setTrack()` directly
557
585
  * and instead use the synchronizers `setTrack()` method.
586
+ *
587
+ * @returns a void promise that resolves once complete, throws if user does not have proper roles
558
588
  */
559
- public setTrack(
589
+ public async setTrack(
560
590
  track: ExtendedMediaMetadata,
561
591
  waitPoints?: CoordinationWaitPoint[]
562
- ): void {
592
+ ): Promise<void> {
563
593
  this._logger.sendTelemetryEvent(
564
594
  TelemetryEvents.MediaPlayerSynchronizer.SetTrackCalled
565
595
  );
566
- this._mediaSession.coordinator.setTrack(track, waitPoints);
596
+ await this._mediaSession.coordinator.setTrack(track, waitPoints);
567
597
 
568
598
  this.dispatchUserAction({ action: "settrack", metadata: track });
569
599
  }
@@ -574,13 +604,15 @@ export class MediaPlayerSynchronizer extends EventEmitter {
574
604
  * @remarks
575
605
  * For proper operation apps should avoid calling `mediaSession.coordinator.setTrackData()` directly
576
606
  * and instead use the synchronizers `setTrackData()` method.
607
+ *
608
+ * @returns a void promise that resolves once complete, throws if user does not have proper roles
577
609
  */
578
- public setTrackData(data: object | null): void {
610
+ public async setTrackData(data: object | null): Promise<void> {
579
611
  this._logger.sendTelemetryEvent(
580
612
  TelemetryEvents.MediaPlayerSynchronizer.SetTrackDataCalled
581
613
  );
582
614
  this._trackData = data;
583
- this._mediaSession.coordinator.setTrackData(data);
615
+ await this._mediaSession.coordinator.setTrackData(data);
584
616
 
585
617
  this.dispatchUserAction({ action: "datachange", data: data });
586
618
  }
@@ -161,7 +161,10 @@ export class VolumeManager {
161
161
  if (typeof requestAnimationFrame == "function") {
162
162
  requestAnimationFrame(callback);
163
163
  } else {
164
- setTimeout(callback, 20);
164
+ setTimeout(
165
+ callback,
166
+ Math.min(this._volumeChangeDuration.milliseconds / 25, 20)
167
+ );
165
168
  }
166
169
  }
167
170