@microsoft/live-share-media 1.0.0-preview.11 → 1.0.0-preview.13

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 (100) hide show
  1. package/README.md +1 -1
  2. package/bin/EphemeralMediaSession.d.ts +102 -0
  3. package/bin/EphemeralMediaSession.d.ts.map +1 -0
  4. package/bin/EphemeralMediaSession.js +226 -0
  5. package/bin/EphemeralMediaSession.js.map +1 -0
  6. package/bin/EphemeralMediaSessionCoordinator.d.ts +228 -0
  7. package/bin/EphemeralMediaSessionCoordinator.d.ts.map +1 -0
  8. package/bin/EphemeralMediaSessionCoordinator.js +396 -0
  9. package/bin/EphemeralMediaSessionCoordinator.js.map +1 -0
  10. package/bin/EphemeralMediaSessionCoordinatorSuspension.d.ts +20 -0
  11. package/bin/EphemeralMediaSessionCoordinatorSuspension.d.ts.map +1 -0
  12. package/bin/EphemeralMediaSessionCoordinatorSuspension.js +28 -0
  13. package/bin/EphemeralMediaSessionCoordinatorSuspension.js.map +1 -0
  14. package/bin/IMediaPlayer.d.ts +21 -0
  15. package/bin/IMediaPlayer.d.ts.map +1 -0
  16. package/bin/IMediaPlayer.js +7 -0
  17. package/bin/IMediaPlayer.js.map +1 -0
  18. package/bin/LiveMediaSession.d.ts +96 -0
  19. package/bin/LiveMediaSession.d.ts.map +1 -0
  20. package/bin/LiveMediaSession.js +236 -0
  21. package/bin/LiveMediaSession.js.map +1 -0
  22. package/bin/LiveMediaSessionCoordinator.d.ts +239 -0
  23. package/bin/LiveMediaSessionCoordinator.d.ts.map +1 -0
  24. package/bin/LiveMediaSessionCoordinator.js +477 -0
  25. package/bin/LiveMediaSessionCoordinator.js.map +1 -0
  26. package/bin/LiveMediaSessionCoordinatorSuspension.d.ts +20 -0
  27. package/bin/LiveMediaSessionCoordinatorSuspension.d.ts.map +1 -0
  28. package/bin/LiveMediaSessionCoordinatorSuspension.js +28 -0
  29. package/bin/LiveMediaSessionCoordinatorSuspension.js.map +1 -0
  30. package/bin/MediaPlayerSynchronizer.d.ts +174 -0
  31. package/bin/MediaPlayerSynchronizer.d.ts.map +1 -0
  32. package/bin/MediaPlayerSynchronizer.js +492 -0
  33. package/bin/MediaPlayerSynchronizer.js.map +1 -0
  34. package/bin/MediaSessionActionThrottler.d.ts +12 -0
  35. package/bin/MediaSessionActionThrottler.d.ts.map +1 -0
  36. package/bin/MediaSessionActionThrottler.js +14 -0
  37. package/bin/MediaSessionActionThrottler.js.map +1 -0
  38. package/bin/MediaSessionExtensions.d.ts +35 -0
  39. package/bin/MediaSessionExtensions.d.ts.map +1 -0
  40. package/bin/MediaSessionExtensions.js +13 -0
  41. package/bin/MediaSessionExtensions.js.map +1 -0
  42. package/bin/RepeatedActionThrottler.d.ts +20 -0
  43. package/bin/RepeatedActionThrottler.d.ts.map +1 -0
  44. package/bin/RepeatedActionThrottler.js +63 -0
  45. package/bin/RepeatedActionThrottler.js.map +1 -0
  46. package/bin/VolumeLimiter.d.ts +70 -0
  47. package/bin/VolumeLimiter.d.ts.map +1 -0
  48. package/bin/VolumeLimiter.js +170 -0
  49. package/bin/VolumeLimiter.js.map +1 -0
  50. package/bin/VolumeManager.d.ts +84 -0
  51. package/bin/VolumeManager.d.ts.map +1 -0
  52. package/bin/VolumeManager.js +175 -0
  53. package/bin/VolumeManager.js.map +1 -0
  54. package/bin/index.d.ts +14 -0
  55. package/bin/index.d.ts.map +1 -0
  56. package/bin/index.js +30 -0
  57. package/bin/index.js.map +1 -0
  58. package/bin/internals/GroupCoordinatorState.d.ts +94 -0
  59. package/bin/internals/GroupCoordinatorState.d.ts.map +1 -0
  60. package/bin/internals/GroupCoordinatorState.js +408 -0
  61. package/bin/internals/GroupCoordinatorState.js.map +1 -0
  62. package/bin/internals/GroupPlaybackPosition.d.ts +68 -0
  63. package/bin/internals/GroupPlaybackPosition.d.ts.map +1 -0
  64. package/bin/internals/GroupPlaybackPosition.js +167 -0
  65. package/bin/internals/GroupPlaybackPosition.js.map +1 -0
  66. package/bin/internals/GroupPlaybackTrack.d.ts +46 -0
  67. package/bin/internals/GroupPlaybackTrack.d.ts.map +1 -0
  68. package/bin/internals/GroupPlaybackTrack.js +122 -0
  69. package/bin/internals/GroupPlaybackTrack.js.map +1 -0
  70. package/bin/internals/GroupPlaybackTrackData.d.ts +40 -0
  71. package/bin/internals/GroupPlaybackTrackData.d.ts.map +1 -0
  72. package/bin/internals/GroupPlaybackTrackData.js +70 -0
  73. package/bin/internals/GroupPlaybackTrackData.js.map +1 -0
  74. package/bin/internals/GroupTransportState.d.ts +50 -0
  75. package/bin/internals/GroupTransportState.d.ts.map +1 -0
  76. package/bin/internals/GroupTransportState.js +112 -0
  77. package/bin/internals/GroupTransportState.js.map +1 -0
  78. package/bin/internals/consts.d.ts +76 -0
  79. package/bin/internals/consts.d.ts.map +1 -0
  80. package/bin/internals/consts.js +85 -0
  81. package/bin/internals/consts.js.map +1 -0
  82. package/bin/internals/index.d.ts +11 -0
  83. package/bin/internals/index.d.ts.map +1 -0
  84. package/bin/internals/index.js +27 -0
  85. package/bin/internals/index.js.map +1 -0
  86. package/package.json +4 -4
  87. package/src/LiveMediaSession.ts +24 -12
  88. package/src/LiveMediaSessionCoordinator.ts +101 -29
  89. package/src/MediaPlayerSynchronizer.ts +30 -14
  90. package/src/internals/GroupCoordinatorState.ts +49 -35
  91. package/src/internals/GroupPlaybackPosition.ts +6 -2
  92. package/src/internals/GroupPlaybackTrack.ts +2 -2
  93. package/src/internals/GroupPlaybackTrackData.ts +2 -2
  94. package/src/internals/GroupTransportState.ts +11 -4
  95. package/src/internals/consts.ts +4 -0
  96. package/src/test/GroupPlaybackPosition.spec.ts +0 -600
  97. package/src/test/GroupPlaybackTrack.spec.ts +0 -122
  98. package/src/test/SharedMediaSession.spec.ts +0 -149
  99. package/src/test/SharedMediaSessionCoordinator.spec.ts +0 -271
  100. package/src/test/VolumeManager.spec.ts +0 -219
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the Microsoft Live Share SDK License.
4
+ */
5
+ export * from "./consts";
6
+ export * from "./GroupCoordinatorState";
7
+ export * from "./GroupPlaybackPosition";
8
+ export * from "./GroupPlaybackTrack";
9
+ export * from "./GroupPlaybackTrackData";
10
+ export * from "./GroupTransportState";
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/internals/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,UAAU,CAAC;AACzB,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC"}
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Microsoft Corporation. All rights reserved.
4
+ * Licensed under the Microsoft Live Share SDK License.
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
18
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
19
+ };
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ __exportStar(require("./consts"), exports);
22
+ __exportStar(require("./GroupCoordinatorState"), exports);
23
+ __exportStar(require("./GroupPlaybackPosition"), exports);
24
+ __exportStar(require("./GroupPlaybackTrack"), exports);
25
+ __exportStar(require("./GroupPlaybackTrackData"), exports);
26
+ __exportStar(require("./GroupTransportState"), exports);
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/internals/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;;;;;;;;;;;AAEH,2CAAyB;AACzB,0DAAwC;AACxC,0DAAwC;AACxC,uDAAqC;AACrC,2DAAyC;AACzC,wDAAsC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/live-share-media",
3
- "version": "1.0.0-preview.11",
3
+ "version": "1.0.0-preview.13",
4
4
  "description": "Live Share classes that synchronize media playback across clients.",
