@microsoft/live-share-media 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/.nycrc.json +10 -10
  2. package/LICENSE +90 -90
  3. package/README.md +135 -135
  4. package/bin/LiveMediaSession.d.ts.map +1 -1
  5. package/bin/LiveMediaSession.js +9 -1
  6. package/bin/LiveMediaSession.js.map +1 -1
  7. package/bin/LiveMediaSessionCoordinator.d.ts +18 -2
  8. package/bin/LiveMediaSessionCoordinator.d.ts.map +1 -1
  9. package/bin/LiveMediaSessionCoordinator.js +52 -13
  10. package/bin/LiveMediaSessionCoordinator.js.map +1 -1
  11. package/bin/MediaPlayerSynchronizer.d.ts +6 -4
  12. package/bin/MediaPlayerSynchronizer.d.ts.map +1 -1
  13. package/bin/MediaPlayerSynchronizer.js +41 -12
  14. package/bin/MediaPlayerSynchronizer.js.map +1 -1
  15. package/bin/MediaSessionExtensions.d.ts +17 -8
  16. package/bin/MediaSessionExtensions.d.ts.map +1 -1
  17. package/bin/MediaSessionExtensions.js +4 -0
  18. package/bin/MediaSessionExtensions.js.map +1 -1
  19. package/bin/internals/GroupCoordinatorState.d.ts +15 -1
  20. package/bin/internals/GroupCoordinatorState.d.ts.map +1 -1
  21. package/bin/internals/GroupCoordinatorState.js +123 -57
  22. package/bin/internals/GroupCoordinatorState.js.map +1 -1
  23. package/bin/internals/GroupPlaybackPosition.d.ts +3 -1
  24. package/bin/internals/GroupPlaybackPosition.d.ts.map +1 -1
  25. package/bin/internals/GroupPlaybackPosition.js +7 -3
  26. package/bin/internals/GroupPlaybackPosition.js.map +1 -1
  27. package/bin/internals/GroupPlaybackRate.d.ts +39 -0
  28. package/bin/internals/GroupPlaybackRate.d.ts.map +1 -0
  29. package/bin/internals/GroupPlaybackRate.js +62 -0
  30. package/bin/internals/GroupPlaybackRate.js.map +1 -0
  31. package/bin/internals/GroupPlaybackTrack.d.ts +2 -2
  32. package/bin/internals/GroupPlaybackTrack.d.ts.map +1 -1
  33. package/bin/internals/GroupPlaybackTrack.js +3 -2
  34. package/bin/internals/GroupPlaybackTrack.js.map +1 -1
  35. package/bin/internals/GroupPlaybackTrackData.d.ts +3 -3
  36. package/bin/internals/GroupPlaybackTrackData.d.ts.map +1 -1
  37. package/bin/internals/GroupPlaybackTrackData.js +12 -11
  38. package/bin/internals/GroupPlaybackTrackData.js.map +1 -1
  39. package/bin/internals/GroupTransportState.d.ts +6 -3
  40. package/bin/internals/GroupTransportState.d.ts.map +1 -1
  41. package/bin/internals/GroupTransportState.js +16 -11
  42. package/bin/internals/GroupTransportState.js.map +1 -1
  43. package/bin/internals/consts.d.ts +10 -3
  44. package/bin/internals/consts.d.ts.map +1 -1
  45. package/bin/internals/consts.js +10 -3
  46. package/bin/internals/consts.js.map +1 -1
  47. package/bin/internals/index.d.ts +1 -0
  48. package/bin/internals/index.d.ts.map +1 -1
  49. package/bin/internals/index.js +1 -0
  50. package/bin/internals/index.js.map +1 -1
  51. package/bin/internals/interfaces.d.ts +10 -0
  52. package/bin/internals/interfaces.d.ts.map +1 -0
  53. package/bin/internals/interfaces.js +3 -0
  54. package/bin/internals/interfaces.js.map +1 -0
  55. package/package.json +45 -45
  56. package/src/IMediaPlayer.ts +29 -29
  57. package/src/LiveMediaSession.ts +349 -336
  58. package/src/LiveMediaSessionCoordinator.ts +760 -678
  59. package/src/LiveMediaSessionCoordinatorSuspension.ts +39 -39
  60. package/src/MediaPlayerSynchronizer.ts +754 -714
  61. package/src/MediaSessionActionThrottler.ts +19 -19
  62. package/src/MediaSessionExtensions.ts +198 -187
  63. package/src/RepeatedActionThrottler.ts +76 -76
  64. package/src/VolumeManager.ts +200 -200
  65. package/src/index.ts +14 -14
  66. package/src/internals/GroupCoordinatorState.ts +802 -685
  67. package/src/internals/GroupPlaybackPosition.ts +235 -228
  68. package/src/internals/GroupPlaybackRate.ts +90 -0
  69. package/src/internals/GroupPlaybackTrack.ts +183 -182
  70. package/src/internals/GroupPlaybackTrackData.ts +105 -104
  71. package/src/internals/GroupTransportState.ts +179 -170
  72. package/src/internals/consts.ts +115 -102
  73. package/src/internals/errors.ts +42 -42
  74. package/src/internals/index.ts +13 -12
  75. package/src/internals/interfaces.ts +10 -0
  76. package/tsconfig.build.json +8 -8
  77. package/tsconfig.json +76 -76
  78. package/typedoc.json +10 -10
