@microsoft/live-share-media 1.0.0-preview.1 → 1.0.0-preview.4
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.
- package/README.md +2 -1
- package/bin/IMediaPlayer.d.ts.map +1 -1
- package/bin/LiveMediaSession.d.ts +7 -7
- package/bin/LiveMediaSession.d.ts.map +1 -1
- package/bin/LiveMediaSession.js +28 -17
- package/bin/LiveMediaSession.js.map +1 -1
- package/bin/LiveMediaSessionCoordinator.d.ts +2 -2
- package/bin/LiveMediaSessionCoordinator.d.ts.map +1 -1
- package/bin/LiveMediaSessionCoordinator.js +34 -22
- package/bin/LiveMediaSessionCoordinator.js.map +1 -1
- package/bin/LiveMediaSessionCoordinatorSuspension.d.ts +1 -1
- package/bin/LiveMediaSessionCoordinatorSuspension.d.ts.map +1 -1
- package/bin/LiveMediaSessionCoordinatorSuspension.js.map +1 -1
- package/bin/MediaPlayerSynchronizer.d.ts +7 -7
- package/bin/MediaPlayerSynchronizer.d.ts.map +1 -1
- package/bin/MediaPlayerSynchronizer.js +127 -72
- package/bin/MediaPlayerSynchronizer.js.map +1 -1
- package/bin/MediaSessionActionThrottler.d.ts +1 -1
- package/bin/MediaSessionActionThrottler.d.ts.map +1 -1
- package/bin/MediaSessionActionThrottler.js.map +1 -1
- package/bin/MediaSessionExtensions.d.ts.map +1 -1
- package/bin/RepeatedActionThrottler.d.ts +2 -2
- package/bin/RepeatedActionThrottler.d.ts.map +1 -1
- package/bin/RepeatedActionThrottler.js +9 -3
- package/bin/RepeatedActionThrottler.js.map +1 -1
- package/bin/VolumeManager.d.ts +1 -1
- package/bin/VolumeManager.d.ts.map +1 -1
- package/bin/VolumeManager.js +4 -2
- package/bin/VolumeManager.js.map +1 -1
- package/bin/index.d.ts +9 -9
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js.map +1 -1
- package/bin/internals/GroupCoordinatorState.d.ts +8 -8
- package/bin/internals/GroupCoordinatorState.d.ts.map +1 -1
- package/bin/internals/GroupCoordinatorState.js +91 -53
- package/bin/internals/GroupCoordinatorState.js.map +1 -1
- package/bin/internals/GroupPlaybackPosition.d.ts +3 -3
- package/bin/internals/GroupPlaybackPosition.d.ts.map +1 -1
- package/bin/internals/GroupPlaybackPosition.js +22 -13
- package/bin/internals/GroupPlaybackPosition.js.map +1 -1
- package/bin/internals/GroupPlaybackTrack.d.ts +4 -4
- package/bin/internals/GroupPlaybackTrack.d.ts.map +1 -1
- package/bin/internals/GroupPlaybackTrack.js +14 -4
- package/bin/internals/GroupPlaybackTrack.js.map +1 -1
- package/bin/internals/GroupPlaybackTrackData.d.ts +3 -3
- package/bin/internals/GroupPlaybackTrackData.d.ts.map +1 -1
- package/bin/internals/GroupPlaybackTrackData.js +8 -4
- package/bin/internals/GroupPlaybackTrackData.js.map +1 -1
- package/bin/internals/GroupTransportState.d.ts +5 -5
- package/bin/internals/GroupTransportState.d.ts.map +1 -1
- package/bin/internals/GroupTransportState.js +27 -12
- package/bin/internals/GroupTransportState.js.map +1 -1
- package/bin/internals/consts.d.ts.map +1 -1
- package/bin/internals/consts.js +54 -54
- package/bin/internals/consts.js.map +1 -1
- package/bin/internals/index.d.ts +6 -6
- package/package.json +3 -3
- package/src/IMediaPlayer.ts +10 -2
- package/src/LiveMediaSession.ts +133 -71
- package/src/LiveMediaSessionCoordinator.ts +302 -142
- package/src/LiveMediaSessionCoordinatorSuspension.ts +13 -5
- package/src/MediaPlayerSynchronizer.ts +329 -149
- package/src/MediaSessionActionThrottler.ts +5 -3
- package/src/MediaSessionExtensions.ts +15 -5
- package/src/RepeatedActionThrottler.ts +27 -9
- package/src/VolumeManager.ts +24 -12
- package/src/index.ts +9 -10
- package/src/internals/GroupCoordinatorState.ts +274 -114
- package/src/internals/GroupPlaybackPosition.ts +61 -27
- package/src/internals/GroupPlaybackTrack.ts +51 -22
- package/src/internals/GroupPlaybackTrackData.ts +24 -16
- package/src/internals/GroupTransportState.ts +53 -23
- package/src/internals/consts.ts +73 -55
- package/src/internals/index.ts +6 -6
- package/src/test/GroupPlaybackPosition.spec.ts +371 -111
- package/src/test/GroupPlaybackTrack.spec.ts +91 -35
- package/src/test/SharedMediaSession.spec.ts +64 -28
- package/src/test/SharedMediaSessionCoordinator.spec.ts +86 -46
- package/src/test/VolumeManager.spec.ts +38 -31
- package/tsconfig.json +1 -0
- package/bin/EphemeralMediaSession.d.ts +0 -102
- package/bin/EphemeralMediaSession.d.ts.map +0 -1
- package/bin/EphemeralMediaSession.js +0 -226
- package/bin/EphemeralMediaSession.js.map +0 -1
- package/bin/EphemeralMediaSessionCoordinator.d.ts +0 -228
- package/bin/EphemeralMediaSessionCoordinator.d.ts.map +0 -1
- package/bin/EphemeralMediaSessionCoordinator.js +0 -396
- package/bin/EphemeralMediaSessionCoordinator.js.map +0 -1
- package/bin/EphemeralMediaSessionCoordinatorSuspension.d.ts +0 -20
- package/bin/EphemeralMediaSessionCoordinatorSuspension.d.ts.map +0 -1
- package/bin/EphemeralMediaSessionCoordinatorSuspension.js +0 -28
- package/bin/EphemeralMediaSessionCoordinatorSuspension.js.map +0 -1
- package/bin/VolumeLimiter.d.ts +0 -70
- package/bin/VolumeLimiter.d.ts.map +0 -1
- package/bin/VolumeLimiter.js +0 -170
- package/bin/VolumeLimiter.js.map +0 -1
package/src/LiveMediaSession.ts
CHANGED
|
@@ -3,40 +3,49 @@
|
|
|
3
3
|
* Licensed under the Microsoft Live Share SDK License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { DataObject, DataObjectFactory } from
|
|
7
|
-
import { LiveTelemetryLogger, UserMeetingRole } from
|
|
8
|
-
import { MediaPlayerSynchronizer } from
|
|
9
|
-
import { ITriggerActionEvent, TelemetryEvents } from
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
import { DataObject, DataObjectFactory } from "@fluidframework/aqueduct";
|
|
7
|
+
import { LiveTelemetryLogger, UserMeetingRole } from "@microsoft/live-share";
|
|
8
|
+
import { MediaPlayerSynchronizer } from "./MediaPlayerSynchronizer";
|
|
9
|
+
import { ITriggerActionEvent, TelemetryEvents } from "./internals";
|
|
10
|
+
import {
|
|
11
|
+
MediaSessionCoordinatorEvents,
|
|
12
|
+
ExtendedMediaSessionAction,
|
|
13
|
+
ExtendedMediaSessionActionDetails,
|
|
14
|
+
} from "./MediaSessionExtensions";
|
|
15
|
+
import {
|
|
16
|
+
LiveMediaSessionCoordinator,
|
|
17
|
+
IMediaPlayerState,
|
|
18
|
+
} from "./LiveMediaSessionCoordinator";
|
|
19
|
+
import { MediaSessionActionThrottler } from "./MediaSessionActionThrottler";
|
|
20
|
+
import { RepeatedActionThrottler } from "./RepeatedActionThrottler";
|
|
21
|
+
import { IMediaPlayer } from "./IMediaPlayer";
|
|
15
22
|
|
|
16
23
|
/**
|
|
17
24
|
* Live fluid object that synchronizes media playback across multiple clients.
|
|
18
25
|
*/
|
|
19
|
-
|
|
20
|
-
private _actionThrottler: MediaSessionActionThrottler =
|
|
26
|
+
export class LiveMediaSession extends DataObject {
|
|
27
|
+
private _actionThrottler: MediaSessionActionThrottler =
|
|
28
|
+
new RepeatedActionThrottler();
|
|
21
29
|
private _logger?: LiveTelemetryLogger;
|
|
22
30
|
private _requestPlayerStateHandler?: () => IMediaPlayerState;
|
|
23
31
|
private _coordinator?: LiveMediaSessionCoordinator;
|
|
24
|
-
private readonly _actionHandlers: Map<string, MediaSessionActionHandler> =
|
|
32
|
+
private readonly _actionHandlers: Map<string, MediaSessionActionHandler> =
|
|
33
|
+
new Map();
|
|
25
34
|
private _synchronizing?: MediaPlayerSynchronizer;
|
|
26
35
|
|
|
27
36
|
// Position update timer
|
|
28
37
|
private _updateTimer: any;
|
|
29
38
|
private _lastUpdateTime: number = 0;
|
|
30
|
-
|
|
39
|
+
|
|
31
40
|
/**
|
|
32
41
|
* The objects fluid type name.
|
|
33
42
|
*/
|
|
34
|
-
|
|
43
|
+
public static readonly TypeName = `@microsoft/fluid-media:SharedMediaSession`;
|
|
35
44
|
|
|
36
45
|
/**
|
|
37
46
|
* The objects fluid type factory.
|
|
38
47
|
*/
|
|
39
|
-
|
|
48
|
+
public static readonly factory = new DataObjectFactory(
|
|
40
49
|
LiveMediaSession.TypeName,
|
|
41
50
|
LiveMediaSession,
|
|
42
51
|
[],
|
|
@@ -46,15 +55,15 @@ import { IMediaPlayer } from './IMediaPlayer';
|
|
|
46
55
|
/**
|
|
47
56
|
* Extension point that lets applications replace the default logic for throttling the sessions
|
|
48
57
|
* local sync behavior.
|
|
49
|
-
*
|
|
58
|
+
*
|
|
50
59
|
* @remarks
|
|
51
|
-
* The `LiveMediaCoordinator` 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.
|
|
60
|
+
* The `LiveMediaCoordinator` is fairly aggressive at wanting to keep the local media player
|
|
61
|
+
* in sync with the rest of the group. This aggressiveness can result in the coordinator sending
|
|
62
|
+
* new sync actions before the local player has finished responding to the previous sync action.
|
|
54
63
|
* 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.
|
|
64
|
+
*
|
|
65
|
+
* By default, a `RepeatedAction` throttler is used which prevents the same sync action from
|
|
66
|
+
* being sent within an adjustable time period.
|
|
58
67
|
*/
|
|
59
68
|
public get actionThrottler(): MediaSessionActionThrottler {
|
|
60
69
|
return this._actionThrottler;
|
|
@@ -76,7 +85,7 @@ import { IMediaPlayer } from './IMediaPlayer';
|
|
|
76
85
|
* Returns true if the object has been initialized.
|
|
77
86
|
*/
|
|
78
87
|
public get isStarted(): boolean {
|
|
79
|
-
return this.isInitialized
|
|
88
|
+
return this.isInitialized;
|
|
80
89
|
}
|
|
81
90
|
|
|
82
91
|
/**
|
|
@@ -88,7 +97,7 @@ import { IMediaPlayer } from './IMediaPlayer';
|
|
|
88
97
|
|
|
89
98
|
/**
|
|
90
99
|
* Returns the logger used by the session and coordinator.
|
|
91
|
-
*
|
|
100
|
+
*
|
|
92
101
|
* @remarks
|
|
93
102
|
* This is used by the `MediaPlayerSynchronizer` to log events.
|
|
94
103
|
*/
|
|
@@ -98,10 +107,12 @@ import { IMediaPlayer } from './IMediaPlayer';
|
|
|
98
107
|
|
|
99
108
|
/**
|
|
100
109
|
* initialize the object.
|
|
101
|
-
* @param acceptTransportChangesFrom Optional. List of roles allowed to group transport
|
|
110
|
+
* @param acceptTransportChangesFrom Optional. List of roles allowed to group transport
|
|
102
111
|
* operations like play/pause/seek/setTrack.
|
|
103
112
|
*/
|
|
104
|
-
|
|
113
|
+
public initialize(
|
|
114
|
+
acceptTransportChangesFrom?: UserMeetingRole[]
|
|
115
|
+
): Promise<void> {
|
|
105
116
|
return this.coordinator.initialize(acceptTransportChangesFrom);
|
|
106
117
|
}
|
|
107
118
|
|
|
@@ -110,11 +121,13 @@ import { IMediaPlayer } from './IMediaPlayer';
|
|
|
110
121
|
* @param action Name of the action to register a handler for.
|
|
111
122
|
* @param handler Function called when the action is triggered.
|
|
112
123
|
*/
|
|
113
|
-
public setActionHandler(
|
|
124
|
+
public setActionHandler(
|
|
125
|
+
action: ExtendedMediaSessionAction,
|
|
126
|
+
handler: MediaSessionActionHandler | null
|
|
127
|
+
): void {
|
|
114
128
|
if (handler) {
|
|
115
129
|
// add handler
|
|
116
130
|
this._actionHandlers.set(action, handler);
|
|
117
|
-
|
|
118
131
|
} else if (this._actionHandlers.has(action)) {
|
|
119
132
|
// remove handler
|
|
120
133
|
this._actionHandlers.delete(action);
|
|
@@ -122,8 +135,8 @@ import { IMediaPlayer } from './IMediaPlayer';
|
|
|
122
135
|
}
|
|
123
136
|
|
|
124
137
|
/**
|
|
125
|
-
* Registers a handler that will be queried anytime the group coordinate needs to know the
|
|
126
|
-
* local players transport state and position.
|
|
138
|
+
* Registers a handler that will be queried anytime the group coordinate needs to know the
|
|
139
|
+
* local players transport state and position.
|
|
127
140
|
*/
|
|
128
141
|
public setRequestPlayerStateHandler(handler: () => IMediaPlayerState) {
|
|
129
142
|
this._requestPlayerStateHandler = handler;
|
|
@@ -142,7 +155,9 @@ import { IMediaPlayer } from './IMediaPlayer';
|
|
|
142
155
|
|
|
143
156
|
// Start position update timer
|
|
144
157
|
if (!this._updateTimer) {
|
|
145
|
-
this.logger.sendTelemetryEvent(
|
|
158
|
+
this.logger.sendTelemetryEvent(
|
|
159
|
+
TelemetryEvents.MediaSession.BeginPositionUpdateTimer
|
|
160
|
+
);
|
|
146
161
|
this._updateTimer = setInterval(() => {
|
|
147
162
|
// Get current state
|
|
148
163
|
const state = this.getCurrentPlayerState();
|
|
@@ -153,7 +168,10 @@ import { IMediaPlayer } from './IMediaPlayer';
|
|
|
153
168
|
// Send position update if interval hit
|
|
154
169
|
const now = new Date().getTime();
|
|
155
170
|
const delta = (now - this._lastUpdateTime) / 1000;
|
|
156
|
-
if (
|
|
171
|
+
if (
|
|
172
|
+
this.coordinator.isInitialized &&
|
|
173
|
+
delta >= this.coordinator.positionUpdateInterval
|
|
174
|
+
) {
|
|
157
175
|
this._lastUpdateTime = now;
|
|
158
176
|
this.coordinator.sendPositionUpdate(state);
|
|
159
177
|
}
|
|
@@ -166,70 +184,114 @@ import { IMediaPlayer } from './IMediaPlayer';
|
|
|
166
184
|
}
|
|
167
185
|
|
|
168
186
|
protected async hasInitialized(): Promise<void> {
|
|
169
|
-
this.
|
|
187
|
+
this._logger = new LiveTelemetryLogger(this.runtime);
|
|
170
188
|
|
|
171
189
|
// Create coordinator and listen for triggered actions
|
|
172
|
-
this._coordinator = new LiveMediaSessionCoordinator(this.runtime, () =>
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
190
|
+
this._coordinator = new LiveMediaSessionCoordinator(this.runtime, () =>
|
|
191
|
+
this.getCurrentPlayerState()
|
|
192
|
+
);
|
|
193
|
+
this._coordinator.on(
|
|
194
|
+
MediaSessionCoordinatorEvents.triggeraction,
|
|
195
|
+
(event: ITriggerActionEvent) => {
|
|
196
|
+
// Pre-process actions
|
|
197
|
+
const details = event.details;
|
|
198
|
+
const seekTime =
|
|
199
|
+
typeof details.seekTime == "number" ? details.seekTime : -1;
|
|
200
|
+
switch (details.action) {
|
|
201
|
+
case "play":
|
|
202
|
+
this.logger.sendTelemetryEvent(
|
|
203
|
+
TelemetryEvents.MediaSession.PlayAction,
|
|
204
|
+
null,
|
|
205
|
+
{
|
|
206
|
+
position: seekTime,
|
|
207
|
+
}
|
|
208
|
+
);
|
|
209
|
+
break;
|
|
210
|
+
case "pause":
|
|
211
|
+
this.logger.sendTelemetryEvent(
|
|
212
|
+
TelemetryEvents.MediaSession.PauseAction,
|
|
213
|
+
null,
|
|
214
|
+
{
|
|
215
|
+
position: seekTime,
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
break;
|
|
219
|
+
case "seekto":
|
|
220
|
+
this.logger.sendTelemetryEvent(
|
|
221
|
+
TelemetryEvents.MediaSession.SeekToAction,
|
|
222
|
+
null,
|
|
223
|
+
{
|
|
224
|
+
position: seekTime,
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
break;
|
|
228
|
+
case "settrack":
|
|
229
|
+
this.logger.sendTelemetryEvent(
|
|
230
|
+
TelemetryEvents.MediaSession.SetTrackAction
|
|
231
|
+
);
|
|
232
|
+
break;
|
|
233
|
+
case "datachange":
|
|
234
|
+
this.logger.sendTelemetryEvent(
|
|
235
|
+
TelemetryEvents.MediaSession.DataChangeAction
|
|
236
|
+
);
|
|
237
|
+
break;
|
|
238
|
+
case "catchup":
|
|
239
|
+
// Default to seekto if catchup not implemented
|
|
240
|
+
this.logger.sendTelemetryEvent(
|
|
241
|
+
TelemetryEvents.MediaSession.CatchUpAction,
|
|
242
|
+
null,
|
|
243
|
+
{
|
|
244
|
+
position: seekTime,
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
if (!this._actionHandlers.has("catchup")) {
|
|
248
|
+
details.action = "seekto";
|
|
249
|
+
}
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
201
252
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
253
|
+
// Dispatch action
|
|
254
|
+
this.dispatchAction(details);
|
|
255
|
+
}
|
|
256
|
+
);
|
|
205
257
|
}
|
|
206
258
|
|
|
207
259
|
private checkWaitPointHit(state: IMediaPlayerState): void {
|
|
208
260
|
// Was a wait point hit?
|
|
209
261
|
if (state.positionState && this.coordinator.isInitialized) {
|
|
210
262
|
const waitPoint = this.coordinator.findNextWaitPoint();
|
|
211
|
-
if (
|
|
263
|
+
if (
|
|
264
|
+
waitPoint &&
|
|
265
|
+
state.positionState.position != undefined &&
|
|
266
|
+
state.positionState.position >= waitPoint.position
|
|
267
|
+
) {
|
|
212
268
|
// Ensure handler registered
|
|
213
|
-
if (!this._actionHandlers.has(
|
|
214
|
-
throw new Error(
|
|
269
|
+
if (!this._actionHandlers.has("wait")) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
`SharedMediaSession: wait point hit but no 'wait' action registered.`
|
|
272
|
+
);
|
|
215
273
|
}
|
|
216
274
|
|
|
217
275
|
// Begin suspension for wait point
|
|
218
276
|
const suspension = this.coordinator.beginSuspension(waitPoint);
|
|
219
|
-
this.dispatchAction({action:
|
|
277
|
+
this.dispatchAction({ action: "wait", suspension: suspension });
|
|
220
278
|
}
|
|
221
279
|
}
|
|
222
280
|
}
|
|
223
281
|
|
|
224
282
|
private getCurrentPlayerState(): IMediaPlayerState {
|
|
225
283
|
if (!this._requestPlayerStateHandler) {
|
|
226
|
-
throw new Error(
|
|
284
|
+
throw new Error(
|
|
285
|
+
`SharedMediaSession: no getPlayerState callback configured.`
|
|
286
|
+
);
|
|
227
287
|
}
|
|
228
288
|
|
|
229
289
|
return this._requestPlayerStateHandler();
|
|
230
290
|
}
|
|
231
291
|
|
|
232
|
-
private dispatchAction(
|
|
292
|
+
private dispatchAction(
|
|
293
|
+
details: MediaSessionActionDetails | ExtendedMediaSessionActionDetails
|
|
294
|
+
): void {
|
|
233
295
|
if (this._actionHandlers.has(details.action)) {
|
|
234
296
|
const handler = this._actionHandlers.get(details.action);
|
|
235
297
|
this._actionThrottler.throttled(details, handler);
|