@microsoft/live-share-media 1.0.0-preview.1 → 1.0.0-preview.3
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 +1 -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 +121 -69
- 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 +317 -146
- 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
|
@@ -2,14 +2,21 @@
|
|
|
2
2
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
3
|
* Licensed under the Microsoft Live Share SDK License.
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
import { LiveTelemetryLogger, IEvent } from "@microsoft/live-share";
|
|
6
|
+
import EventEmitter from "events";
|
|
7
|
+
import {
|
|
8
|
+
ExtendedMediaSessionAction,
|
|
9
|
+
ExtendedMediaSessionPlaybackState,
|
|
10
|
+
ExtendedMediaMetadata,
|
|
11
|
+
CoordinationWaitPoint,
|
|
12
|
+
ExtendedMediaSessionActionDetails,
|
|
13
|
+
MediaSessionCoordinatorEvents,
|
|
14
|
+
MediaSessionCoordinatorSuspension,
|
|
15
|
+
} from "./MediaSessionExtensions";
|
|
16
|
+
import { VolumeManager } from "./VolumeManager";
|
|
17
|
+
import { LiveMediaSession } from "./LiveMediaSession";
|
|
18
|
+
import { IMediaPlayer } from "./IMediaPlayer";
|
|
19
|
+
import { TelemetryEvents } from "./internals";
|
|
13
20
|
|
|
14
21
|
/**
|
|
15
22
|
* Event data returned by `MediaPlayerSynchronizer` object.
|
|
@@ -30,25 +37,41 @@ export interface IMediaPlayerSynchronizerEvent extends IEvent {
|
|
|
30
37
|
*/
|
|
31
38
|
export enum MediaPlayerSynchronizerEvents {
|
|
32
39
|
/**
|
|
33
|
-
*
|
|
40
|
+
*
|
|
34
41
|
*/
|
|
35
|
-
coordinatorstatechange =
|
|
36
|
-
groupaction =
|
|
37
|
-
useraction =
|
|
42
|
+
coordinatorstatechange = "coordinatorstatechange",
|
|
43
|
+
groupaction = "groupaction",
|
|
44
|
+
useraction = "useraction",
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
/**
|
|
41
48
|
* Synchronizes a local HTML Media Element with a group of remote HTML Media Elements.
|
|
42
|
-
*
|
|
49
|
+
*
|
|
43
50
|
* @remarks
|
|
44
51
|
* All of an apps transport control commands should be routed through the synchronizer. If the
|
|
45
|
-
* app is not currently joined to the group media session, the commands will be applied directly
|
|
52
|
+
* app is not currently joined to the group media session, the commands will be applied directly
|
|
46
53
|
* to the local player. When the group session is joined the commands will be broadcast to the
|
|
47
|
-
* group in addition to being applied to the local player.
|
|
54
|
+
* group in addition to being applied to the local player.
|
|
48
55
|
*/
|
|
49
56
|
export class MediaPlayerSynchronizer extends EventEmitter {
|
|
50
|
-
private static SESSION_ACTIONS: ExtendedMediaSessionAction[] = [
|
|
51
|
-
|
|
57
|
+
private static SESSION_ACTIONS: ExtendedMediaSessionAction[] = [
|
|
58
|
+
"play",
|
|
59
|
+
"pause",
|
|
60
|
+
"seekto",
|
|
61
|
+
"settrack",
|
|
62
|
+
"datachange",
|
|
63
|
+
"catchup",
|
|
64
|
+
"wait",
|
|
65
|
+
];
|
|
66
|
+
private static PLAYER_EVENTS: string[] = [
|
|
67
|
+
"playing",
|
|
68
|
+
"pause",
|
|
69
|
+
"ratechange",
|
|
70
|
+
"timeupdate",
|
|
71
|
+
"ended",
|
|
72
|
+
"loadedmetadata",
|
|
73
|
+
"blocked",
|
|
74
|
+
];
|
|
52
75
|
|
|
53
76
|
private _logger: LiveTelemetryLogger;
|
|
54
77
|
private _player: IMediaPlayer;
|
|
@@ -58,8 +81,8 @@ export class MediaPlayerSynchronizer extends EventEmitter {
|
|
|
58
81
|
private _onPlayerEvent: EventListener;
|
|
59
82
|
private _seekSuspension?: MediaSessionCoordinatorSuspension;
|
|
60
83
|
private _viewOnly = false;
|
|
61
|
-
private _expectedPlaybackState: ExtendedMediaSessionPlaybackState =
|
|
62
|
-
private _trackData: object|null = null;
|
|
84
|
+
private _expectedPlaybackState: ExtendedMediaSessionPlaybackState = "none";
|
|
85
|
+
private _trackData: object | null = null;
|
|
63
86
|
|
|
64
87
|
/**
|
|
65
88
|
* Creates a new `MediaElementSynchronizer` instance.
|
|
@@ -67,7 +90,11 @@ export class MediaPlayerSynchronizer extends EventEmitter {
|
|
|
67
90
|
* @param mediaSession Group MediaSession object being used.
|
|
68
91
|
* @param onEnd Optional. Function to call when synchronizers `end()` method is called.
|
|
69
92
|
*/
|
|
70
|
-
constructor(
|
|
93
|
+
constructor(
|
|
94
|
+
player: IMediaPlayer,
|
|
95
|
+
mediaSession: LiveMediaSession,
|
|
96
|
+
onEnd: () => void
|
|
97
|
+
) {
|
|
71
98
|
super();
|
|
72
99
|
this._player = player;
|
|
73
100
|
this._mediaSession = mediaSession;
|
|
@@ -81,13 +108,16 @@ export class MediaPlayerSynchronizer extends EventEmitter {
|
|
|
81
108
|
let playbackState: ExtendedMediaSessionPlaybackState;
|
|
82
109
|
const src = this._player.currentSrc || this._player.src;
|
|
83
110
|
if (this._player.ended) {
|
|
84
|
-
playbackState =
|
|
85
|
-
} else if (
|
|
86
|
-
|
|
111
|
+
playbackState = "ended";
|
|
112
|
+
} else if (
|
|
113
|
+
!src ||
|
|
114
|
+
(this._player.paused && this._player.currentTime < 1.0)
|
|
115
|
+
) {
|
|
116
|
+
playbackState = "none";
|
|
87
117
|
} else if (this._player.paused) {
|
|
88
|
-
playbackState =
|
|
118
|
+
playbackState = "paused";
|
|
89
119
|
} else {
|
|
90
|
-
playbackState =
|
|
120
|
+
playbackState = "playing";
|
|
91
121
|
}
|
|
92
122
|
|
|
93
123
|
const state = {
|
|
@@ -96,9 +126,9 @@ export class MediaPlayerSynchronizer extends EventEmitter {
|
|
|
96
126
|
positionState: {
|
|
97
127
|
position: this._player.currentTime,
|
|
98
128
|
playbackRate: this._player.playbackRate,
|
|
99
|
-
duration: this._player.duration
|
|
129
|
+
duration: this._player.duration,
|
|
100
130
|
},
|
|
101
|
-
trackData: this._trackData
|
|
131
|
+
trackData: this._trackData,
|
|
102
132
|
};
|
|
103
133
|
|
|
104
134
|
return state;
|
|
@@ -106,118 +136,192 @@ export class MediaPlayerSynchronizer extends EventEmitter {
|
|
|
106
136
|
|
|
107
137
|
// Listen for player events
|
|
108
138
|
this._onPlayerEvent = (evt: Event) => {
|
|
109
|
-
if (evt.type !=
|
|
110
|
-
this._logger.sendTelemetryEvent(
|
|
139
|
+
if (evt.type != "timeupdate") {
|
|
140
|
+
this._logger.sendTelemetryEvent(
|
|
141
|
+
TelemetryEvents.MediaPlayerSynchronizer.PlayerEvent,
|
|
142
|
+
null,
|
|
143
|
+
{
|
|
144
|
+
type: evt.type,
|
|
145
|
+
}
|
|
146
|
+
);
|
|
111
147
|
}
|
|
112
148
|
|
|
113
149
|
switch (evt.type) {
|
|
114
|
-
case
|
|
150
|
+
case "loadedmetadata":
|
|
151
|
+
// eslint-disable-next-line no-case-declarations
|
|
115
152
|
const src = this._player.currentSrc || this._player.src;
|
|
116
153
|
this._trackData = null;
|
|
117
154
|
break;
|
|
118
|
-
case
|
|
155
|
+
case "playing":
|
|
119
156
|
// Handle case for YouTube player where user can pause/play video by clicking on it.
|
|
120
157
|
// - Videos don't always start at 0.0 seconds.
|
|
121
|
-
if (this._expectedPlaybackState !=
|
|
122
|
-
if (
|
|
123
|
-
this.
|
|
158
|
+
if (this._expectedPlaybackState != "playing") {
|
|
159
|
+
if (
|
|
160
|
+
this._mediaSession.coordinator.canPlayPause &&
|
|
161
|
+
this._player.currentTime < 1.0
|
|
162
|
+
) {
|
|
163
|
+
this._logger.sendTelemetryEvent(
|
|
164
|
+
TelemetryEvents.MediaPlayerSynchronizer
|
|
165
|
+
.UserTappedVideoToPlay
|
|
166
|
+
);
|
|
124
167
|
this.play();
|
|
125
168
|
}
|
|
126
169
|
}
|
|
127
170
|
// block play if player state is playing when expected synced state is paused.
|
|
128
171
|
// needed because cannot tell if its a user initiated event, so disallow play
|
|
129
|
-
if (this._expectedPlaybackState ===
|
|
172
|
+
if (this._expectedPlaybackState === "paused") {
|
|
130
173
|
this._player.pause();
|
|
131
174
|
}
|
|
132
175
|
|
|
133
176
|
// block play if player state is playing when expected synced state is none.
|
|
134
177
|
// needed because user who is not in control should not be able to start, so disallow play
|
|
135
|
-
if (this._expectedPlaybackState ===
|
|
178
|
+
if (this._expectedPlaybackState === "none") {
|
|
136
179
|
this._player.pause();
|
|
137
180
|
}
|
|
138
181
|
break;
|
|
139
|
-
case
|
|
182
|
+
case "pause":
|
|
140
183
|
// block pause if player state is paused when expected synced state is playing
|
|
141
184
|
// needed because cannot tell if its a user initiated event, so disallow pause
|
|
142
|
-
if (this._expectedPlaybackState ===
|
|
185
|
+
if (this._expectedPlaybackState === "playing") {
|
|
143
186
|
this._player.play();
|
|
144
187
|
}
|
|
145
188
|
break;
|
|
146
|
-
case
|
|
189
|
+
case "ratechange":
|
|
147
190
|
// Block rate changes unless suspended.
|
|
148
|
-
if (
|
|
149
|
-
this.
|
|
191
|
+
if (
|
|
192
|
+
this._player.playbackRate != 1.0 &&
|
|
193
|
+
!this._mediaSession.coordinator.isSuspended
|
|
194
|
+
) {
|
|
195
|
+
this._logger.sendTelemetryEvent(
|
|
196
|
+
TelemetryEvents.MediaPlayerSynchronizer
|
|
197
|
+
.PlaybackRateChangeBlocked
|
|
198
|
+
);
|
|
150
199
|
this._player.playbackRate = 1.0;
|
|
151
200
|
}
|
|
152
201
|
break;
|
|
153
|
-
case
|
|
154
|
-
this._logger.sendTelemetryEvent(
|
|
202
|
+
case "blocked":
|
|
203
|
+
this._logger.sendTelemetryEvent(
|
|
204
|
+
TelemetryEvents.MediaPlayerSynchronizer
|
|
205
|
+
.PlaybackRateChangeBlocked
|
|
206
|
+
);
|
|
155
207
|
break;
|
|
156
208
|
}
|
|
157
209
|
};
|
|
158
210
|
|
|
159
211
|
// Register for coordinator state changes
|
|
160
|
-
this._mediaSession.coordinator.on(
|
|
212
|
+
this._mediaSession.coordinator.on(
|
|
213
|
+
MediaSessionCoordinatorEvents.coordinatorstatechange,
|
|
214
|
+
(evt) => this.emit(evt.type, evt)
|
|
215
|
+
);
|
|
161
216
|
|
|
162
217
|
// Register media session actions
|
|
163
218
|
for (const action of MediaPlayerSynchronizer.SESSION_ACTIONS) {
|
|
164
|
-
this._mediaSession.setActionHandler(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
this.
|
|
172
|
-
if (
|
|
173
|
-
this._logger.sendTelemetryEvent(
|
|
174
|
-
|
|
219
|
+
this._mediaSession.setActionHandler(
|
|
220
|
+
action,
|
|
221
|
+
async (details: ExtendedMediaSessionActionDetails) => {
|
|
222
|
+
let error: Error | undefined;
|
|
223
|
+
try {
|
|
224
|
+
switch (details.action) {
|
|
225
|
+
case "play":
|
|
226
|
+
this._expectedPlaybackState = "playing";
|
|
227
|
+
if (this._player.paused) {
|
|
228
|
+
this._logger.sendTelemetryEvent(
|
|
229
|
+
TelemetryEvents.MediaPlayerSynchronizer
|
|
230
|
+
.PlayAction
|
|
231
|
+
);
|
|
232
|
+
if (
|
|
233
|
+
typeof details.seekTime == "number" &&
|
|
234
|
+
this._player.currentTime < 1.0
|
|
235
|
+
) {
|
|
236
|
+
this._logger.sendTelemetryEvent(
|
|
237
|
+
TelemetryEvents
|
|
238
|
+
.MediaPlayerSynchronizer
|
|
239
|
+
.SeekingPlayerToStartPosition,
|
|
240
|
+
null,
|
|
241
|
+
{ position: details.seekTime }
|
|
242
|
+
);
|
|
243
|
+
this._player.currentTime =
|
|
244
|
+
details.seekTime!;
|
|
245
|
+
}
|
|
246
|
+
// Reference: https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/play#exceptions
|
|
247
|
+
await this._player.play();
|
|
175
248
|
}
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
249
|
+
break;
|
|
250
|
+
case "pause":
|
|
251
|
+
this._logger.sendTelemetryEvent(
|
|
252
|
+
TelemetryEvents.MediaPlayerSynchronizer
|
|
253
|
+
.PauseAction
|
|
254
|
+
);
|
|
255
|
+
this._expectedPlaybackState = "paused";
|
|
256
|
+
if (
|
|
257
|
+
typeof details.seekTime == "number" &&
|
|
258
|
+
this._player.currentTime < 1.0
|
|
259
|
+
) {
|
|
260
|
+
this._logger.sendTelemetryEvent(
|
|
261
|
+
TelemetryEvents.MediaPlayerSynchronizer
|
|
262
|
+
.SeekingPlayerToStartPosition,
|
|
263
|
+
null,
|
|
264
|
+
{ position: details.seekTime }
|
|
265
|
+
);
|
|
266
|
+
this._player.currentTime =
|
|
267
|
+
details.seekTime!;
|
|
268
|
+
}
|
|
269
|
+
this._player.pause();
|
|
270
|
+
break;
|
|
271
|
+
case "seekto":
|
|
272
|
+
if (typeof details.seekTime == "number") {
|
|
273
|
+
this._logger.sendTelemetryEvent(
|
|
274
|
+
TelemetryEvents.MediaPlayerSynchronizer
|
|
275
|
+
.SeekToAction,
|
|
276
|
+
null,
|
|
277
|
+
{ position: details.seekTime }
|
|
278
|
+
);
|
|
279
|
+
this._player.currentTime =
|
|
280
|
+
details.seekTime!;
|
|
281
|
+
}
|
|
282
|
+
break;
|
|
283
|
+
case "settrack":
|
|
284
|
+
this._logger.sendTelemetryEvent(
|
|
285
|
+
TelemetryEvents.MediaPlayerSynchronizer
|
|
286
|
+
.SetTrackAction
|
|
287
|
+
);
|
|
288
|
+
this._expectedPlaybackState = "none";
|
|
289
|
+
this._player.src =
|
|
290
|
+
details.metadata!.trackIdentifier;
|
|
291
|
+
this._player.load();
|
|
292
|
+
break;
|
|
293
|
+
case "datachange":
|
|
294
|
+
this._logger.sendTelemetryEvent(
|
|
295
|
+
TelemetryEvents.MediaPlayerSynchronizer
|
|
296
|
+
.DataChangeAction
|
|
297
|
+
);
|
|
298
|
+
break;
|
|
299
|
+
case "catchup":
|
|
300
|
+
if (typeof details.seekTime == "number") {
|
|
301
|
+
this._logger.sendTelemetryEvent(
|
|
302
|
+
TelemetryEvents.MediaPlayerSynchronizer
|
|
303
|
+
.CatchupAction,
|
|
304
|
+
null,
|
|
305
|
+
{ position: details.seekTime }
|
|
306
|
+
);
|
|
307
|
+
this.catchupPlayer(details.seekTime!);
|
|
308
|
+
}
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
} catch (err: any) {
|
|
312
|
+
if (err instanceof Error) {
|
|
313
|
+
error = err;
|
|
314
|
+
} else {
|
|
315
|
+
error = new Error(
|
|
316
|
+
err?.message ??
|
|
317
|
+
"An unknown error occurred after processing the group session action."
|
|
318
|
+
);
|
|
319
|
+
}
|
|
216
320
|
}
|
|
217
|
-
}
|
|
218
321
|
|
|
219
|
-
|
|
220
|
-
|
|
322
|
+
this.dispatchGroupAction(details, false, error);
|
|
323
|
+
}
|
|
324
|
+
);
|
|
221
325
|
}
|
|
222
326
|
|
|
223
327
|
// Subscribe to player events
|
|
@@ -225,7 +329,9 @@ export class MediaPlayerSynchronizer extends EventEmitter {
|
|
|
225
329
|
this._player.addEventListener(event, this._onPlayerEvent);
|
|
226
330
|
}
|
|
227
331
|
|
|
228
|
-
this._logger.sendTelemetryEvent(
|
|
332
|
+
this._logger.sendTelemetryEvent(
|
|
333
|
+
TelemetryEvents.MediaPlayerSynchronizer.SynchronizationStarted
|
|
334
|
+
);
|
|
229
335
|
}
|
|
230
336
|
|
|
231
337
|
/**
|
|
@@ -244,7 +350,7 @@ export class MediaPlayerSynchronizer extends EventEmitter {
|
|
|
244
350
|
|
|
245
351
|
/**
|
|
246
352
|
* If true the client is in a view only mode.
|
|
247
|
-
*
|
|
353
|
+
*
|
|
248
354
|
* @remarks
|
|
249
355
|
* Toggling this value to true results in `mediaSession.coordinator.canPlayPause`,
|
|
250
356
|
* `mediaSession.coordinator.canSeek`, `mediaSession.coordinator.canSetTrack`, and
|
|
@@ -276,7 +382,10 @@ export class MediaPlayerSynchronizer extends EventEmitter {
|
|
|
276
382
|
* @param event Name of the event to add.
|
|
277
383
|
* @param listener Function to call when the event is triggered.
|
|
278
384
|
*/
|
|
279
|
-
public addEventListener(
|
|
385
|
+
public addEventListener(
|
|
386
|
+
event: MediaPlayerSynchronizerEvents,
|
|
387
|
+
listener: (evt: IMediaPlayerSynchronizerEvent) => void
|
|
388
|
+
): this {
|
|
280
389
|
this.on(event, listener);
|
|
281
390
|
return this;
|
|
282
391
|
}
|
|
@@ -286,7 +395,10 @@ export class MediaPlayerSynchronizer extends EventEmitter {
|
|
|
286
395
|
* @param event Name of the event to remove.
|
|
287
396
|
* @param listener Function that was registered in call to `addEventListener()`.
|
|
288
397
|
*/
|
|
289
|
-
public removeEventListener(
|
|
398
|
+
public removeEventListener(
|
|
399
|
+
event: MediaPlayerSynchronizerEvents,
|
|
400
|
+
listener: (evt: IMediaPlayerSynchronizerEvent) => void
|
|
401
|
+
): this {
|
|
290
402
|
this.off(event, listener);
|
|
291
403
|
return this;
|
|
292
404
|
}
|
|
@@ -304,7 +416,10 @@ export class MediaPlayerSynchronizer extends EventEmitter {
|
|
|
304
416
|
|
|
305
417
|
// Subscribe to player events
|
|
306
418
|
for (const event of MediaPlayerSynchronizer.PLAYER_EVENTS) {
|
|
307
|
-
this._player.removeEventListener(
|
|
419
|
+
this._player.removeEventListener(
|
|
420
|
+
event,
|
|
421
|
+
this._onPlayerEvent
|
|
422
|
+
);
|
|
308
423
|
}
|
|
309
424
|
|
|
310
425
|
// Notify parent
|
|
@@ -317,19 +432,23 @@ export class MediaPlayerSynchronizer extends EventEmitter {
|
|
|
317
432
|
|
|
318
433
|
/**
|
|
319
434
|
* Begin a local seek operation.
|
|
320
|
-
*
|
|
435
|
+
*
|
|
321
436
|
* @remarks
|
|
322
|
-
* UI can call this when a user grabs a timeline scrubber and starts scrubbing the video to a
|
|
437
|
+
* UI can call this when a user grabs a timeline scrubber and starts scrubbing the video to a
|
|
323
438
|
* new playback position. The synchronizer will being a new suspension which temporarily
|
|
324
439
|
* disconnects the client for the rest of the group for synchronization purposes. Calling
|
|
325
440
|
* `endSeek()` will end the suspension and seek the group to the users final seek position.
|
|
326
441
|
*/
|
|
327
442
|
public beginSeek(): void {
|
|
328
443
|
if (this._seekSuspension) {
|
|
329
|
-
throw new Error(
|
|
444
|
+
throw new Error(
|
|
445
|
+
`MediaPlayerSynchronizer: cannot begin seek. A seek is already in progress.`
|
|
446
|
+
);
|
|
330
447
|
}
|
|
331
448
|
|
|
332
|
-
this._logger.sendTelemetryEvent(
|
|
449
|
+
this._logger.sendTelemetryEvent(
|
|
450
|
+
TelemetryEvents.MediaPlayerSynchronizer.BeginSeekCalled
|
|
451
|
+
);
|
|
333
452
|
this._seekSuspension = this._mediaSession.coordinator.beginSuspension();
|
|
334
453
|
}
|
|
335
454
|
|
|
@@ -339,7 +458,9 @@ export class MediaPlayerSynchronizer extends EventEmitter {
|
|
|
339
458
|
*/
|
|
340
459
|
public endSeek(seekTo: number): void {
|
|
341
460
|
if (!this._seekSuspension) {
|
|
342
|
-
throw new Error(
|
|
461
|
+
throw new Error(
|
|
462
|
+
`MediaPlayerSynchronizer: cannot end seek. No seek is in progress.`
|
|
463
|
+
);
|
|
343
464
|
}
|
|
344
465
|
|
|
345
466
|
// Seek player to new time immediately.
|
|
@@ -350,48 +471,58 @@ export class MediaPlayerSynchronizer extends EventEmitter {
|
|
|
350
471
|
const suspension = this._seekSuspension;
|
|
351
472
|
this._seekSuspension = undefined;
|
|
352
473
|
|
|
353
|
-
this._logger.sendTelemetryEvent(
|
|
474
|
+
this._logger.sendTelemetryEvent(
|
|
475
|
+
TelemetryEvents.MediaPlayerSynchronizer.EndSeekCalled,
|
|
476
|
+
null,
|
|
477
|
+
{
|
|
478
|
+
position: seekTo,
|
|
479
|
+
}
|
|
480
|
+
);
|
|
354
481
|
suspension.end(seekTo);
|
|
355
482
|
|
|
356
|
-
this.dispatchUserAction({action:
|
|
483
|
+
this.dispatchUserAction({ action: "seekto", seekTime: seekTo });
|
|
357
484
|
}
|
|
358
485
|
|
|
359
486
|
/**
|
|
360
487
|
* Tells the group to begin playing the current video.
|
|
361
|
-
*
|
|
488
|
+
*
|
|
362
489
|
* @remarks
|
|
363
|
-
* For proper operation apps should avoid calling `mediaSession.coordinator.play()` directly
|
|
364
|
-
* and instead use the synchronizers `play()` method.
|
|
490
|
+
* For proper operation apps should avoid calling `mediaSession.coordinator.play()` directly
|
|
491
|
+
* and instead use the synchronizers `play()` method.
|
|
365
492
|
*/
|
|
366
493
|
public play(): void {
|
|
367
|
-
this._logger.sendTelemetryEvent(
|
|
368
|
-
|
|
494
|
+
this._logger.sendTelemetryEvent(
|
|
495
|
+
TelemetryEvents.MediaPlayerSynchronizer.PlayCalled
|
|
496
|
+
);
|
|
497
|
+
this._expectedPlaybackState = "playing";
|
|
369
498
|
this._mediaSession.coordinator.play();
|
|
370
499
|
|
|
371
|
-
this.dispatchUserAction({action:
|
|
500
|
+
this.dispatchUserAction({ action: "play" });
|
|
372
501
|
}
|
|
373
502
|
|
|
374
503
|
/**
|
|
375
504
|
* Tells the group to pause the current video.
|
|
376
|
-
*
|
|
505
|
+
*
|
|
377
506
|
* @remarks
|
|
378
|
-
* For proper operation apps should avoid calling `mediaSession.coordinator.pause()` directly
|
|
379
|
-
* and instead use the synchronizers `pause()` method.
|
|
507
|
+
* For proper operation apps should avoid calling `mediaSession.coordinator.pause()` directly
|
|
508
|
+
* and instead use the synchronizers `pause()` method.
|
|
380
509
|
*/
|
|
381
510
|
public pause(): void {
|
|
382
|
-
this._logger.sendTelemetryEvent(
|
|
383
|
-
|
|
511
|
+
this._logger.sendTelemetryEvent(
|
|
512
|
+
TelemetryEvents.MediaPlayerSynchronizer.PauseCalled
|
|
513
|
+
);
|
|
514
|
+
this._expectedPlaybackState = "paused";
|
|
384
515
|
this._mediaSession.coordinator.pause();
|
|
385
516
|
|
|
386
|
-
this.dispatchUserAction({action:
|
|
517
|
+
this.dispatchUserAction({ action: "pause" });
|
|
387
518
|
}
|
|
388
519
|
|
|
389
520
|
/**
|
|
390
521
|
* Tells the group to seek the current video to a new playback position.
|
|
391
|
-
*
|
|
522
|
+
*
|
|
392
523
|
* @remarks
|
|
393
|
-
* For proper operation apps should avoid calling `mediaSession.coordinator.seekTo()` directly
|
|
394
|
-
* and instead use the synchronizers `seekTo()` method.
|
|
524
|
+
* For proper operation apps should avoid calling `mediaSession.coordinator.seekTo()` directly
|
|
525
|
+
* and instead use the synchronizers `seekTo()` method.
|
|
395
526
|
*/
|
|
396
527
|
public seekTo(time: number): void {
|
|
397
528
|
// Always seek player to new time.
|
|
@@ -399,58 +530,98 @@ export class MediaPlayerSynchronizer extends EventEmitter {
|
|
|
399
530
|
// location.
|
|
400
531
|
this._player.currentTime = time;
|
|
401
532
|
|
|
402
|
-
this._logger.sendTelemetryEvent(
|
|
533
|
+
this._logger.sendTelemetryEvent(
|
|
534
|
+
TelemetryEvents.MediaPlayerSynchronizer.SeekToCalled,
|
|
535
|
+
null,
|
|
536
|
+
{ position: time }
|
|
537
|
+
);
|
|
403
538
|
this._mediaSession.coordinator.seekTo(time);
|
|
404
539
|
|
|
405
|
-
this.dispatchUserAction({action:
|
|
540
|
+
this.dispatchUserAction({ action: "seekto", seekTime: time });
|
|
406
541
|
}
|
|
407
542
|
|
|
408
543
|
/**
|
|
409
544
|
* Tells the group to change to a new track.
|
|
410
|
-
*
|
|
545
|
+
*
|
|
411
546
|
* @remarks
|
|
412
|
-
* For proper operation apps should avoid calling `mediaSession.coordinator.setTrack()` directly
|
|
413
|
-
* and instead use the synchronizers `setTrack()` method.
|
|
547
|
+
* For proper operation apps should avoid calling `mediaSession.coordinator.setTrack()` directly
|
|
548
|
+
* and instead use the synchronizers `setTrack()` method.
|
|
414
549
|
*/
|
|
415
|
-
public setTrack(
|
|
416
|
-
|
|
550
|
+
public setTrack(
|
|
551
|
+
track: ExtendedMediaMetadata,
|
|
552
|
+
waitPoints?: CoordinationWaitPoint[]
|
|
553
|
+
): void {
|
|
554
|
+
this._logger.sendTelemetryEvent(
|
|
555
|
+
TelemetryEvents.MediaPlayerSynchronizer.SetTrackCalled
|
|
556
|
+
);
|
|
417
557
|
this._mediaSession.coordinator.setTrack(track, waitPoints);
|
|
418
558
|
|
|
419
|
-
this.dispatchUserAction({action:
|
|
559
|
+
this.dispatchUserAction({ action: "settrack", metadata: track });
|
|
420
560
|
}
|
|
421
561
|
|
|
422
562
|
/**
|
|
423
563
|
* Updates the current tracks data object.
|
|
424
|
-
*
|
|
564
|
+
*
|
|
425
565
|
* @remarks
|
|
426
|
-
* For proper operation apps should avoid calling `mediaSession.coordinator.setTrackData()` directly
|
|
427
|
-
* and instead use the synchronizers `setTrackData()` method.
|
|
566
|
+
* For proper operation apps should avoid calling `mediaSession.coordinator.setTrackData()` directly
|
|
567
|
+
* and instead use the synchronizers `setTrackData()` method.
|
|
428
568
|
*/
|
|
429
|
-
public setTrackData(data: object|null): void {
|
|
430
|
-
this._logger.sendTelemetryEvent(
|
|
569
|
+
public setTrackData(data: object | null): void {
|
|
570
|
+
this._logger.sendTelemetryEvent(
|
|
571
|
+
TelemetryEvents.MediaPlayerSynchronizer.SetTrackDataCalled
|
|
572
|
+
);
|
|
431
573
|
this._trackData = data;
|
|
432
574
|
this._mediaSession.coordinator.setTrackData(data);
|
|
433
575
|
|
|
434
|
-
this.dispatchUserAction({action:
|
|
576
|
+
this.dispatchUserAction({ action: "datachange", data: data });
|
|
435
577
|
}
|
|
436
578
|
|
|
437
|
-
private dispatchGroupAction(
|
|
579
|
+
private dispatchGroupAction(
|
|
580
|
+
details: ExtendedMediaSessionActionDetails,
|
|
581
|
+
delay = false,
|
|
582
|
+
error: Error | undefined
|
|
583
|
+
): void {
|
|
438
584
|
if (delay) {
|
|
439
|
-
setTimeout(
|
|
585
|
+
setTimeout(
|
|
586
|
+
() =>
|
|
587
|
+
this.emit(MediaPlayerSynchronizerEvents.groupaction, {
|
|
588
|
+
type: MediaPlayerSynchronizerEvents.groupaction,
|
|
589
|
+
details: details,
|
|
590
|
+
playerError: error,
|
|
591
|
+
}),
|
|
592
|
+
50
|
|
593
|
+
);
|
|
440
594
|
} else {
|
|
441
|
-
this.emit(MediaPlayerSynchronizerEvents.groupaction, {
|
|
595
|
+
this.emit(MediaPlayerSynchronizerEvents.groupaction, {
|
|
596
|
+
type: MediaPlayerSynchronizerEvents.groupaction,
|
|
597
|
+
details: details,
|
|
598
|
+
error,
|
|
599
|
+
});
|
|
442
600
|
}
|
|
443
601
|
}
|
|
444
602
|
|
|
445
|
-
private dispatchUserAction(
|
|
603
|
+
private dispatchUserAction(
|
|
604
|
+
details: ExtendedMediaSessionActionDetails,
|
|
605
|
+
delay = false
|
|
606
|
+
): void {
|
|
446
607
|
if (delay) {
|
|
447
|
-
setTimeout(
|
|
608
|
+
setTimeout(
|
|
609
|
+
() =>
|
|
610
|
+
this.emit(MediaPlayerSynchronizerEvents.useraction, {
|
|
611
|
+
type: MediaPlayerSynchronizerEvents.useraction,
|
|
612
|
+
details: details,
|
|
613
|
+
}),
|
|
614
|
+
50
|
|
615
|
+
);
|
|
448
616
|
} else {
|
|
449
|
-
this.emit(MediaPlayerSynchronizerEvents.useraction, {
|
|
617
|
+
this.emit(MediaPlayerSynchronizerEvents.useraction, {
|
|
618
|
+
type: MediaPlayerSynchronizerEvents.useraction,
|
|
619
|
+
details: details,
|
|
620
|
+
});
|
|
450
621
|
}
|
|
451
622
|
}
|
|
452
623
|
|
|
453
624
|
private async catchupPlayer(time: number): Promise<void> {
|
|
454
625
|
this._player.currentTime = time;
|
|
455
626
|
}
|
|
456
|
-
}
|
|
627
|
+
}
|