@microsoft/live-share-media 0.1.2 → 0.2.0

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 (65) hide show
  1. package/.vscode/settings.json +2 -0
  2. package/LICENSE +87 -0
  3. package/README.md +108 -92
  4. package/bin/EphemeralMediaSession.d.ts +51 -4
  5. package/bin/EphemeralMediaSession.d.ts.map +1 -1
  6. package/bin/EphemeralMediaSession.js +91 -44
  7. package/bin/EphemeralMediaSession.js.map +1 -1
  8. package/bin/EphemeralMediaSessionCoordinator.d.ts +173 -6
  9. package/bin/EphemeralMediaSessionCoordinator.d.ts.map +1 -1
  10. package/bin/EphemeralMediaSessionCoordinator.js +170 -23
  11. package/bin/EphemeralMediaSessionCoordinator.js.map +1 -1
  12. package/bin/EphemeralMediaSessionCoordinatorSuspension.d.ts +1 -1
  13. package/bin/EphemeralMediaSessionCoordinatorSuspension.js +1 -1
  14. package/bin/IMediaPlayer.d.ts +1 -1
  15. package/bin/IMediaPlayer.js +1 -1
  16. package/bin/MediaPlayerSynchronizer.d.ts +95 -6
  17. package/bin/MediaPlayerSynchronizer.d.ts.map +1 -1
  18. package/bin/MediaPlayerSynchronizer.js +93 -31
  19. package/bin/MediaPlayerSynchronizer.js.map +1 -1
  20. package/bin/MediaSessionActionThrottler.d.ts +1 -1
  21. package/bin/MediaSessionActionThrottler.js +1 -1
  22. package/bin/MediaSessionExtensions.d.ts +1 -1
  23. package/bin/MediaSessionExtensions.js +1 -1
  24. package/bin/RepeatedActionThrottler.d.ts +1 -1
  25. package/bin/RepeatedActionThrottler.js +1 -1
  26. package/bin/VolumeLimiter.d.ts +2 -2
  27. package/bin/VolumeLimiter.js +2 -2
  28. package/bin/index.d.ts +1 -1
  29. package/bin/index.js +1 -1
  30. package/bin/internals/GroupCoordinatorState.d.ts +1 -1
  31. package/bin/internals/GroupCoordinatorState.js +1 -1
  32. package/bin/internals/GroupPlaybackPosition.d.ts +1 -1
  33. package/bin/internals/GroupPlaybackPosition.js +1 -1
  34. package/bin/internals/GroupPlaybackTrack.d.ts +1 -1
  35. package/bin/internals/GroupPlaybackTrack.js +1 -1
  36. package/bin/internals/GroupPlaybackTrackData.d.ts +1 -1
  37. package/bin/internals/GroupPlaybackTrackData.js +1 -1
  38. package/bin/internals/GroupTransportState.d.ts +1 -1
  39. package/bin/internals/GroupTransportState.js +1 -1
  40. package/bin/internals/consts.d.ts +1 -1
  41. package/bin/internals/consts.js +1 -1
  42. package/bin/internals/index.d.ts +1 -1
  43. package/bin/internals/index.js +1 -1
  44. package/package.json +13 -4
  45. package/src/EphemeralMediaSession.ts +95 -48
  46. package/src/EphemeralMediaSessionCoordinator.ts +190 -26
  47. package/src/EphemeralMediaSessionCoordinatorSuspension.ts +1 -1
  48. package/src/IMediaPlayer.ts +1 -1
  49. package/src/MediaPlayerSynchronizer.ts +101 -33
  50. package/src/MediaSessionActionThrottler.ts +1 -1
  51. package/src/MediaSessionExtensions.ts +1 -1
  52. package/src/RepeatedActionThrottler.ts +1 -1
  53. package/src/VolumeLimiter.ts +2 -2
  54. package/src/index.ts +1 -1
  55. package/src/internals/GroupCoordinatorState.ts +1 -1
  56. package/src/internals/GroupPlaybackPosition.ts +1 -1
  57. package/src/internals/GroupPlaybackTrack.ts +1 -1
  58. package/src/internals/GroupPlaybackTrackData.ts +1 -1
  59. package/src/internals/GroupTransportState.ts +1 -1
  60. package/src/internals/consts.ts +1 -1
  61. package/src/internals/index.ts +1 -1
  62. package/src/test/GroupPlaybackPosition.spec.ts +1 -1
  63. package/src/test/GroupPlaybackTrack.spec.ts +1 -1
  64. package/src/test/SharedMediaSession.spec.ts +1 -1
  65. package/src/test/SharedMediaSessionCoordinator.spec.ts +1 -1
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
3
  * Copyright (c) Microsoft Corporation. All rights reserved.
