@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
|
@@ -3,10 +3,35 @@
|
|
|
3
3
|
* Licensed under the Microsoft Live Share SDK License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
import {
|
|
7
|
+
LiveEventScope,
|
|
8
|
+
LiveTelemetryLogger,
|
|
9
|
+
LiveEventTarget,
|
|
10
|
+
ILiveEvent,
|
|
11
|
+
IRuntimeSignaler,
|
|
12
|
+
TimeInterval,
|
|
13
|
+
UserMeetingRole,
|
|
14
|
+
} from "@microsoft/live-share";
|
|
15
|
+
import {
|
|
16
|
+
CoordinationWaitPoint,
|
|
17
|
+
ExtendedMediaSessionActionDetails,
|
|
18
|
+
ExtendedMediaMetadata,
|
|
19
|
+
ExtendedMediaSessionPlaybackState,
|
|
20
|
+
MediaSessionCoordinatorEvents,
|
|
21
|
+
MediaSessionCoordinatorState,
|
|
22
|
+
MediaSessionCoordinatorSuspension,
|
|
23
|
+
} from "./MediaSessionExtensions";
|
|
24
|
+
import {
|
|
25
|
+
TelemetryEvents,
|
|
26
|
+
ITransportCommandEvent,
|
|
27
|
+
ISetTrackEvent,
|
|
28
|
+
IPositionUpdateEvent,
|
|
29
|
+
GroupCoordinatorState,
|
|
30
|
+
GroupCoordinatorStateEvents,
|
|
31
|
+
ITriggerActionEvent,
|
|
32
|
+
ISetTrackDataEvent,
|
|
33
|
+
} from "./internals";
|
|
34
|
+
import { LiveMediaSessionCoordinatorSuspension } from "./LiveMediaSessionCoordinatorSuspension";
|
|
10
35
|
import EventEmitter from "events";
|
|
11
36
|
|
|
12
37
|
/**
|
|
@@ -16,15 +41,15 @@ export interface IMediaPlayerState {
|
|
|
16
41
|
/**
|
|
17
42
|
* Metadata for the sessions current track.
|
|
18
43
|
*/
|
|
19
|
-
metadata: ExtendedMediaMetadata|null;
|
|
44
|
+
metadata: ExtendedMediaMetadata | null;
|
|
20
45
|
|
|
21
46
|
/**
|
|
22
47
|
* Optional track data object being synchronized.
|
|
23
|
-
*
|
|
48
|
+
*
|
|
24
49
|
* @remarks
|
|
25
50
|
* This can be used to sync things like pitch, roll, and yaw when watching 360 videos together.
|
|
26
51
|
*/
|
|
27
|
-
trackData: object|null;
|
|
52
|
+
trackData: object | null;
|
|
28
53
|
|
|
29
54
|
/**
|
|
30
55
|
* Sessions current playback state.
|
|
@@ -38,11 +63,11 @@ export interface IMediaPlayerState {
|
|
|
38
63
|
}
|
|
39
64
|
|
|
40
65
|
/**
|
|
41
|
-
* The `LiveMediaSessionCoordinator` tracks the playback & position state of all other
|
|
66
|
+
* The `LiveMediaSessionCoordinator` tracks the playback & position state of all other
|
|
42
67
|
* clients being synchronized with. It is responsible for keeping the local media player
|
|
43
|
-
* in sync with the group.
|
|
68
|
+
* in sync with the group.
|
|
44
69
|
*/
|
|
45
|
-
export class LiveMediaSessionCoordinator extends EventEmitter
|
|
70
|
+
export class LiveMediaSessionCoordinator extends EventEmitter {
|
|
46
71
|
private readonly _runtime: IRuntimeSignaler;
|
|
47
72
|
private readonly _logger: LiveTelemetryLogger;
|
|
48
73
|
private readonly _getPlayerState: () => IMediaPlayerState;
|
|
@@ -62,70 +87,72 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
|
|
|
62
87
|
|
|
63
88
|
// Distributed state
|
|
64
89
|
private _groupState?: GroupCoordinatorState;
|
|
65
|
-
|
|
90
|
+
|
|
66
91
|
/**
|
|
67
92
|
* @hidden
|
|
68
93
|
* Applications shouldn't directly create new coordinator instances.
|
|
69
94
|
*/
|
|
70
|
-
constructor(
|
|
95
|
+
constructor(
|
|
96
|
+
runtime: IRuntimeSignaler,
|
|
97
|
+
getPlayerState: () => IMediaPlayerState
|
|
98
|
+
) {
|
|
71
99
|
super();
|
|
72
100
|
this._runtime = runtime;
|
|
73
101
|
this._logger = new LiveTelemetryLogger(runtime);
|
|
74
102
|
this._getPlayerState = getPlayerState;
|
|
75
103
|
}
|
|
76
104
|
|
|
77
|
-
|
|
78
105
|
/**
|
|
79
106
|
* Controls whether or not the local client is allowed to instruct the group to play or pause.
|
|
80
|
-
*
|
|
107
|
+
*
|
|
81
108
|
* @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.
|
|
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 play/pause media, you should use "role based verification" to enforce those policies.
|
|
88
115
|
*/
|
|
89
116
|
public canPlayPause: boolean = true;
|
|
90
117
|
|
|
91
118
|
/**
|
|
92
|
-
* Controls whether or not the local client is allowed to seek the group to a new playback
|
|
119
|
+
* Controls whether or not the local client is allowed to seek the group to a new playback
|
|
93
120
|
* position.
|
|
94
|
-
*
|
|
121
|
+
*
|
|
95
122
|
* @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.
|
|
123
|
+
* This flag largely meant to influence decisions made by the coordinator and can be used by
|
|
124
|
+
* the UI to determine what controls should be shown to the user. It does not provide any
|
|
125
|
+
* security in itself.
|
|
126
|
+
*
|
|
127
|
+
* If your app is running in a semi-trusted environment where only some clients are allowed
|
|
128
|
+
* to change the playback position, you should use "role based verification" to enforce those policies.
|
|
102
129
|
*/
|
|
103
130
|
public canSeek: boolean = true;
|
|
104
131
|
|
|
105
132
|
/**
|
|
106
133
|
* Controls whether or not the local client is allowed to change tracks.
|
|
107
|
-
*
|
|
134
|
+
*
|
|
108
135
|
* @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.
|
|
136
|
+
* This flag largely meant to influence decisions made by the coordinator and can be used by
|
|
137
|
+
* the UI to determine what controls should be shown to the user. It does not provide any
|
|
138
|
+
* security in itself.
|
|
139
|
+
*
|
|
140
|
+
* If your app is running in a semi-trusted environment where only some clients are allowed
|
|
141
|
+
* to change tracks, you should use "role based verification" to enforce those policies.
|
|
115
142
|
*/
|
|
116
143
|
public canSetTrack: boolean = true;
|
|
117
144
|
|
|
118
145
|
/**
|
|
119
146
|
* Controls whether or not the local client is allowed to change the tracks custom data object.
|
|
120
|
-
*
|
|
147
|
+
*
|
|
121
148
|
* @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.
|
|
149
|
+
* This flag largely meant to influence decisions made by the coordinator and can be used by
|
|
150
|
+
* the UI to determine what controls should be shown to the user. It does not provide any
|
|
151
|
+
* security in itself.
|
|
152
|
+
*
|
|
153
|
+
* If your app is running in a semi-trusted environment where only some clients are allowed
|
|
154
|
+
* to change the tracks data object, you should use "role based verification" to enforce those
|
|
155
|
+
* policies.
|
|
129
156
|
*/
|
|
130
157
|
public canSetTrackData: boolean = true;
|
|
131
158
|
|
|
@@ -138,11 +165,11 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
|
|
|
138
165
|
|
|
139
166
|
/**
|
|
140
167
|
* Max amount of playback drift allowed in seconds.
|
|
141
|
-
*
|
|
168
|
+
*
|
|
142
169
|
* @remarks
|
|
143
|
-
* Should the local clients playback position lag by more than the specified value, the
|
|
144
|
-
* coordinator will trigger a `catchup` action.
|
|
145
|
-
*
|
|
170
|
+
* Should the local clients playback position lag by more than the specified value, the
|
|
171
|
+
* coordinator will trigger a `catchup` action.
|
|
172
|
+
*
|
|
146
173
|
* Defaults to a value of `1` second.
|
|
147
174
|
*/
|
|
148
175
|
public get maxPlaybackDrift(): number {
|
|
@@ -154,11 +181,11 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
|
|
|
154
181
|
}
|
|
155
182
|
|
|
156
183
|
/**
|
|
157
|
-
* Frequency with which position updates are broadcast to the rest of the group in
|
|
184
|
+
* Frequency with which position updates are broadcast to the rest of the group in
|
|
158
185
|
* seconds.
|
|
159
|
-
*
|
|
186
|
+
*
|
|
160
187
|
* @remarks
|
|
161
|
-
* Defaults to a value of `2` seconds.
|
|
188
|
+
* Defaults to a value of `2` seconds.
|
|
162
189
|
*/
|
|
163
190
|
public get positionUpdateInterval(): number {
|
|
164
191
|
return this._positionUpdateInterval.seconds;
|
|
@@ -170,200 +197,273 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
|
|
|
170
197
|
|
|
171
198
|
/**
|
|
172
199
|
* Instructs the group to play the current track.
|
|
173
|
-
*
|
|
200
|
+
*
|
|
174
201
|
* @remarks
|
|
175
|
-
* Throws an exception if the session/coordinator hasn't been initialized, no track has been
|
|
202
|
+
* Throws an exception if the session/coordinator hasn't been initialized, no track has been
|
|
176
203
|
* loaded, or `canPlayPause` is false.
|
|
177
204
|
*/
|
|
178
205
|
public play(): void {
|
|
179
206
|
if (!this._hasInitialized) {
|
|
180
|
-
throw new Error(
|
|
207
|
+
throw new Error(
|
|
208
|
+
`LiveMediaSessionCoordinator.play() called before initialize() called.`
|
|
209
|
+
);
|
|
181
210
|
}
|
|
182
211
|
|
|
183
212
|
if (!this._groupState?.playbackTrack.current.metadata) {
|
|
184
|
-
throw new Error(
|
|
213
|
+
throw new Error(
|
|
214
|
+
`LiveMediaSessionCoordinator.play() called before MediaSession.metadata assigned.`
|
|
215
|
+
);
|
|
185
216
|
}
|
|
186
217
|
|
|
187
218
|
if (!this.canPlayPause) {
|
|
188
|
-
throw new Error(
|
|
219
|
+
throw new Error(
|
|
220
|
+
`LiveMediaSessionCoordinator.play() operation blocked.`
|
|
221
|
+
);
|
|
189
222
|
}
|
|
190
223
|
|
|
191
224
|
// Get projected position
|
|
192
225
|
const position = this.getPlayerPosition();
|
|
193
226
|
|
|
194
227
|
// Send transport command
|
|
195
|
-
this._logger.sendTelemetryEvent(
|
|
228
|
+
this._logger.sendTelemetryEvent(
|
|
229
|
+
TelemetryEvents.SessionCoordinator.PlayCalled,
|
|
230
|
+
null,
|
|
231
|
+
{ position: position }
|
|
232
|
+
);
|
|
196
233
|
this._playEvent!.sendEvent({
|
|
197
234
|
track: this._groupState.playbackTrack.current,
|
|
198
|
-
position: position
|
|
235
|
+
position: position,
|
|
199
236
|
});
|
|
200
237
|
}
|
|
201
|
-
|
|
238
|
+
|
|
202
239
|
/**
|
|
203
240
|
* Instructs the group to pause the current track.
|
|
204
|
-
*
|
|
241
|
+
*
|
|
205
242
|
* @remarks
|
|
206
|
-
* Throws an exception if the session/coordinator hasn't been initialized, no track has been
|
|
243
|
+
* Throws an exception if the session/coordinator hasn't been initialized, no track has been
|
|
207
244
|
* loaded, or `canPlayPause` is false.
|
|
208
245
|
*/
|
|
209
246
|
public pause(): void {
|
|
210
247
|
if (!this._hasInitialized) {
|
|
211
|
-
throw new Error(
|
|
248
|
+
throw new Error(
|
|
249
|
+
`LiveMediaSessionCoordinator.pause() called before initialize() called.`
|
|
250
|
+
);
|
|
212
251
|
}
|
|
213
252
|
|
|
214
253
|
if (!this._groupState?.playbackTrack.current.metadata) {
|
|
215
|
-
throw new Error(
|
|
254
|
+
throw new Error(
|
|
255
|
+
`LiveMediaSessionCoordinator.pause() called before MediaSession.metadata assigned.`
|
|
256
|
+
);
|
|
216
257
|
}
|
|
217
258
|
|
|
218
259
|
if (!this.canPlayPause) {
|
|
219
|
-
throw new Error(
|
|
260
|
+
throw new Error(
|
|
261
|
+
`LiveMediaSessionCoordinator.pause() operation blocked.`
|
|
262
|
+
);
|
|
220
263
|
}
|
|
221
264
|
|
|
222
265
|
// Get projected position
|
|
223
266
|
const position = this.getPlayerPosition();
|
|
224
267
|
|
|
225
268
|
// Send transport command
|
|
226
|
-
this._logger.sendTelemetryEvent(
|
|
269
|
+
this._logger.sendTelemetryEvent(
|
|
270
|
+
TelemetryEvents.SessionCoordinator.PauseCalled,
|
|
271
|
+
null,
|
|
272
|
+
{ position: position }
|
|
273
|
+
);
|
|
227
274
|
this._pauseEvent!.sendEvent({
|
|
228
275
|
track: this._groupState.playbackTrack.current,
|
|
229
|
-
position: position
|
|
276
|
+
position: position,
|
|
230
277
|
});
|
|
231
278
|
}
|
|
232
279
|
|
|
233
280
|
/**
|
|
234
281
|
* Instructs the group to seek to a new position within the current track.
|
|
235
|
-
*
|
|
282
|
+
*
|
|
236
283
|
* @remarks
|
|
237
|
-
* Throws an exception if the session/coordinator hasn't been initialized, no track has been
|
|
284
|
+
* Throws an exception if the session/coordinator hasn't been initialized, no track has been
|
|
238
285
|
* loaded, or `canSeek` is false.
|
|
239
286
|
* @param time Playback position in seconds to seek to.
|
|
240
287
|
*/
|
|
241
288
|
public seekTo(time: number): void {
|
|
242
289
|
if (!this._hasInitialized) {
|
|
243
|
-
throw new Error(
|
|
290
|
+
throw new Error(
|
|
291
|
+
`LiveMediaSessionCoordinator.seekTo() called before initialize() called.`
|
|
292
|
+
);
|
|
244
293
|
}
|
|
245
294
|
|
|
246
295
|
if (!this._groupState?.playbackTrack.current.metadata) {
|
|
247
|
-
throw new Error(
|
|
296
|
+
throw new Error(
|
|
297
|
+
`LiveMediaSessionCoordinator.seekTo() called before MediaSession.metadata assigned.`
|
|
298
|
+
);
|
|
248
299
|
}
|
|
249
300
|
|
|
250
301
|
if (!this.canSeek) {
|
|
251
|
-
throw new Error(
|
|
302
|
+
throw new Error(
|
|
303
|
+
`LiveMediaSessionCoordinator.seekTo() operation blocked.`
|
|
304
|
+
);
|
|
252
305
|
}
|
|
253
306
|
|
|
254
307
|
// Send transport command
|
|
255
|
-
this._logger.sendTelemetryEvent(
|
|
308
|
+
this._logger.sendTelemetryEvent(
|
|
309
|
+
TelemetryEvents.SessionCoordinator.SeekToCalled,
|
|
310
|
+
null,
|
|
311
|
+
{ position: time }
|
|
312
|
+
);
|
|
256
313
|
this._seekToEvent!.sendEvent({
|
|
257
314
|
track: this._groupState.playbackTrack.current,
|
|
258
|
-
position: time
|
|
315
|
+
position: time,
|
|
259
316
|
});
|
|
260
317
|
}
|
|
261
318
|
|
|
262
319
|
/**
|
|
263
320
|
* Instructs the group to load a new track.
|
|
264
|
-
*
|
|
321
|
+
*
|
|
265
322
|
* @remarks
|
|
266
|
-
* Throws an exception if the session/coordinator hasn't been initialized or `canSetTrack` is
|
|
323
|
+
* Throws an exception if the session/coordinator hasn't been initialized or `canSetTrack` is
|
|
267
324
|
* false.
|
|
268
325
|
* @param metadata The track to load or `null` to indicate that the end of the track is reached.
|
|
269
326
|
* @param waitPoints Optional. List of static wait points to configure for the track. Dynamic wait points can be added via the `beginSuspension()` call.
|
|
270
327
|
*/
|
|
271
|
-
public setTrack(
|
|
328
|
+
public setTrack(
|
|
329
|
+
metadata: ExtendedMediaMetadata | null,
|
|
330
|
+
waitPoints?: CoordinationWaitPoint[]
|
|
331
|
+
): void {
|
|
272
332
|
if (!this._hasInitialized) {
|
|
273
|
-
throw new Error(
|
|
333
|
+
throw new Error(
|
|
334
|
+
`LiveMediaSessionCoordinator.setTrack() called before initialize() called.`
|
|
335
|
+
);
|
|
274
336
|
}
|
|
275
337
|
|
|
276
338
|
if (!this.canSetTrack) {
|
|
277
|
-
throw new Error(
|
|
339
|
+
throw new Error(
|
|
340
|
+
`LiveMediaSessionCoordinator.setTrack() operation blocked.`
|
|
341
|
+
);
|
|
278
342
|
}
|
|
279
343
|
|
|
280
344
|
// Send transport command
|
|
281
|
-
this._logger.sendTelemetryEvent(
|
|
345
|
+
this._logger.sendTelemetryEvent(
|
|
346
|
+
TelemetryEvents.SessionCoordinator.SetTrackCalled
|
|
347
|
+
);
|
|
282
348
|
this._setTrackEvent!.sendEvent({
|
|
283
349
|
metadata: metadata,
|
|
284
|
-
waitPoints: waitPoints || []
|
|
350
|
+
waitPoints: waitPoints || [],
|
|
285
351
|
});
|
|
286
352
|
}
|
|
287
353
|
|
|
288
354
|
/**
|
|
289
355
|
* Updates the track data object for the current track.
|
|
290
|
-
*
|
|
356
|
+
*
|
|
291
357
|
* @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 initialized or `canSetTrackData` is
|
|
358
|
+
* The track data object can be used by applications to synchronize things like pitch, roll,
|
|
359
|
+
* and yaw of a 360 video. This data object will be reset to null anytime the track changes.
|
|
360
|
+
*
|
|
361
|
+
* Throws an exception if the session/coordinator hasn't been initialized or `canSetTrackData` is
|
|
296
362
|
* false.
|
|
297
|
-
* @param data New data object to sync with the group. This value will be synchronized using a last writer wins strategy.
|
|
363
|
+
* @param data New data object to sync with the group. This value will be synchronized using a last writer wins strategy.
|
|
298
364
|
*/
|
|
299
|
-
|
|
365
|
+
public setTrackData(data: object | null): void {
|
|
300
366
|
if (!this._hasInitialized) {
|
|
301
|
-
throw new Error(
|
|
367
|
+
throw new Error(
|
|
368
|
+
`LiveMediaSessionCoordinator.setTrackData() called before initialize() called.`
|
|
369
|
+
);
|
|
302
370
|
}
|
|
303
371
|
|
|
304
372
|
if (!this.canSetTrackData) {
|
|
305
|
-
throw new Error(
|
|
373
|
+
throw new Error(
|
|
374
|
+
`LiveMediaSessionCoordinator.setTrackData() operation blocked.`
|
|
375
|
+
);
|
|
306
376
|
}
|
|
307
377
|
|
|
308
378
|
// Send transport command
|
|
309
|
-
this._logger.sendTelemetryEvent(
|
|
379
|
+
this._logger.sendTelemetryEvent(
|
|
380
|
+
TelemetryEvents.SessionCoordinator.SetTrackDataCalled
|
|
381
|
+
);
|
|
310
382
|
this._setTrackDataEvent!.sendEvent({
|
|
311
|
-
data: data
|
|
383
|
+
data: data,
|
|
312
384
|
});
|
|
313
385
|
}
|
|
314
386
|
|
|
315
387
|
/**
|
|
316
388
|
* Begins a new local suspension.
|
|
317
|
-
*
|
|
389
|
+
*
|
|
318
390
|
* @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
|
|
391
|
+
* Suspension temporarily suspend the clients local synchronization with the group. This can
|
|
392
|
+
* be useful for displaying ads to users or temporarily disconnecting from the session while
|
|
393
|
+
* the user seeks the video using a timeline scrubber.
|
|
394
|
+
*
|
|
395
|
+
* Multiple simultaneous suspensions are allowed and when the last suspension ends the local
|
|
324
396
|
* 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
|
|
397
|
+
*
|
|
398
|
+
* A "Dynamic Wait Point" can be specified when `beginSuspension()` is called and the wait
|
|
399
|
+
* point will be broadcast to all other clients in the group. Those clients will then
|
|
400
|
+
* automatically enter a suspension state once they reach the positions specified by the
|
|
401
|
+
* wait point. Clients that are passed the wait point will immediately suspend.
|
|
402
|
+
*
|
|
403
|
+
* Any wait point based suspension (dynamic or static) will result in all clients remaining
|
|
332
404
|
* in a suspension state until the list client ends their suspension. This behavior can be
|
|
333
405
|
* conditionally bypassed by settings the wait points `maxClients` value.
|
|
334
|
-
*
|
|
406
|
+
*
|
|
335
407
|
* Throws an exception if the session/coordinator hasn't been initialized.
|
|
336
|
-
* @param waitPoint Optional. Dynamic wait point to broadcast to all of the clients.
|
|
408
|
+
* @param waitPoint Optional. Dynamic wait point to broadcast to all of the clients.
|
|
337
409
|
* @returns The suspension object. Call `end()` on the returned suspension to end the suspension.
|
|
338
410
|
*/
|
|
339
|
-
public beginSuspension(
|
|
411
|
+
public beginSuspension(
|
|
412
|
+
waitPoint?: CoordinationWaitPoint
|
|
413
|
+
): MediaSessionCoordinatorSuspension {
|
|
340
414
|
if (!this._hasInitialized) {
|
|
341
|
-
throw new Error(
|
|
415
|
+
throw new Error(
|
|
416
|
+
`LiveMediaSessionCoordinator.beginSuspension() called before initialize() called.`
|
|
417
|
+
);
|
|
342
418
|
}
|
|
343
419
|
|
|
344
420
|
if (!this._groupState?.playbackTrack.current.metadata) {
|
|
345
|
-
throw new Error(
|
|
421
|
+
throw new Error(
|
|
422
|
+
`LiveMediaSessionCoordinator.beginSuspension() called before MediaSession.metadata assigned.`
|
|
423
|
+
);
|
|
346
424
|
}
|
|
347
425
|
|
|
348
426
|
// Tell group state that suspension is started
|
|
349
427
|
if (waitPoint) {
|
|
350
|
-
this._logger.sendTelemetryEvent(
|
|
428
|
+
this._logger.sendTelemetryEvent(
|
|
429
|
+
TelemetryEvents.SessionCoordinator.BeginSuspensionAndWait,
|
|
430
|
+
null,
|
|
431
|
+
{
|
|
432
|
+
position: waitPoint.position,
|
|
433
|
+
maxClients: waitPoint.maxClients,
|
|
434
|
+
}
|
|
435
|
+
);
|
|
351
436
|
this._lastWaitPoint = waitPoint;
|
|
352
437
|
this._groupState.beginSuspension(waitPoint);
|
|
353
438
|
} else {
|
|
354
|
-
this._logger.sendTelemetryEvent(
|
|
439
|
+
this._logger.sendTelemetryEvent(
|
|
440
|
+
TelemetryEvents.SessionCoordinator.BeginSuspension
|
|
441
|
+
);
|
|
355
442
|
this._groupState.beginSuspension();
|
|
356
443
|
}
|
|
357
444
|
|
|
358
445
|
// Return new suspension object
|
|
359
|
-
return new LiveMediaSessionCoordinatorSuspension(waitPoint, time => {
|
|
446
|
+
return new LiveMediaSessionCoordinatorSuspension(waitPoint, (time) => {
|
|
360
447
|
if (waitPoint) {
|
|
361
|
-
this._logger.sendTelemetryEvent(
|
|
448
|
+
this._logger.sendTelemetryEvent(
|
|
449
|
+
TelemetryEvents.SessionCoordinator.EndSuspensionAndWait,
|
|
450
|
+
null,
|
|
451
|
+
{
|
|
452
|
+
position: waitPoint.position,
|
|
453
|
+
maxClients: waitPoint.maxClients,
|
|
454
|
+
}
|
|
455
|
+
);
|
|
362
456
|
} else {
|
|
363
|
-
this._logger.sendTelemetryEvent(
|
|
457
|
+
this._logger.sendTelemetryEvent(
|
|
458
|
+
TelemetryEvents.SessionCoordinator.EndSuspension
|
|
459
|
+
);
|
|
364
460
|
}
|
|
365
461
|
this._groupState!.endSuspension(time == undefined);
|
|
366
|
-
if (
|
|
462
|
+
if (
|
|
463
|
+
time != undefined &&
|
|
464
|
+
!this._groupState?.isSuspended &&
|
|
465
|
+
!this._groupState?.isWaiting
|
|
466
|
+
) {
|
|
367
467
|
// Seek to new position
|
|
368
468
|
this.seekTo(Math.max(time, 0.0));
|
|
369
469
|
}
|
|
@@ -374,7 +474,7 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
|
|
|
374
474
|
* @hidden
|
|
375
475
|
* Called by MediaSession to verify the coordinator has been initialized.
|
|
376
476
|
*/
|
|
377
|
-
|
|
477
|
+
public get isInitialized(): boolean {
|
|
378
478
|
return this._hasInitialized;
|
|
379
479
|
}
|
|
380
480
|
|
|
@@ -382,9 +482,13 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
|
|
|
382
482
|
* @hidden
|
|
383
483
|
* Called by MediaSession to start coordinator.
|
|
384
484
|
*/
|
|
385
|
-
public async initialize(
|
|
485
|
+
public async initialize(
|
|
486
|
+
acceptTransportChangesFrom?: UserMeetingRole[]
|
|
487
|
+
): Promise<void> {
|
|
386
488
|
if (this._hasInitialized) {
|
|
387
|
-
throw new Error(
|
|
489
|
+
throw new Error(
|
|
490
|
+
`LiveMediaSessionCoordinator.initialize() already initialized.`
|
|
491
|
+
);
|
|
388
492
|
}
|
|
389
493
|
|
|
390
494
|
// Create children
|
|
@@ -396,8 +500,12 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
|
|
|
396
500
|
* @hidden
|
|
397
501
|
* Called by the MediaSession to detect if a wait point has been hit.
|
|
398
502
|
*/
|
|
399
|
-
public findNextWaitPoint(): CoordinationWaitPoint|null {
|
|
400
|
-
return
|
|
503
|
+
public findNextWaitPoint(): CoordinationWaitPoint | null {
|
|
504
|
+
return (
|
|
505
|
+
this._groupState?.playbackTrack.findNextWaitPoint(
|
|
506
|
+
this._lastWaitPoint
|
|
507
|
+
) || null
|
|
508
|
+
);
|
|
401
509
|
}
|
|
402
510
|
|
|
403
511
|
/**
|
|
@@ -406,43 +514,93 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
|
|
|
406
514
|
*/
|
|
407
515
|
public sendPositionUpdate(state: IMediaPlayerState): void {
|
|
408
516
|
if (!this._hasInitialized) {
|
|
409
|
-
throw new Error(
|
|
517
|
+
throw new Error(
|
|
518
|
+
`LiveMediaSessionCoordinator.sendPositionUpdate() called before initialize() called.`
|
|
519
|
+
);
|
|
410
520
|
}
|
|
411
521
|
|
|
412
522
|
// Send position update event
|
|
413
523
|
const evt = this._groupState!.createPositionUpdateEvent(state);
|
|
414
524
|
this._positionUpdateEvent?.sendEvent(evt);
|
|
415
525
|
}
|
|
416
|
-
|
|
417
|
-
protected async createChildren(
|
|
526
|
+
|
|
527
|
+
protected async createChildren(
|
|
528
|
+
acceptTransportChangesFrom?: UserMeetingRole[]
|
|
529
|
+
): Promise<void> {
|
|
418
530
|
// Create event scopes
|
|
419
|
-
const scope = new LiveEventScope(
|
|
531
|
+
const scope = new LiveEventScope(
|
|
532
|
+
this._runtime,
|
|
533
|
+
acceptTransportChangesFrom
|
|
534
|
+
);
|
|
420
535
|
const unrestrictedScope = new LiveEventScope(this._runtime);
|
|
421
536
|
|
|
422
537
|
// Initialize internal coordinator state
|
|
423
|
-
this._groupState = new GroupCoordinatorState(
|
|
424
|
-
|
|
538
|
+
this._groupState = new GroupCoordinatorState(
|
|
539
|
+
this._runtime,
|
|
540
|
+
this._maxPlaybackDrift,
|
|
541
|
+
this._positionUpdateInterval,
|
|
542
|
+
this._getPlayerState
|
|
543
|
+
);
|
|
544
|
+
this._groupState.on(GroupCoordinatorStateEvents.triggeraction, (evt) =>
|
|
545
|
+
this.emit(evt.name, evt)
|
|
546
|
+
);
|
|
425
547
|
|
|
426
548
|
// Listen for track changes
|
|
427
|
-
this._setTrackEvent = new LiveEventTarget<ISetTrackEvent>(
|
|
428
|
-
|
|
549
|
+
this._setTrackEvent = new LiveEventTarget<ISetTrackEvent>(
|
|
550
|
+
scope,
|
|
551
|
+
"setTrack",
|
|
552
|
+
(event, local) => this._groupState!.handleSetTrack(event, local)
|
|
553
|
+
);
|
|
554
|
+
this._setTrackDataEvent = new LiveEventTarget<ISetTrackDataEvent>(
|
|
555
|
+
scope,
|
|
556
|
+
"setTrackData",
|
|
557
|
+
(event, local) => this._groupState!.handleSetTrackData(event, local)
|
|
558
|
+
);
|
|
429
559
|
|
|
430
560
|
// Listen for transport commands
|
|
431
|
-
this._playEvent = new LiveEventTarget(scope,
|
|
432
|
-
|
|
433
|
-
|
|
561
|
+
this._playEvent = new LiveEventTarget(scope, "play", (event, local) =>
|
|
562
|
+
this._groupState!.handleTransportCommand(event, local)
|
|
563
|
+
);
|
|
564
|
+
this._pauseEvent = new LiveEventTarget(scope, "pause", (event, local) =>
|
|
565
|
+
this._groupState!.handleTransportCommand(event, local)
|
|
566
|
+
);
|
|
567
|
+
this._seekToEvent = new LiveEventTarget(
|
|
568
|
+
scope,
|
|
569
|
+
"seekTo",
|
|
570
|
+
(event, local) =>
|
|
571
|
+
this._groupState!.handleTransportCommand(event, local)
|
|
572
|
+
);
|
|
434
573
|
|
|
435
574
|
// Listen for position updates
|
|
436
|
-
this._positionUpdateEvent = new LiveEventTarget(
|
|
575
|
+
this._positionUpdateEvent = new LiveEventTarget(
|
|
576
|
+
unrestrictedScope,
|
|
577
|
+
"positionUpdate",
|
|
578
|
+
(event, local) =>
|
|
579
|
+
this._groupState!.handlePositionUpdate(event, local)
|
|
580
|
+
);
|
|
437
581
|
|
|
438
582
|
// Listen for joined event
|
|
439
|
-
this._joinedEvent = new LiveEventTarget(
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
583
|
+
this._joinedEvent = new LiveEventTarget(
|
|
584
|
+
unrestrictedScope,
|
|
585
|
+
"joined",
|
|
586
|
+
(evt, local) => {
|
|
587
|
+
// Immediately send a position update
|
|
588
|
+
this._logger.sendTelemetryEvent(
|
|
589
|
+
TelemetryEvents.SessionCoordinator.RemoteJoinReceived,
|
|
590
|
+
null,
|
|
591
|
+
{
|
|
592
|
+
correlationId: LiveTelemetryLogger.formatCorrelationId(
|
|
593
|
+
evt.clientId,
|
|
594
|
+
evt.timestamp
|
|
595
|
+
),
|
|
596
|
+
}
|
|
597
|
+
);
|
|
598
|
+
const state = this._getPlayerState();
|
|
599
|
+
const update =
|
|
600
|
+
this._groupState!.createPositionUpdateEvent(state);
|
|
601
|
+
this._positionUpdateEvent?.sendEvent(update);
|
|
602
|
+
}
|
|
603
|
+
);
|
|
446
604
|
|
|
447
605
|
// Send initial joined event
|
|
448
606
|
this._joinedEvent.sendEvent({});
|
|
@@ -451,11 +609,13 @@ export class LiveMediaSessionCoordinator extends EventEmitter {
|
|
|
451
609
|
private getPlayerPosition(): number {
|
|
452
610
|
const { positionState, playbackState } = this._getPlayerState();
|
|
453
611
|
switch (playbackState) {
|
|
454
|
-
case
|
|
455
|
-
case
|
|
612
|
+
case "none":
|
|
613
|
+
case "ended":
|
|
456
614
|
return 0.0;
|
|
457
615
|
default:
|
|
458
|
-
return positionState && positionState.position != undefined
|
|
616
|
+
return positionState && positionState.position != undefined
|
|
617
|
+
? positionState.position
|
|
618
|
+
: 0.0;
|
|
459
619
|
}
|
|
460
|
-
}
|
|
620
|
+
}
|
|
461
621
|
}
|