5
5
  "author": "Microsoft",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -14,7 +14,7 @@
14
14
  "test:coverage": "nyc --reporter=html --reporter=text --reporter=text-summary npm test"
15
15
  },
16
16
  "devDependencies": {
17
- "@microsoft/live-share": "1.0.0-preview.11",
17
+ "@microsoft/live-share": "1.0.0-preview.13",
18
18
  "@types/assert": "^1.5.6",
19
19
  "@types/mocha": "^10.0.1",
20
20
  "@types/node": "^18.11.18",
@@ -22,10 +22,10 @@
22
22
  "mocha": "^10.2.0",
23
23
  "nyc": "^15.1.0",
24
24
  "ts-mocha": "^10.0.0",
25
- "typescript": "^4.6.4"
25
+ "typescript": "^5.0.4"
26
26
  },
27
27
  "peerDependencies": {
28
- "@microsoft/live-share": "1.0.0-preview.11",
28
+ "@microsoft/live-share": "1.0.0-preview.13",
29
29
  "fluid-framework": "^1.2.3"
30
30
  },
31
31
  "repository": {
@@ -3,8 +3,13 @@
3
3
  * Licensed under the Microsoft Live Share SDK License.
4
4
  */
5
5
 
6
- import { DataObject, DataObjectFactory } from "@fluidframework/aqueduct";
7
- import { DynamicObjectRegistry, LiveTelemetryLogger, UserMeetingRole } from "@microsoft/live-share";
6
+ import { DataObjectFactory } from "@fluidframework/aqueduct";
7
+ import {
8
+ DynamicObjectRegistry,
9
+ LiveDataObject,
10
+ LiveTelemetryLogger,
11
+ UserMeetingRole,
12
+ } from "@microsoft/live-share";
8
13
  import { MediaPlayerSynchronizer } from "./MediaPlayerSynchronizer";
9
14
  import { ITriggerActionEvent, TelemetryEvents } from "./internals";
10
15
  import {
@@ -23,7 +28,7 @@ import { IMediaPlayer } from "./IMediaPlayer";
23
28
  /**
24
29
  * Live fluid object that synchronizes media playback across multiple clients.
25
30
  */
26
- export class LiveMediaSession extends DataObject {
31
+ export class LiveMediaSession extends LiveDataObject {
27
32
  private _actionThrottler: MediaSessionActionThrottler =
28
33
  new RepeatedActionThrottler();
29
34
  private _logger?: LiveTelemetryLogger;
@@ -107,13 +112,15 @@ export class LiveMediaSession extends DataObject {
107
112
 
108
113
  /**
109
114
  * initialize the object.
110
- * @param acceptTransportChangesFrom Optional. List of roles allowed to group transport
115
+ * @param allowedRoles Optional. List of roles allowed to group transport
111
116
  * operations like play/pause/seek/setTrack.
117
+ * @returns a void promise that resolves once complete
112
118
  */
113
- public initialize(
114
- acceptTransportChangesFrom?: UserMeetingRole[]
115
- ): Promise<void> {
116
- return this.coordinator.initialize(acceptTransportChangesFrom);
119
+ public initialize(allowedRoles?: UserMeetingRole[]): Promise<void> {
120
+ if (allowedRoles) {
121
+ this._allowedRoles = allowedRoles;
122
+ }
123
+ return this.coordinator.initialize(allowedRoles);
117
124
  }
118
125
 
119
126
  /**
@@ -184,11 +191,13 @@ export class LiveMediaSession extends DataObject {
184
191
  }
185
192
 
186
193
  protected async hasInitialized(): Promise<void> {
187
- this._logger = new LiveTelemetryLogger(this.runtime);
194
+ this._logger = new LiveTelemetryLogger(this.runtime, this.liveRuntime);
188
195
 
189
196
  // Create coordinator and listen for triggered actions
190
- this._coordinator = new LiveMediaSessionCoordinator(this.runtime, () =>
191
- this.getCurrentPlayerState()
197
+ this._coordinator = new LiveMediaSessionCoordinator(
198
+ this.runtime,
199
+ this.liveRuntime,
200
+ () => this.getCurrentPlayerState()
192
201
  );
193
202
  this._coordinator.on(
194
203
  MediaSessionCoordinatorEvents.triggeraction,
@@ -302,4 +311,7 @@ export class LiveMediaSession extends DataObject {
302
311
  /**
303
312
  * Register `LiveMediaSession` as an available `LoadableObjectClass` for use in packages that support dynamic object loading, such as `@microsoft/live-share-turbo`.
304
313
  */
305
- DynamicObjectRegistry.registerObjectClass(LiveMediaSession, LiveMediaSession.TypeName);
314
+ DynamicObjectRegistry.registerObjectClass(
315
+ LiveMediaSession,
316
+ LiveMediaSession.TypeName
317
+ );
@@ -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
  */
@@ -164,7 +164,13 @@ export class MediaPlayerSynchronizer extends EventEmitter {
164
164
  TelemetryEvents.MediaPlayerSynchronizer
165
165
  .UserTappedVideoToPlay
166
166
  );
167
- this.play();
167
+ this.play().catch((err) => {
168
+ this._logger.sendErrorEvent(
169
+ TelemetryEvents.MediaPlayerSynchronizer
170
+ .UserTappedVideoToPlayError,
171
+ err
172
+ );
173
+ });
168
174
  }
169
175
  }
170
176
  // block play if player state is playing when expected synced state is paused and coordinator is not suspended.
@@ -498,13 +504,15 @@ export class MediaPlayerSynchronizer extends EventEmitter {
498
504
  * @remarks
499
505
  * For proper operation apps should avoid calling `mediaSession.coordinator.play()` directly
500
506
  * and instead use the synchronizers `play()` method.
507
+ *
508
+ * @returns a void promise that resolves once complete, throws if user does not have proper roles
501
509
  */
502
- public play(): void {
510
+ public async play(): Promise<void> {
503
511
  this._logger.sendTelemetryEvent(
504
512
  TelemetryEvents.MediaPlayerSynchronizer.PlayCalled
505
513
  );
506
514
  this._expectedPlaybackState = "playing";
507
- this._mediaSession.coordinator.play();
515
+ await this._mediaSession.coordinator.play();
508
516
 
509
517
  this.dispatchUserAction({ action: "play" });
510
518
  }
@@ -515,13 +523,15 @@ export class MediaPlayerSynchronizer extends EventEmitter {
515
523
  * @remarks
516
524
  * For proper operation apps should avoid calling `mediaSession.coordinator.pause()` directly
517
525
  * and instead use the synchronizers `pause()` method.
526
+ *
527
+ * @returns a void promise that resolves once complete, throws if user does not have proper roles
518
528
  */
519
- public pause(): void {
529
+ public async pause(): Promise<void> {
520
530
  this._logger.sendTelemetryEvent(
521
531
  TelemetryEvents.MediaPlayerSynchronizer.PauseCalled
522
532
  );
523
533
  this._expectedPlaybackState = "paused";
524
- this._mediaSession.coordinator.pause();
534
+ await this._mediaSession.coordinator.pause();
525
535
 
526
536
  this.dispatchUserAction({ action: "pause" });
527
537
  }
@@ -532,8 +542,10 @@ export class MediaPlayerSynchronizer extends EventEmitter {
532
542
  * @remarks
533
543
  * For proper operation apps should avoid calling `mediaSession.coordinator.seekTo()` directly
534
544
  * and instead use the synchronizers `seekTo()` method.
545
+ *
546
+ * @returns a void promise that resolves once complete, throws if user does not have proper roles
535
547
  */
536
- public seekTo(time: number): void {
548
+ public async seekTo(time: number): Promise<void> {
537
549
  // Always seek player to new time.
538
550
  // - This resolves an issue where the timeline scrubber can temporarily snap back to the original
539
551
  // location.
@@ -544,7 +556,7 @@ export class MediaPlayerSynchronizer extends EventEmitter {
544
556
  null,
545
557
  { position: time }
546
558
  );
547
- this._mediaSession.coordinator.seekTo(time);
559
+ await this._mediaSession.coordinator.seekTo(time);
548
560
 
549
561
  this.dispatchUserAction({ action: "seekto", seekTime: time });
550
562
  }
@@ -555,15 +567,17 @@ export class MediaPlayerSynchronizer extends EventEmitter {
555
567
  * @remarks
556
568
  * For proper operation apps should avoid calling `mediaSession.coordinator.setTrack()` directly
557
569
  * and instead use the synchronizers `setTrack()` method.
570
+ *
571
+ * @returns a void promise that resolves once complete, throws if user does not have proper roles
558
572
  */
559
- public setTrack(
573
+ public async setTrack(
560
574
  track: ExtendedMediaMetadata,
561
575
  waitPoints?: CoordinationWaitPoint[]
562
- ): void {
576
+ ): Promise<void> {
563
577
  this._logger.sendTelemetryEvent(
564
578
  TelemetryEvents.MediaPlayerSynchronizer.SetTrackCalled
565
579
  );
566
- this._mediaSession.coordinator.setTrack(track, waitPoints);
580
+ await this._mediaSession.coordinator.setTrack(track, waitPoints);
567
581
 
568
582
  this.dispatchUserAction({ action: "settrack", metadata: track });
569
583
  }
@@ -574,13 +588,15 @@ export class MediaPlayerSynchronizer extends EventEmitter {
574
588
  * @remarks
575
589
  * For proper operation apps should avoid calling `mediaSession.coordinator.setTrackData()` directly
576
590
  * and instead use the synchronizers `setTrackData()` method.
591
+ *
592
+ * @returns a void promise that resolves once complete, throws if user does not have proper roles
577
593
  */
578
- public setTrackData(data: object | null): void {
594
+ public async setTrackData(data: object | null): Promise<void> {
579
595
  this._logger.sendTelemetryEvent(
580
596
  TelemetryEvents.MediaPlayerSynchronizer.SetTrackDataCalled
581
597
  );
582
598
  this._trackData = data;
583
- this._mediaSession.coordinator.setTrackData(data);
599
+ await this._mediaSession.coordinator.setTrackData(data);
584
600
 
585
601
  this.dispatchUserAction({ action: "datachange", data: data });
586
602
  }