@@ -1,228 +1,235 @@
1
- /**
2
- * Copyright (c) Microsoft Corporation. All rights reserved.
3
- * Licensed under the Microsoft Live Share SDK License.
4
- */
5
-
6
- import {
7
- IRuntimeSignaler,
8
- TimeInterval,
9
- LiveShareRuntime,
10
- } from "@microsoft/live-share";
11
- import { GroupTransportState } from "./GroupTransportState";
12
- import { GroupPlaybackTrackEvents } from "./GroupPlaybackTrack";
13
- import {
14
- CoordinationWaitPoint,
15
- ExtendedMediaSessionPlaybackState,
16
- } from "../MediaSessionExtensions";
17
-
18
- /**
19
- * Per client position
20
- * @hidden
21
- */
22
- export interface ICurrentPlaybackPosition {
23
- playbackState: ExtendedMediaSessionPlaybackState;
24
- waitPoint?: CoordinationWaitPoint;
25
- position: number;
26
- duration?: number;
27
- timestamp: number;
28
- clientId: string;
29
- }
30
-
31
- /**
32
- * @hidden
33
- */
34
- export class GroupPlaybackPosition {
35
- private _transportState: GroupTransportState;
36
- private _runtime: IRuntimeSignaler;
37
- private _liveRuntime: LiveShareRuntime;
38
- private _updateInterval: TimeInterval;
39
- private _positions: Map<string, ICurrentPlaybackPosition>;
40
-
41
- constructor(
42
- transportState: GroupTransportState,
43
- runtime: IRuntimeSignaler,
44
- liveRuntime: LiveShareRuntime,
45
- updateInterval: TimeInterval
46
- ) {
47
- this._transportState = transportState;
48
- this._runtime = runtime;
49
- this._liveRuntime = liveRuntime;
50
- this._updateInterval = updateInterval;
51
- this._positions = new Map();
52
-
53
- // Listen for track change
54
- this._transportState.track.on(
55
- GroupPlaybackTrackEvents.trackChange,
56
- (_) => {
57
- // Reset position tracking and duration
58
- this._positions = new Map();
59
- this.mediaDuration = undefined;
60
- }
61
- );
62
- }
63
-
64
- /**
65
- * Returns the latest position info for the local client.
66
- */
67
- public get localPosition(): ICurrentPlaybackPosition | undefined {
68
- return this._positions.get(this._runtime.clientId!);
69
- }
70
-
71
- /**
72
- * Returns the total number of clients actively being tracked.
73
- */
74
- public get totalClients(): number {
75
- let cnt = 0;
76
- this.forEach((_) => cnt++);
77
- return cnt;
78
- }
79
-
80
- /**
81
- * Returns true if all clients being tracked are reporting that they have
82
- * either ended or haven't started.
83
- */
84
- public get trackEnded(): boolean {
85
- let playing = 0;
86
- this.forEach((position, projectedPosition) => {
87
- switch (position.playbackState) {
88
- case "none":
89
- case "ended":
90
- // not playing.
91
- break;
92
- default:
93
- playing++;
94
- break;
95
- }
96
- });
97
-
98
- return playing == 0;
99
- }
100
-
101
- /**
102
- * Returns the number of clients we're waiting for before we can stop waiting.
103
- */
104
- public get clientsWaiting(): number {
105
- let cnt = 0;
106
- const waitPoint = this.localPosition?.waitPoint;
107
- if (
108
- waitPoint &&
109
- (waitPoint.maxClients == undefined ||
110
- this.totalClients <= waitPoint.maxClients)
111
- ) {
112
- this.forEach((position, projectedPosition) => {
113
- if (
114
- position.playbackState == "suspended" &&
115
- position.waitPoint
116
- ) {
117
- cnt++;
118
- } else if (position.position < waitPoint.position) {
119
- cnt++;
120
- }
121
- });
122
- }
123
-
124
- return cnt;
125
- }
126
-
127
- /**
128
- * Returns the max playback position relative to the start position.
129
- *
130
- * @remarks
131
- * This is called when calculating the current seekTo position.
132
- */
133
- public get maxPosition(): number {
134
- if (this._transportState.playbackState == "playing") {
135
- const now = this._liveRuntime.getTimestamp();
136
- const projected =
137
- this._transportState.startPosition +
138
- (now - this._transportState.timestamp) / 1000;
139
- return this.limitProjectedPosition(projected);
140
- } else {
141
- return this._transportState.startPosition;
142
- }
143
- }
144
-
145
- /**
146
- * Optional media duration if known.
147
- */
148
- public mediaDuration?: number;
149
-
150
- public get targetPosition(): number {
151
- if (this._transportState.playbackState == "playing") {
152
- return this.getMostProgressedPosition();
153
- } else {
154
- return this._transportState.startPosition;
155
- }
156
- }
157
-
158
- /**
159
- * Enumerates every reported playback position.
160
- * @param callbackFn Function applied to each position,
161
- */
162
- public forEach(
163
- callbackFn: (
164
- position: ICurrentPlaybackPosition,
165
- projectedPosition: number
166
- ) => void
167
- ): void {
168
- const now = this._liveRuntime.getTimestamp();
169
- const ignoreBefore = now - this._updateInterval.milliseconds * 2;
170
- const shouldProject = !this._transportState.track.metadata?.liveStream;
171
- this._positions.forEach((position, _) => {
172
- // Ignore any old updates
173
- if (position.timestamp > ignoreBefore) {
174
- // Compute projected playback position
175
- // - This computation does not take into account future wait points.
176
- const projected =
177
- position.playbackState == "playing" && shouldProject
178
- ? position.position + (now - position.timestamp) / 1000
179
- : position.position;
180
- callbackFn(position, this.limitProjectedPosition(projected));
181
- }
182
- });
183
- }
184
-
185
- public UpdatePlaybackPosition(position: ICurrentPlaybackPosition): void {
186
- // Update duration
187
- if (position.duration != undefined) {
188
- this.mediaDuration = position.duration;
189
- }
190
-
191
- // Save last position
192
- if (this._positions.has(position.clientId)) {
193
- // Only update if newer position
194
- const current = this._positions.get(position.clientId)!;
195
- if (position.timestamp >= current.timestamp) {
196
- this._positions.set(position.clientId, position);
197
- }
198
- } else {
199
- this._positions.set(position.clientId, position);
200
- }
201
- }
202
-
203
- private getMostProgressedPosition(): number {
204
- // Compute the max possible position for the current transport state.
205
- // - This is needed to properly handle seeking backwards in time. Some playback heads may
206
- // not have performed their seek yet and will therefore be ahead of the local player.
207
- const maxPosition = this.maxPosition;
208
-
209
- // Compute max progress
210
- let progress = -1;
211
- this.forEach((position, projectedPosition) => {
212
- if (
213
- projectedPosition <= maxPosition &&
214
- projectedPosition > progress
215
- ) {
216
- progress = projectedPosition;
217
- }
218
- });
219
-
220
- return progress;
221
- }
222
-
223
- private limitProjectedPosition(position: number): number {
224
- return this.mediaDuration != undefined
225
- ? Math.min(position, this.mediaDuration)
226
- : position;
227
- }
228
- }
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the Microsoft Live Share SDK License.
4
+ */
5
+
6
+ import {
7
+ IRuntimeSignaler,
8
+ TimeInterval,
9
+ LiveShareRuntime,
10
+ } from "@microsoft/live-share";
11
+ import { GroupTransportState } from "./GroupTransportState";
12
+ import { GroupPlaybackTrackEvents } from "./GroupPlaybackTrack";
13
+ import {
14
+ CoordinationWaitPoint,
15
+ ExtendedMediaSessionPlaybackState,
16
+ } from "../MediaSessionExtensions";
17
+ import { GroupPlaybackRate } from "./GroupPlaybackRate";
18
+
19
+ /**
20
+ * Per client position
21
+ * @hidden
22
+ */
23
+ export interface ICurrentPlaybackPosition {
24
+ playbackState: ExtendedMediaSessionPlaybackState;
25
+ waitPoint?: CoordinationWaitPoint;
26
+ position: number;
27
+ duration?: number;
28
+ timestamp: number;
29
+ clientId: string;
30
+ }
31
+
32
+ /**
33
+ * @hidden
34
+ */
35
+ export class GroupPlaybackPosition {
36
+ private _transportState: GroupTransportState;
37
+ private _playbackRate: GroupPlaybackRate;
38
+ private _runtime: IRuntimeSignaler;
39
+ private _liveRuntime: LiveShareRuntime;
40
+ private _updateInterval: TimeInterval;
41
+ private _positions: Map<string, ICurrentPlaybackPosition>;
42
+
43
+ constructor(
44
+ transportState: GroupTransportState,
45
+ playbackRate: GroupPlaybackRate,
46
+ runtime: IRuntimeSignaler,
47
+ liveRuntime: LiveShareRuntime,
48
+ updateInterval: TimeInterval
49
+ ) {
50
+ this._transportState = transportState;
51
+ this._playbackRate = playbackRate;
52
+ this._runtime = runtime;
53
+ this._liveRuntime = liveRuntime;
54
+ this._updateInterval = updateInterval;
55
+ this._positions = new Map();
56
+
57
+ // Listen for track change
58
+ this._transportState.track.on(
59
+ GroupPlaybackTrackEvents.trackChange,
60
+ (_) => {
61
+ // Reset position tracking and duration
62
+ this._positions = new Map();
63
+ this.mediaDuration = undefined;
64
+ }
65
+ );
66
+ }
67
+
68
+ /**
69
+ * Returns the latest position info for the local client.
70
+ */
71
+ public get localPosition(): ICurrentPlaybackPosition | undefined {
72
+ return this._positions.get(this._runtime.clientId!);
73
+ }
74
+
75
+ /**
76
+ * Returns the total number of clients actively being tracked.
77
+ */
78
+ public get totalClients(): number {
79
+ let cnt = 0;
80
+ this.forEach((_) => cnt++);
81
+ return cnt;
82
+ }
83
+
84
+ /**
85
+ * Returns true if all clients being tracked are reporting that they have
86
+ * either ended or haven't started.
87
+ */
88
+ public get trackEnded(): boolean {
89
+ let playing = 0;
90
+ this.forEach((position, projectedPosition) => {
91
+ switch (position.playbackState) {
92
+ case "none":
93
+ case "ended":
94
+ // not playing.
95
+ break;
96
+ default:
97
+ playing++;
98
+ break;
99
+ }
100
+ });
101
+
102
+ return playing == 0;
103
+ }
104
+
105
+ /**
106
+ * Returns the number of clients we're waiting for before we can stop waiting.
107
+ */
108
+ public get clientsWaiting(): number {
109
+ let cnt = 0;
110
+ const waitPoint = this.localPosition?.waitPoint;
111
+ if (
112
+ waitPoint &&
113
+ (waitPoint.maxClients == undefined ||
114
+ this.totalClients <= waitPoint.maxClients)
115
+ ) {
116
+ this.forEach((position, projectedPosition) => {
117
+ if (
118
+ position.playbackState == "suspended" &&
119
+ position.waitPoint
120
+ ) {
121
+ cnt++;
122
+ } else if (position.position < waitPoint.position) {
123
+ cnt++;
124
+ }
125
+ });
126
+ }
127
+
128
+ return cnt;
129
+ }
130
+
131
+ /**
132
+ * Returns the max playback position relative to the start position.
133
+ *
134
+ * @remarks
135
+ * This is called when calculating the current seekTo position.
136
+ */
137
+ public get maxPosition(): number {
138
+ if (this._transportState.playbackState == "playing") {
139
+ const now = this._liveRuntime.getTimestamp();
140
+ const projected =
141
+ this._transportState.startPosition +
142
+ ((now - this._transportState.timestamp) / 1000) *
143
+ this._playbackRate.rate;
144
+ return this.limitProjectedPosition(projected);
145
+ } else {
146
+ return this._transportState.startPosition;
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Optional media duration if known.
152
+ */
153
+ public mediaDuration?: number;
154
+
155
+ public get targetPosition(): number {
156
+ if (this._transportState.playbackState == "playing") {
157
+ return this.getMostProgressedPosition();
158
+ } else {
159
+ return this._transportState.startPosition;
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Enumerates every reported playback position.
165
+ * @param callbackFn Function applied to each position,
166
+ */
167
+ public forEach(
168
+ callbackFn: (
169
+ position: ICurrentPlaybackPosition,
170
+ projectedPosition: number
171
+ ) => void
172
+ ): void {
173
+ const now = this._liveRuntime.getTimestamp();
174
+ const ignoreBefore = now - this._updateInterval.milliseconds * 2;
175
+ const shouldProject = !this._transportState.track.metadata?.liveStream;
176
+ this._positions.forEach((position, _) => {
177
+ // Ignore any old updates
178
+ if (position.timestamp > ignoreBefore) {
179
+ // Compute projected playback position
180
+ // - This computation does not take into account future wait points.
181
+ const projected =
182
+ position.playbackState == "playing" && shouldProject
183
+ ? position.position +
184
+ ((now - position.timestamp) / 1000) *
185
+ this._playbackRate.rate
186
+ : position.position;
187
+ callbackFn(position, this.limitProjectedPosition(projected));
188
+ }
189
+ });
190
+ }
191
+
192
+ public UpdatePlaybackPosition(position: ICurrentPlaybackPosition): void {
193
+ // Update duration
194
+ if (position.duration != undefined) {
195
+ this.mediaDuration = position.duration;
196
+ }
197
+
198
+ // Save last position
199
+ if (this._positions.has(position.clientId)) {
200
+ // Only update if newer position
201
+ const current = this._positions.get(position.clientId)!;
202
+ if (position.timestamp >= current.timestamp) {
203
+ this._positions.set(position.clientId, position);
204
+ }
205
+ } else {
206
+ this._positions.set(position.clientId, position);
207
+ }
208
+ }
209
+
210
+ private getMostProgressedPosition(): number {
211
+ // Compute the max possible position for the current transport state.
212
+ // - This is needed to properly handle seeking backwards in time. Some playback heads may
213
+ // not have performed their seek yet and will therefore be ahead of the local player.
214
+ const maxPosition = this.maxPosition;
215
+
216
+ // Compute max progress
217
+ let progress = -1;
218
+ this.forEach((position, projectedPosition) => {
219
+ if (
220
+ projectedPosition <= maxPosition &&
221
+ projectedPosition > progress
222
+ ) {
223
+ progress = projectedPosition;
224
+ }
225
+ });
226
+
227
+ return progress;
228
+ }
229
+
230
+ private limitProjectedPosition(position: number): number {
231
+ return this.mediaDuration != undefined
232
+ ? Math.min(position, this.mediaDuration)
233
+ : position;
234
+ }
235
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the Microsoft Live Share SDK License.
4
+ */
5
+
6
+ import EventEmitter from "events";
7
+ import { ExtendedMediaSessionActionSource } from "../MediaSessionExtensions";
8
+ import { IGroupStateEvent } from "./interfaces";
9
+
10
+ /**
11
+ * @hidden
12
+ */
13
+ export interface IPlaybackRate {
14
+ playbackRate: number;
15
+ timestamp: number;
16
+ clientId: string;
17
+ }
18
+
19
+ /**
20
+ * @hidden
21
+ */
22
+ export enum PlaybackRateEvents {
23
+ rateChange = "rateChange",
24
+ }
25
+
26
+ /**
27
+ * @hidden
28
+ */
29
+ export interface IPlaybackRateChangeEvent extends IGroupStateEvent {
30
+ playbackRate: number;
31
+ }
32
+
33
+ /**
34
+ * @hidden
35
+ */
36
+ export class GroupPlaybackRate extends EventEmitter {
37
+ private _current: IPlaybackRate;
38
+
39
+ constructor() {
40
+ super();
41
+ this._current = { playbackRate: 1.0, timestamp: 0, clientId: "" };
42
+ }
43
+
44
+ public get current(): Readonly<IPlaybackRate> {
45
+ return this._current;
46
+ }
47
+
48
+ public get rate(): number {
49
+ return this.current.playbackRate;
50
+ }
51
+
52
+ public updatePlaybackRate(
53
+ newRate: IPlaybackRate,
54
+ source: ExtendedMediaSessionActionSource
55
+ ): boolean {
56
+ // Ignore state changes that are older
57
+ const current = this.current;
58
+ if (newRate.timestamp < current.timestamp) {
59
+ return false;
60
+ }
61
+
62
+ // Ignore state changes that have the same timestamp and the clientId sorts higher.
63
+ if (
64
+ newRate.timestamp == current.timestamp &&
65
+ newRate.clientId.localeCompare(current.clientId) > 0
66
+ ) {
67
+ return false;
68
+ }
69
+
70
+ // Ignore state changes for same data object
71
+ if (current.playbackRate == newRate.playbackRate) {
72
+ return false;
73
+ }
74
+
75
+ // Update current data
76
+ this._current = newRate;
77
+
78
+ const event: IPlaybackRateChangeEvent = {
79
+ name: PlaybackRateEvents.rateChange,
80
+ clientId: newRate.clientId,
81
+ playbackRate: newRate.playbackRate,
82
+ source,
83
+ };
84
+
85
+ // Notify listeners
86
+ this.emit(PlaybackRateEvents.rateChange, event);
87
+
88
+ return true;
89
+ }
90
+ }