@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.
- package/.vscode/settings.json +2 -0
- package/LICENSE +87 -0
- package/README.md +108 -92
- package/bin/EphemeralMediaSession.d.ts +51 -4
- package/bin/EphemeralMediaSession.d.ts.map +1 -1
- package/bin/EphemeralMediaSession.js +91 -44
- package/bin/EphemeralMediaSession.js.map +1 -1
- package/bin/EphemeralMediaSessionCoordinator.d.ts +173 -6
- package/bin/EphemeralMediaSessionCoordinator.d.ts.map +1 -1
- package/bin/EphemeralMediaSessionCoordinator.js +170 -23
- package/bin/EphemeralMediaSessionCoordinator.js.map +1 -1
- package/bin/EphemeralMediaSessionCoordinatorSuspension.d.ts +1 -1
- package/bin/EphemeralMediaSessionCoordinatorSuspension.js +1 -1
- package/bin/IMediaPlayer.d.ts +1 -1
- package/bin/IMediaPlayer.js +1 -1
- package/bin/MediaPlayerSynchronizer.d.ts +95 -6
- package/bin/MediaPlayerSynchronizer.d.ts.map +1 -1
- package/bin/MediaPlayerSynchronizer.js +93 -31
- package/bin/MediaPlayerSynchronizer.js.map +1 -1
- package/bin/MediaSessionActionThrottler.d.ts +1 -1
- package/bin/MediaSessionActionThrottler.js +1 -1
- package/bin/MediaSessionExtensions.d.ts +1 -1
- package/bin/MediaSessionExtensions.js +1 -1
- package/bin/RepeatedActionThrottler.d.ts +1 -1
- package/bin/RepeatedActionThrottler.js +1 -1
- package/bin/VolumeLimiter.d.ts +2 -2
- package/bin/VolumeLimiter.js +2 -2
- package/bin/index.d.ts +1 -1
- package/bin/index.js +1 -1
- package/bin/internals/GroupCoordinatorState.d.ts +1 -1
- package/bin/internals/GroupCoordinatorState.js +1 -1
- package/bin/internals/GroupPlaybackPosition.d.ts +1 -1
- package/bin/internals/GroupPlaybackPosition.js +1 -1
- package/bin/internals/GroupPlaybackTrack.d.ts +1 -1
- package/bin/internals/GroupPlaybackTrack.js +1 -1
- package/bin/internals/GroupPlaybackTrackData.d.ts +1 -1
- package/bin/internals/GroupPlaybackTrackData.js +1 -1
- package/bin/internals/GroupTransportState.d.ts +1 -1
- package/bin/internals/GroupTransportState.js +1 -1
- package/bin/internals/consts.d.ts +1 -1
- package/bin/internals/consts.js +1 -1
- package/bin/internals/index.d.ts +1 -1
- package/bin/internals/index.js +1 -1
- package/package.json +13 -4
- package/src/EphemeralMediaSession.ts +95 -48
- package/src/EphemeralMediaSessionCoordinator.ts +190 -26
- package/src/EphemeralMediaSessionCoordinatorSuspension.ts +1 -1
- package/src/IMediaPlayer.ts +1 -1
- package/src/MediaPlayerSynchronizer.ts +101 -33
- package/src/MediaSessionActionThrottler.ts +1 -1
- package/src/MediaSessionExtensions.ts +1 -1
- package/src/RepeatedActionThrottler.ts +1 -1
- package/src/VolumeLimiter.ts +2 -2
- package/src/index.ts +1 -1
- package/src/internals/GroupCoordinatorState.ts +1 -1
- package/src/internals/GroupPlaybackPosition.ts +1 -1
- package/src/internals/GroupPlaybackTrack.ts +1 -1
- package/src/internals/GroupPlaybackTrackData.ts +1 -1
- package/src/internals/GroupTransportState.ts +1 -1
- package/src/internals/consts.ts +1 -1
- package/src/internals/index.ts +1 -1
- package/src/test/GroupPlaybackPosition.spec.ts +1 -1
- package/src/test/GroupPlaybackTrack.spec.ts +1 -1
- package/src/test/SharedMediaSession.spec.ts +1 -1
- 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
|
|
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
|
|
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
|
package/bin/internals/consts.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
4
|
-
* Licensed under the
|
|
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;
|
package/bin/internals/index.d.ts
CHANGED
package/bin/internals/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
4
|
-
* Licensed under the
|
|
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.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Live Share classes that synchronize media playback across clients.",
|
|
5
5
|
"author": "Microsoft",
|
|
6
|
-
"license": "
|
|
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": "
|
|
18
|
-
"@microsoft/live-share": "0.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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.
|
|
217
|
+
if (!this._requestPlayerStateHandler) {
|
|
171
218
|
throw new Error(`SharedMediaSession: no getPlayerState callback configured.`);
|
|
172
219
|
}
|
|
173
220
|
|
|
174
|
-
return this.
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
|
3
|
+
* Licensed under the Microsoft Live Share SDK License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { MediaSessionCoordinatorSuspension, CoordinationWaitPoint } from './MediaSessionExtensions';
|