@microsoft/live-share-media 0.5.1 → 1.0.0-preview.10

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