4
- * Licensed under the MIT License.
4
+ * Licensed under the Microsoft Live Share SDK License.
5
5
  */
6
6
  var __importDefault = (this && this.__importDefault) || function (mod) {
7
7
  return (mod && mod.__esModule) ? mod : { "default": mod };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
- * Licensed under the MIT License.
3
+ * Licensed under the Microsoft Live Share SDK License.
4
4
  */
5
5
  /**
6
6
  * List of telemetry events. Wrap with a call to transmit() if the event should be transmitted to
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
3
  * Copyright (c) Microsoft Corporation. All rights reserved.
4
- * Licensed under the MIT License.
4
+ * Licensed under the Microsoft Live Share SDK License.
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.TelemetryEvents = void 0;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
- * Licensed under the MIT License.
3
+ * Licensed under the Microsoft Live Share SDK License.
4
4
  */
5
5
  export * from './consts';
6
6
  export * from './GroupCoordinatorState';
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
3
  * Copyright (c) Microsoft Corporation. All rights reserved.
4
- * Licensed under the MIT License.
4
+ * Licensed under the Microsoft Live Share SDK License.
5
5
  */
6
6
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
7
  if (k2 === undefined) k2 = k;
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@microsoft/live-share-media",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Live Share classes that synchronize media playback across clients.",
5
5
  "author": "Microsoft",
6
- "license": "Microsoft",
6
+ "license": "SEE LICENSE IN LICENSE",
7
7
  "main": "./bin/index.js",
8
8
  "types": "./bin/index.d.ts",
9
9
  "scripts": {
@@ -14,8 +14,8 @@
14
14
  "test:coverage": "nyc --reporter=html --reporter=text --reporter=text-summary npm test"
15
15
  },
16
16
  "dependencies": {
17
- "fluid-framework": "^0.58.2000",
18
- "@microsoft/live-share": "0.1.2"
17
+ "fluid-framework": "~0.59.0",
18
+ "@microsoft/live-share": "0.2.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/assert": "^1.5.6",
@@ -25,5 +25,14 @@
25
25
  "nyc": "^15.1.0",
26
26
  "ts-mocha": "^9.0.2",
27
27
  "typescript": "^4.4.2"
28
+ },
29
+ "repository": {
30
+ "directory": "packages/live-share-media",
31
+ "type": "git",
32
+ "url": "https://github.com/microsoft/live-share-sdk"
33
+ },
34
+ "homepage": "https://aka.ms/teamsliveshare",
35
+ "bugs": {
36
+ "url": "https://github.com/microsoft/live-share-sdk/issues"
28
37
  }
29
38
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
- * Licensed under the MIT License.
3
+ * Licensed under the Microsoft Live Share SDK License.
4
4
  */
5
5
 
6
6
  import { DataObject, DataObjectFactory } from '@fluidframework/aqueduct';
@@ -13,10 +13,13 @@ import { MediaSessionActionThrottler } from './MediaSessionActionThrottler';
13
13
  import { RepeatedActionThrottler } from './RepeatedActionThrottler';
14
14
  import { IMediaPlayer } from './IMediaPlayer';
15
15
 
16
- export class EphemeralMediaSession extends DataObject {
16
+ /**
17
+ * Ephemeral fluid object that synchronizes media playback across multiple clients.
18
+ */
19
+ export class EphemeralMediaSession extends DataObject {
17
20
  private _actionThrottler: MediaSessionActionThrottler = new RepeatedActionThrottler();
18
21
  private _logger?: EphemeralTelemetryLogger;
19
- private _onRequestPlayerState?: () => IMediaPlayerState;
22
+ private _requestPlayerStateHandler?: () => IMediaPlayerState;
20
23
  private _coordinator?: EphemeralMediaSessionCoordinator;
21
24
  private readonly _actionHandlers: Map<string, MediaSessionActionHandler> = new Map();
22
25
  private _synchronizing?: MediaPlayerSynchronizer;
@@ -40,45 +43,19 @@ export class EphemeralMediaSession extends DataObject {
40
43
  {}
41
44
  );
42
45
 
43
- protected async hasInitialized(): Promise<void> {
44
- this. _logger = new EphemeralTelemetryLogger(this.runtime);
45
-
46
- // Create coordinator and listen for triggered actions
47
- this._coordinator = new EphemeralMediaSessionCoordinator(this.runtime, () => this.getCurrentPlayerState());
48
- this._coordinator.on(MediaSessionCoordinatorEvents.triggeraction, (event: ITriggerActionEvent) => {
49
- // Pre-process actions
50
- const details = event.details;
51
- const seekTime = typeof details.seekTime == 'number' ? details.seekTime : -1;
52
- switch (details.action) {
53
- case 'play':
54
- this.logger.sendTelemetryEvent(TelemetryEvents.MediaSession.PlayAction, null, {position: seekTime});
55
- break;
56
- case 'pause':
57
- this.logger.sendTelemetryEvent(TelemetryEvents.MediaSession.PauseAction, null, {position: seekTime});
58
- break;
59
- case 'seekto':
60
- this.logger.sendTelemetryEvent(TelemetryEvents.MediaSession.SeekToAction, null, {position: seekTime});
61
- break;
62
- case 'settrack':
63
- this.logger.sendTelemetryEvent(TelemetryEvents.MediaSession.SetTrackAction);
64
- break;
65
- case 'datachange':
66
- this.logger.sendTelemetryEvent(TelemetryEvents.MediaSession.DataChangeAction);
67
- break;
68
- case 'catchup':
69
- // Default to seekto if catchup not implemented
70
- this.logger.sendTelemetryEvent(TelemetryEvents.MediaSession.CatchUpAction, null, {position: seekTime});
71
- if (!this._actionHandlers.has('catchup')) {
72
- details.action = 'seekto';
73
- }
74
- break;
75
- }
76
-
77
- // Dispatch action
78
- this.dispatchAction(details);
79
- });
80
- }
81
-
46
+ /**
47
+ * Extension point that lets applications replace the default logic for throttling the sessions
48
+ * local sync behavior.
49
+ *
50
+ * @remarks
51
+ * The `EphemeralMediaCoordinator` is fairly aggressive at wanting to keep the local media player
52
+ * in sync with the rest of the group. This aggressiveness can result in the coordinator sending
53
+ * new sync actions before the local player has finished responding to the previous sync action.
54
+ * The `ActionThrottler` gives apps fine grain control over how aggressive they want sync to be.
55
+ *
56
+ * By default, a `RepeatedAction` throttler is used which prevents the same sync action from
57
+ * being sent within an adjustable time period.
58
+ */
82
59
  public get actionThrottler(): MediaSessionActionThrottler {
83
60
  return this._actionThrottler;
84
61
  }
@@ -87,26 +64,44 @@ export class EphemeralMediaSession extends DataObject {
87
64
  this._actionThrottler = value;
88
65
  }
89
66
 
67
+ /**
68
+ * Returns true if the object has been started.
69
+ */
90
70
  public get isStarted(): boolean {
91
71
  return this.coordinator.isStarted;
92
72
  }
93
73
 
74
+ /**
75
+ * The group coordinator for the session.
76
+ */
94
77
  public get coordinator(): EphemeralMediaSessionCoordinator {
95
78
  return this._coordinator!;
96
79
  }
97
80
 
98
- public set onRequestPlayerState(handler: () => IMediaPlayerState) {
99
- this._onRequestPlayerState = handler;
100
- }
101
-
81
+ /**
82
+ * Returns the logger used by the session and coordinator.
83
+ *
84
+ * @remarks
85
+ * This is used by the `MediaPlayerSynchronizer` to log events.
86
+ */
102
87
  public get logger(): EphemeralTelemetryLogger {
103
88
  return this._logger!;
104
89
  }
105
90
 
91
+ /**
92
+ * Starts the object.
93
+ * @param acceptTransportChangesFrom Optional. List of roles allowed to group transport
94
+ * operations like play/pause/seek/setTrack.
95
+ */
106
96
  public async start(acceptTransportChangesFrom?: UserMeetingRole[]): Promise<void> {
107
97
  this.coordinator.start(acceptTransportChangesFrom);
108
98
  }
109
99
 
100
+ /**
101
+ * Registers an action handler with the session.
102
+ * @param action Name of the action to register a handler for.
103
+ * @param handler Function called when the action is triggered.
104
+ */
110
105
  public setActionHandler(action: ExtendedMediaSessionAction, handler: MediaSessionActionHandler | null): void {
111
106
  if (handler) {
112
107
  // add handler
@@ -118,6 +113,19 @@ export class EphemeralMediaSession extends DataObject {
118
113
  }
119
114
  }
120
115
 
116
+ /**
117
+ * Registers a handler that will be queried anytime the group coordinate needs to know the
118
+ * local players transport state and position.
119
+ */
120
+ public setRequestPlayerStateHandler(handler: () => IMediaPlayerState) {
121
+ this._requestPlayerStateHandler = handler;
122
+ }
123
+
124
+ /**
125
+ * Begins synchronizing the playback of a media element.
126
+ * @param player Something that "looks like" and HTML Media Element.
127
+ * @returns A new synchronizer instance. Call `synchronizer.end()` to stop synchronizing the elements playback.
128
+ */
121
129
  public synchronize(player: IMediaPlayer): MediaPlayerSynchronizer {
122
130
  // A session can only synchronize one player at a time
123
131
  if (this._synchronizing) {
@@ -149,6 +157,45 @@ export class EphemeralMediaSession extends DataObject {
149
157
  });
150
158
  }
151
159
 
160
+ protected async hasInitialized(): Promise<void> {
161
+ this. _logger = new EphemeralTelemetryLogger(this.runtime);
162
+
163
+ // Create coordinator and listen for triggered actions
164
+ this._coordinator = new EphemeralMediaSessionCoordinator(this.runtime, () => this.getCurrentPlayerState());
165
+ this._coordinator.on(MediaSessionCoordinatorEvents.triggeraction, (event: ITriggerActionEvent) => {
166
+ // Pre-process actions
167
+ const details = event.details;
168
+ const seekTime = typeof details.seekTime == 'number' ? details.seekTime : -1;
169
+ switch (details.action) {
170
+ case 'play':
171
+ this.logger.sendTelemetryEvent(TelemetryEvents.MediaSession.PlayAction, null, {position: seekTime});
172
+ break;
173
+ case 'pause':
174
+ this.logger.sendTelemetryEvent(TelemetryEvents.MediaSession.PauseAction, null, {position: seekTime});
175
+ break;
176
+ case 'seekto':
177
+ this.logger.sendTelemetryEvent(TelemetryEvents.MediaSession.SeekToAction, null, {position: seekTime});
178
+ break;
179
+ case 'settrack':
180
+ this.logger.sendTelemetryEvent(TelemetryEvents.MediaSession.SetTrackAction);
181
+ break;
182
+ case 'datachange':
183
+ this.logger.sendTelemetryEvent(TelemetryEvents.MediaSession.DataChangeAction);
184
+ break;
185
+ case 'catchup':
186
+ // Default to seekto if catchup not implemented
187
+ this.logger.sendTelemetryEvent(TelemetryEvents.MediaSession.CatchUpAction, null, {position: seekTime});
188
+ if (!this._actionHandlers.has('catchup')) {
189
+ details.action = 'seekto';
190
+ }
191
+ break;
192
+ }
193
+
194
+ // Dispatch action
195
+ this.dispatchAction(details);
196
+ });
197
+ }
198
+
152
199
  private checkWaitPointHit(state: IMediaPlayerState): void {
153
200
  // Was a wait point hit?
154
201
  if (state.positionState && this.coordinator.isStarted) {
@@ -167,11 +214,11 @@ export class EphemeralMediaSession extends DataObject {
167
214
  }
168
215
 
169
216
  private getCurrentPlayerState(): IMediaPlayerState {
170
- if (!this._onRequestPlayerState) {
217
+ if (!this._requestPlayerStateHandler) {
171
218
  throw new Error(`SharedMediaSession: no getPlayerState callback configured.`);
172
219
  }
173
220
 
174
- return this._onRequestPlayerState();
221
+ return this._requestPlayerStateHandler();
175
222
  }
176
223
 
177
224
  private dispatchAction(details: MediaSessionActionDetails|ExtendedMediaSessionActionDetails): void {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
- * Licensed under the MIT License.
3
+ * Licensed under the Microsoft Live Share SDK License.
4
4
  */
5
5
 
6
6
  import { EphemeralEventScope, EphemeralTelemetryLogger, EphemeralEventTarget, IEphemeralEvent, IRuntimeSignaler, TimeInterval, UserMeetingRole } from '@microsoft/live-share';
@@ -13,14 +13,34 @@ import EventEmitter from "events";
13
13
  * Most recent state of the media session.
14
14
  */
15
15
  export interface IMediaPlayerState {
16
+ /**
17
+ * Metadata for the sessions current track.
18
+ */
16
19
  metadata: ExtendedMediaMetadata|null;
20
+
21
+ /**
22
+ * Optional track data object being synchronized.
23
+ *
24
+ * @remarks
25
+ * This can be used to sync things like pitch, roll, and yaw when watching 360 videos together.
26
+ */
17
27
  trackData: object|null;
28
+
29
+ /**
30
+ * Sessions current playback state.
31
+ */
18
32
  playbackState: ExtendedMediaSessionPlaybackState;
33
+
34
+ /**
35
+ * Sessions current position state if known.
36
+ */
19
37
  positionState?: MediaPositionState;
20
38
  }
21
39
 
22
40
  /**
23
- * Implements MediaSessionCoordinator interface
41
+ * The `EphemeralMediaSessionCoordinator` tracks the playback & position state of all other
42
+ * clients being synchronized with. It is responsible for keeping the local media player
43
+ * in sync with the group.
24
44
  */
25
45
  export class EphemeralMediaSessionCoordinator extends EventEmitter {
26
46
  private readonly _runtime: IRuntimeSignaler;
@@ -43,6 +63,10 @@ export class EphemeralMediaSessionCoordinator extends EventEmitter {
43
63
  // Distributed state
44
64
  private _groupState?: GroupCoordinatorState;
45
65
 
66
+ /**
67
+ * @hidden
68
+ * Applications shouldn't directly create new coordinator instances.
69
+ */
46
70
  constructor(runtime: IRuntimeSignaler, getPlayerState: () => IMediaPlayerState) {
47
71
  super();
48
72
  this._runtime = runtime;
@@ -50,22 +74,77 @@ export class EphemeralMediaSessionCoordinator extends EventEmitter {
50
74
  this._getPlayerState = getPlayerState;
51
75
  }
52
76
 
53
- public get isStarted(): boolean {
54
- return this._hasStarted;
55
- }
56
77
 
78
+ /**
79
+ * Controls whether or not the local client is allowed to instruct the group to play or pause.
80
+ *
81
+ * @remarks
82
+ * This flag largely meant to influence decisions made by the coordinator and can be used by
83
+ * the UI to determine what controls should be shown to the user. It does not provide any
84
+ * security in itself.
85
+ *
86
+ * If your app is running in a semi-trusted environment where only some clients are allowed
87
+ * to play/pause media, you should use "role based verification" to enforce those policies.
88
+ */
57
89
  public canPlayPause: boolean = true;
58
90
 
91
+ /**
92
+ * Controls whether or not the local client is allowed to seek the group to a new playback
93
+ * position.
94
+ *
95
+ * @remarks
96
+ * This flag largely meant to influence decisions made by the coordinator and can be used by
97
+ * the UI to determine what controls should be shown to the user. It does not provide any
98
+ * security in itself.
99
+ *
100
+ * If your app is running in a semi-trusted environment where only some clients are allowed
101
+ * to change the playback position, you should use "role based verification" to enforce those policies.
102
+ */
59
103
  public canSeek: boolean = true;
60
104
 
105
+ /**
106
+ * Controls whether or not the local client is allowed to change tracks.
107
+ *
108
+ * @remarks
109
+ * This flag largely meant to influence decisions made by the coordinator and can be used by
110
+ * the UI to determine what controls should be shown to the user. It does not provide any
111
+ * security in itself.
112
+ *
113
+ * If your app is running in a semi-trusted environment where only some clients are allowed
114
+ * to change tracks, you should use "role based verification" to enforce those policies.
115
+ */
61
116
  public canSetTrack: boolean = true;
62
117
 
118
+ /**
119
+ * Controls whether or not the local client is allowed to change the tracks custom data object.
120
+ *
121
+ * @remarks
122
+ * This flag largely meant to influence decisions made by the coordinator and can be used by
123
+ * the UI to determine what controls should be shown to the user. It does not provide any
124
+ * security in itself.
125
+ *
126
+ * If your app is running in a semi-trusted environment where only some clients are allowed
127
+ * to change the tracks data object, you should use "role based verification" to enforce those
128
+ * policies.
129
+ */
63
130
  public canSetTrackData: boolean = true;
64
131
 
132
+ /**
133
+ * Returns true if the local client is in a suspended state.
134
+ */
65
135
  public get isSuspended(): boolean {
66
136
  return this._groupState ? this._groupState.isSuspended : false;
67
137
  }
68
138
 
139
+ /**
140
+ * Max amount of playback drift allowed in seconds.
141
+ *
142
+ * @remarks
143
+ * Should the local clients playback position lag by more than the specified value, the
144
+ * coordinator will trigger a `catchup` action.
145
+ *
146
+ * Defaults to a value of `1` second.
147
+ */
69
148
  public get maxPlaybackDrift(): number {
70
149
  return this._maxPlaybackDrift.seconds;
71
150
  }
@@ -74,6 +153,13 @@ export class EphemeralMediaSessionCoordinator extends EventEmitter {
74
153
  this._maxPlaybackDrift.seconds = value;
75
154
  }
76
155
 
156
+ /**
157
+ * Frequency with which position updates are broadcast to the rest of the group in
158
+ * seconds.
159
+ *
160
+ * @remarks
161
+ * Defaults to a value of `2` seconds.
162
+ */
77
163
  public get positionUpdateInterval(): number {
78
164
  return this._positionUpdateInterval.seconds;
79
165
  }
@@ -82,10 +168,13 @@ export class EphemeralMediaSessionCoordinator extends EventEmitter {
82
168
  this._positionUpdateInterval.seconds = value;
83
169
  }
84
170
 
85
- public findNextWaitPoint(): CoordinationWaitPoint|null {
86
- return this._groupState?.playbackTrack.findNextWaitPoint(this._lastWaitPoint) || null;
87
- }
88
-
171
+ /**
172
+ * Instructs the group to play the current track.
173
+ *
174
+ * @remarks
175
+ * Throws an exception if the session/coordinator hasn't been started, no track has been
176
+ * loaded, or `canPlayPause` is false.
177
+ */
89
178
  public play(): void {
90
179
  if (!this._hasStarted) {
91
180
  throw new Error(`EphemeralMediaSessionCoordinator.play() called before start() called.`);
@@ -110,6 +199,13 @@ export class EphemeralMediaSessionCoordinator extends EventEmitter {
110
199
  });
111
200
  }
112
201
 
202
+ /**
203
+ * Instructs the group to pause the current track.
204
+ *
205
+ * @remarks
206
+ * Throws an exception if the session/coordinator hasn't been started, no track has been
207
+ * loaded, or `canPlayPause` is false.
208
+ */
113
209
  public pause(): void {
114
210
  if (!this._hasStarted) {
115
211
  throw new Error(`EphemeralMediaSessionCoordinator.pause() called before start() called.`);
@@ -134,6 +230,14 @@ export class EphemeralMediaSessionCoordinator extends EventEmitter {
134
230
  });
135
231
  }
136
232
 
233
+ /**
234
+ * Instructs the group to seek to a new position within the current track.
235
+ *
236
+ * @remarks
237
+ * Throws an exception if the session/coordinator hasn't been started, no track has been
238
+ * loaded, or `canSeek` is false.
239
+ * @param time Playback position in seconds to seek to.
240
+ */
137
241
  public seekTo(time: number): void {
138
242
  if (!this._hasStarted) {
139
243
  throw new Error(`EphemeralMediaSessionCoordinator.seekTo() called before start() called.`);
@@ -155,6 +259,15 @@ export class EphemeralMediaSessionCoordinator extends EventEmitter {
155
259
  });
156
260
  }
157
261
 
262
+ /**
263
+ * Instructs the group to load a new track.
264
+ *
265
+ * @remarks
266
+ * Throws an exception if the session/coordinator hasn't been started or `canSetTrack` is
267
+ * false.
268
+ * @param metadata The track to load or `null` to indicate that the end of the track is reached.
269
+ * @param waitPoints Optional. List of static wait points to configure for the track. Dynamic wait points can be added via the `beginSuspension()` call.
270
+ */
158
271
  public setTrack(metadata: ExtendedMediaMetadata|null, waitPoints?: CoordinationWaitPoint[]): void {
159
272
  if (!this._hasStarted) {
160
273
  throw new Error(`EphemeralMediaSessionCoordinator.setTrack() called before start() called.`);
@@ -172,7 +285,18 @@ export class EphemeralMediaSessionCoordinator extends EventEmitter {
172
285
  });
173
286
  }
174
287
 
175
- public setTrackData(data: object|null): void {
288
+ /**
289
+ * Updates the track data object for the current track.
290
+ *
291
+ * @remarks
292
+ * The track data object can be used by applications to synchronize things like pitch, roll,
293
+ * and yaw of a 360 video. This data object will be reset to null anytime the track changes.
294
+ *
295
+ * Throws an exception if the session/coordinator hasn't been started or `canSetTrackData` is
296
+ * false.
297
+ * @param data New data object to sync with the group. This value will be synchronized using a last writer wins strategy.
298
+ */
299
+ public setTrackData(data: object|null): void {
176
300
  if (!this._hasStarted) {
177
301
  throw new Error(`EphemeralMediaSessionCoordinator.setTrackData() called before start() called.`);
178
302
  }
@@ -188,6 +312,30 @@ export class EphemeralMediaSessionCoordinator extends EventEmitter {
188
312
  });
189
313
  }
190
314
 
315
+ /**
316
+ * Begins a new local suspension.
317
+ *
318
+ * @remarks
319
+ * Suspension temporarily suspend the clients local synchronization with the group. This can
320
+ * be useful for displaying ads to users or temporarily disconnecting from the session while
321
+ * the user seeks the video using a timeline scrubber.
322
+ *
323
+ * Multiple simultaneous suspensions are allowed and when the last suspension ends the local
324
+ * client will be immediately re-synchronized with the group.
325
+ *
326
+ * A "Dynamic Wait Point" can be specified when `beginSuspension()` is called and the wait
327
+ * point will be broadcast to all other clients in the group. Those clients will then
328
+ * automatically enter a suspension state once they reach the positions specified by the
329
+ * wait point. Clients that are passed the wait point will immediately suspend.
330
+ *
331
+ * Any wait point based suspension (dynamic or static) will result in all clients remaining
332
+ * in a suspension state until the list client ends their suspension. This behavior can be
333
+ * conditionally bypassed by settings the wait points `maxClients` value.
334
+ *
335
+ * Throws an exception if the session/coordinator hasn't been started.
336
+ * @param waitPoint Optional. Dynamic wait point to broadcast to all of the clients.
337
+ * @returns The suspension object. Call `end()` on the returned suspension to end the suspension.
338
+ */
191
339
  public beginSuspension(waitPoint?: CoordinationWaitPoint): MediaSessionCoordinatorSuspension {
192
340
  if (!this._hasStarted) {
193
341
  throw new Error(`EphemeralMediaSessionCoordinator.beginSuspension() called before start() called.`);
@@ -222,24 +370,18 @@ export class EphemeralMediaSessionCoordinator extends EventEmitter {
222
370
  });
223
371
  }
224
372
 
225
- public sendPositionUpdate(state: IMediaPlayerState): void {
226
- if (!this._hasStarted) {
227
- throw new Error(`EphemeralMediaSessionCoordinator.sendPositionUpdate() called before start() called.`);
228
- }
229
-
230
- // Send position update event
231
- const evt = this._groupState!.createPositionUpdateEvent(state);
232
- this._positionUpdateEvent?.sendEvent(evt);
233
- }
234
-
235
- public syncLocalMediaSession(): void {
236
- if (!this._hasStarted) {
237
- throw new Error(`EphemeralMediaSessionCoordinator.syncLocalMediaSession() called before start() called.`);
238
- }
239
-
240
- this._groupState!.syncLocalMediaSession();
373
+ /**
374
+ * @hidden
375
+ * Called by MediaSession to verify the coordinator has been started.
376
+ */
377
+ public get isStarted(): boolean {
378
+ return this._hasStarted;
241
379
  }
242
380
 
381
+ /**
382
+ * @hidden
383
+ * Called by MediaSession to start coordinator.
384
+ */
243
385
  public async start(acceptTransportChangesFrom?: UserMeetingRole[]): Promise<void> {
244
386
  if (this._hasStarted) {
245
387
  throw new Error(`EphemeralMediaSessionCoordinator.start() already started.`);
@@ -249,6 +391,28 @@ export class EphemeralMediaSessionCoordinator extends EventEmitter {
249
391
  await this.createChildren(acceptTransportChangesFrom);
250
392
  this._hasStarted = true;
251
393
  }
394
+
395
+ /**
396
+ * @hidden
397
+ * Called by the MediaSession to detect if a wait point has been hit.
398
+ */
399
+ public findNextWaitPoint(): CoordinationWaitPoint|null {
400
+ return this._groupState?.playbackTrack.findNextWaitPoint(this._lastWaitPoint) || null;
401
+ }
402
+
403
+ /**
404
+ * @hidden
405
+ * Called by MediaSession to trigger the sending of a position update.
406
+ */
407
+ public sendPositionUpdate(state: IMediaPlayerState): void {
408
+ if (!this._hasStarted) {
409
+ throw new Error(`EphemeralMediaSessionCoordinator.sendPositionUpdate() called before start() called.`);
410
+ }
411
+
412
+ // Send position update event
413
+ const evt = this._groupState!.createPositionUpdateEvent(state);
414
+ this._positionUpdateEvent?.sendEvent(evt);
415
+ }
252
416
 
253
417
  protected async createChildren(acceptTransportChangesFrom?: UserMeetingRole[]): Promise<void> {
254
418
  // Create event scopes
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
- * Licensed under the MIT License.
3
+ * Licensed under the Microsoft Live Share SDK License.
4
4
  */
5
5
 
6
6
  import { MediaSessionCoordinatorSuspension, CoordinationWaitPoint } from './MediaSessionExtensions';
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
- * Licensed under the MIT License.
3
+ * Licensed under the Microsoft Live Share SDK License.
4
4
  */
5
5
 
6
6
  export interface IMediaPlayer {