@microsoft/live-share-media 0.5.0 → 1.0.0-preview.1

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 (54) hide show
  1. package/.vscode/launch.json +29 -0
  2. package/.vscode/settings.json +2 -0
  3. package/README.md +6 -5
  4. package/bin/LiveMediaSession.d.ts +95 -0
  5. package/bin/LiveMediaSession.d.ts.map +1 -0
  6. package/bin/LiveMediaSession.js +217 -0
  7. package/bin/LiveMediaSession.js.map +1 -0
  8. package/bin/LiveMediaSessionCoordinator.d.ts +228 -0
  9. package/bin/LiveMediaSessionCoordinator.d.ts.map +1 -0
  10. package/bin/LiveMediaSessionCoordinator.js +396 -0
  11. package/bin/LiveMediaSessionCoordinator.js.map +1 -0
  12. package/bin/LiveMediaSessionCoordinatorSuspension.d.ts +20 -0
  13. package/bin/LiveMediaSessionCoordinatorSuspension.d.ts.map +1 -0
  14. package/bin/LiveMediaSessionCoordinatorSuspension.js +28 -0
  15. package/bin/LiveMediaSessionCoordinatorSuspension.js.map +1 -0
  16. package/bin/MediaPlayerSynchronizer.d.ts +6 -6
  17. package/bin/MediaPlayerSynchronizer.d.ts.map +1 -1
  18. package/bin/MediaPlayerSynchronizer.js +12 -7
  19. package/bin/MediaPlayerSynchronizer.js.map +1 -1
  20. package/bin/VolumeManager.d.ts +84 -0
  21. package/bin/VolumeManager.d.ts.map +1 -0
  22. package/bin/VolumeManager.js +173 -0
  23. package/bin/VolumeManager.js.map +1 -0
  24. package/bin/index.d.ts +4 -4
  25. package/bin/index.d.ts.map +1 -1
  26. package/bin/index.js +4 -4
  27. package/bin/index.js.map +1 -1
  28. package/bin/internals/GroupCoordinatorState.d.ts +6 -6
  29. package/bin/internals/GroupCoordinatorState.d.ts.map +1 -1
  30. package/bin/internals/GroupCoordinatorState.js +6 -6
  31. package/bin/internals/GroupCoordinatorState.js.map +1 -1
  32. package/bin/internals/GroupPlaybackPosition.d.ts.map +1 -1
  33. package/bin/internals/GroupPlaybackPosition.js +2 -2
  34. package/bin/internals/GroupPlaybackPosition.js.map +1 -1
  35. package/bin/internals/GroupPlaybackTrack.d.ts +1 -1
  36. package/bin/internals/GroupPlaybackTrack.d.ts.map +1 -1
  37. package/bin/internals/GroupTransportState.d.ts +1 -1
  38. package/bin/internals/GroupTransportState.d.ts.map +1 -1
  39. package/bin/internals/GroupTransportState.js +1 -1
  40. package/bin/internals/GroupTransportState.js.map +1 -1
  41. package/package.json +2 -2
  42. package/src/{EphemeralMediaSession.ts → LiveMediaSession.ts} +13 -23
  43. package/src/{EphemeralMediaSessionCoordinator.ts → LiveMediaSessionCoordinator.ts} +41 -41
  44. package/src/{EphemeralMediaSessionCoordinatorSuspension.ts → LiveMediaSessionCoordinatorSuspension.ts} +1 -1
  45. package/src/MediaPlayerSynchronizer.ts +20 -14
  46. package/src/VolumeManager.ts +185 -0
  47. package/src/index.ts +4 -4
  48. package/src/internals/GroupCoordinatorState.ts +13 -13
  49. package/src/internals/GroupPlaybackPosition.ts +3 -3
  50. package/src/internals/GroupPlaybackTrack.ts +1 -1
  51. package/src/internals/GroupTransportState.ts +3 -3
  52. package/src/test/SharedMediaSession.spec.ts +2 -2
  53. package/src/test/VolumeManager.spec.ts +212 -0
  54. package/src/VolumeLimiter.ts +0 -182
@@ -3,13 +3,13 @@
3
3
  * Licensed under the Microsoft Live Share SDK License.
4
4
  */
5
5
 
6
- import { IEvent, IEphemeralEvent, EphemeralEvent, TimeInterval, IRuntimeSignaler, EphemeralTelemetryLogger } from '@microsoft/live-share';
6
+ import { IEvent, ILiveEvent, LiveEvent, TimeInterval, IRuntimeSignaler, LiveTelemetryLogger } from '@microsoft/live-share';
7
7
  import EventEmitter from 'events';
8
8
  import { ExtendedMediaMetadata, CoordinationWaitPoint, ExtendedMediaSessionPlaybackState, ExtendedMediaSessionActionDetails } from '../MediaSessionExtensions';
9
9
  import { GroupPlaybackTrack, GroupPlaybackTrackEvents, IPlaybackTrack } from './GroupPlaybackTrack';
10
10
  import { GroupTransportState, ITransportState } from './GroupTransportState';
11
11
  import { GroupPlaybackPosition, ICurrentPlaybackPosition } from './GroupPlaybackPosition';
12
- import { IMediaPlayerState } from '../EphemeralMediaSessionCoordinator';
12
+ import { IMediaPlayerState } from '../LiveMediaSessionCoordinator';
13
13
  import { GroupTransportStateEvents } from './GroupTransportState';
14
14
  import { GroupPlaybackTrackData, PlaybackTrackDataEvents, IPlaybackTrackData } from './GroupPlaybackTrackData';
15
15
  import { TelemetryEvents } from './consts';
@@ -17,7 +17,7 @@ import { TelemetryEvents } from './consts';
17
17
  /**
18
18
  * @hidden
19
19
  */
20
- export interface IPositionUpdateEvent extends IEphemeralEvent {
20
+ export interface IPositionUpdateEvent extends ILiveEvent {
21
21
  track: IPlaybackTrack;
22
22
  trackData: IPlaybackTrackData;
23
23
  transport: ITransportState;
@@ -29,7 +29,7 @@ export interface IPositionUpdateEvent extends IEphemeralEvent {
29
29
  /**
30
30
  * @hidden
31
31
  */
32
- export interface ITransportCommandEvent extends IEphemeralEvent {
32
+ export interface ITransportCommandEvent extends ILiveEvent {
33
33
  track: IPlaybackTrack;
34
34
  position: number;
35
35
  }
@@ -37,7 +37,7 @@ export interface ITransportCommandEvent extends IEphemeralEvent {
37
37
  /**
38
38
  * @hidden
39
39
  */
40
- export interface ISetTrackEvent extends IEphemeralEvent {
40
+ export interface ISetTrackEvent extends ILiveEvent {
41
41
  metadata: ExtendedMediaMetadata|null;
42
42
  waitPoints: CoordinationWaitPoint[];
43
43
  }
@@ -45,7 +45,7 @@ export interface ISetTrackEvent extends IEphemeralEvent {
45
45
  /**
46
46
  * @hidden
47
47
  */
48
- export interface ISetTrackDataEvent extends IEphemeralEvent {
48
+ export interface ISetTrackDataEvent extends ILiveEvent {
49
49
  data: object|null;
50
50
  }
51
51
 
@@ -69,7 +69,7 @@ export enum GroupCoordinatorStateEvents {
69
69
  */
70
70
  export class GroupCoordinatorState extends EventEmitter {
71
71
  private readonly _runtime: IRuntimeSignaler;
72
- private readonly _logger: EphemeralTelemetryLogger;
72
+ private readonly _logger: LiveTelemetryLogger;
73
73
  private readonly _maxPlaybackDrift: TimeInterval;
74
74
  private _getMediaPlayerState: () => IMediaPlayerState;
75
75
 
@@ -93,7 +93,7 @@ export enum GroupCoordinatorStateEvents {
93
93
  constructor (runtime: IRuntimeSignaler, maxPlaybackDrift: TimeInterval, positionUpdateInterval: TimeInterval, getMediaPlayerState: () => IMediaPlayerState) {
94
94
  super();
95
95
  this._runtime = runtime;
96
- this._logger = new EphemeralTelemetryLogger(runtime);
96
+ this._logger = new LiveTelemetryLogger(runtime);
97
97
  this._maxPlaybackDrift = maxPlaybackDrift;
98
98
  this._playbackTrack = new GroupPlaybackTrack(getMediaPlayerState);
99
99
  this._playbackTrackData = new GroupPlaybackTrackData(this._playbackTrack);
@@ -240,7 +240,7 @@ export enum GroupCoordinatorStateEvents {
240
240
  });
241
241
 
242
242
  if (updated) {
243
- this._logger.sendTelemetryEvent(TelemetryEvents.SessionCoordinator.RemoteSetTrackReceived, null, {correlationId: EphemeralTelemetryLogger.formatCorrelationId(event.clientId, event.timestamp)});
243
+ this._logger.sendTelemetryEvent(TelemetryEvents.SessionCoordinator.RemoteSetTrackReceived, null, {correlationId: LiveTelemetryLogger.formatCorrelationId(event.clientId, event.timestamp)});
244
244
  }
245
245
  }
246
246
 
@@ -254,7 +254,7 @@ export enum GroupCoordinatorStateEvents {
254
254
  });
255
255
 
256
256
  if (updated) {
257
- this._logger.sendTelemetryEvent(TelemetryEvents.SessionCoordinator.RemoteSetTrackReceived, null, {correlationId: EphemeralTelemetryLogger.formatCorrelationId(event.clientId, event.timestamp)});
257
+ this._logger.sendTelemetryEvent(TelemetryEvents.SessionCoordinator.RemoteSetTrackReceived, null, {correlationId: LiveTelemetryLogger.formatCorrelationId(event.clientId, event.timestamp)});
258
258
  }
259
259
  }
260
260
 
@@ -284,7 +284,7 @@ export enum GroupCoordinatorStateEvents {
284
284
  const updated = this.transportState.updateState(newState);
285
285
 
286
286
  if (updated) {
287
- const correlationId = EphemeralTelemetryLogger.formatCorrelationId(event.clientId, event.timestamp);
287
+ const correlationId = LiveTelemetryLogger.formatCorrelationId(event.clientId, event.timestamp);
288
288
  switch (event.name) {
289
289
  case 'play':
290
290
  this._logger.sendTelemetryEvent(TelemetryEvents.SessionCoordinator.RemotePlayReceived, null, {correlationId: correlationId});
@@ -341,7 +341,7 @@ export enum GroupCoordinatorStateEvents {
341
341
  if (event.playbackState != this._lastStateChange) {
342
342
  this._logger.sendTelemetryEvent(TelemetryEvents.GroupCoordinator.BeginSoftSuspension, null, {playbackState: event.playbackState});
343
343
  this._lastStateChange = event.playbackState;
344
- this._lastStateChangeTime = EphemeralEvent.getTimestamp();
344
+ this._lastStateChangeTime = LiveEvent.getTimestamp();
345
345
  }
346
346
  break;
347
347
  }
@@ -366,7 +366,7 @@ export enum GroupCoordinatorStateEvents {
366
366
 
367
367
  public async syncLocalMediaSession(): Promise<void> {
368
368
  // Skip further syncs if we're waiting or in a "soft suspension".
369
- const softSuspensionDelta = EphemeralEvent.getTimestamp() - this._lastStateChangeTime;
369
+ const softSuspensionDelta = LiveEvent.getTimestamp() - this._lastStateChangeTime;
370
370
  if (!this.isWaiting && softSuspensionDelta >= 1000) {
371
371
  let { metadata, trackData, positionState, playbackState } = this._getMediaPlayerState();
372
372
  this._logger.sendTelemetryEvent(TelemetryEvents.GroupCoordinator.CheckingForSyncIssues);
@@ -3,7 +3,7 @@
3
3
  * Licensed under the Microsoft Live Share SDK License.
4
4
  */
5
5
 
6
- import { EphemeralEvent, IRuntimeSignaler, TimeInterval } from '@microsoft/live-share';
6
+ import { LiveEvent, IRuntimeSignaler, TimeInterval } from '@microsoft/live-share';
7
7
  import { GroupTransportState } from './GroupTransportState';
8
8
  import { GroupPlaybackTrackEvents } from './GroupPlaybackTrack';
9
9
  import { CoordinationWaitPoint, ExtendedMediaSessionPlaybackState } from '../MediaSessionExtensions';
@@ -109,7 +109,7 @@ export class GroupPlaybackPosition {
109
109
  */
110
110
  public get maxPosition(): number {
111
111
  if (this._transportState.playbackState == 'playing') {
112
- const now = EphemeralEvent.getTimestamp();
112
+ const now = LiveEvent.getTimestamp();
113
113
  const projected = this._transportState.startPosition + ((now - this._transportState.timestamp) / 1000);
114
114
  return this.limitProjectedPosition(projected);
115
115
  } else {
@@ -136,7 +136,7 @@ export class GroupPlaybackPosition {
136
136
  * @param callbackFn Function applied to each position,
137
137
  */
138
138
  public forEach(callbackFn: (position: ICurrentPlaybackPosition, projectedPosition: number) => void): void {
139
- const now = EphemeralEvent.getTimestamp();
139
+ const now = LiveEvent.getTimestamp();
140
140
  const ignoreBefore = now - (this._updateInterval.milliseconds * 2);
141
141
  const shouldProject = !this._transportState.track.metadata?.liveStream;
142
142
  this._positions.forEach((value, key) => {
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { IEvent } from '@microsoft/live-share';
7
7
  import EventEmitter from 'events';
8
- import { IMediaPlayerState } from '../EphemeralMediaSessionCoordinator';
8
+ import { IMediaPlayerState } from '../LiveMediaSessionCoordinator';
9
9
  import { CoordinationWaitPoint, ExtendedMediaMetadata } from '../MediaSessionExtensions';
10
10
 
11
11
  /**
@@ -3,9 +3,9 @@
3
3
  * Licensed under the Microsoft Live Share SDK License.
4
4
  */
5
5
 
6
- import { IEvent, EphemeralEvent } from '@microsoft/live-share';
6
+ import { IEvent, LiveEvent } from '@microsoft/live-share';
7
7
  import EventEmitter from 'events';
8
- import { IMediaPlayerState } from '../EphemeralMediaSessionCoordinator';
8
+ import { IMediaPlayerState } from '../LiveMediaSessionCoordinator';
9
9
  import { ExtendedMediaSessionPlaybackState, ExtendedMediaSessionAction } from '../MediaSessionExtensions';
10
10
  import { GroupPlaybackTrack, GroupPlaybackTrackEvents } from './GroupPlaybackTrack';
11
11
 
@@ -115,7 +115,7 @@ export interface ITransportStateChangeEvent extends IEvent {
115
115
  if (originalState.playbackState == state.playbackState && playerState != 'ended') {
116
116
  this.emit(GroupTransportStateEvents.transportStateChange, { type: GroupTransportStateEvents.transportStateChange, action: 'seekto', seekTime: state.startPosition });
117
117
  } else if (state.playbackState == 'playing') {
118
- const now = EphemeralEvent.getTimestamp();
118
+ const now = LiveEvent.getTimestamp();
119
119
  const projectedPosition = state.startPosition + ((now - state.timestamp) / 1000);
120
120
  this.emit(GroupTransportStateEvents.transportStateChange, { type: GroupTransportStateEvents.transportStateChange, action: 'play', seekTime: projectedPosition});
121
121
  } else {
@@ -6,9 +6,9 @@
6
6
  import 'mocha';
7
7
  import { strict as assert } from 'assert';
8
8
  import { CollaborationSpace, CollaborationSpaceEvents, Deferred } from '@microsoft/teams-collaboration';
9
- import { LastMediaPositionState } from '../EphemeralMediaSessionCoordinator';
9
+ import { LastMediaPositionState } from '../LiveMediaSessionCoordinator';
10
10
  import { ExtendedMediaMetadata, ExtendedMediaSessionActionDetails } from '../MediaSessionExtensions';
11
- import { SharedMediaSession } from '../EphemeralMediaSession';
11
+ import { SharedMediaSession } from '../LiveMediaSession';
12
12
 
13
13
  export class TestMediaSession extends SharedMediaSession {
14
14
  private done = new Deferred<ExtendedMediaSessionActionDetails>();
@@ -0,0 +1,212 @@
1
+ import { strict as assert } from "assert";
2
+ import { IMediaPlayer } from "../IMediaPlayer"
3
+ import { LimitLevelType, VolumeManager } from "../VolumeManager"
4
+ import { Deferred } from '@microsoft/live-share/src/test/Deferred';
5
+
6
+ describe('VolumeManager', () => {
7
+ it('should ramp down volume', async () => {
8
+ const testAwait = new Deferred();
9
+ const player = new TestMediaPlayer();
10
+ player.volume = 1.0
11
+ const volumeManager = new VolumeManager(player);
12
+
13
+ assert(player.volume == 1.0);
14
+ volumeManager.startLimiting();
15
+ setTimeout(() => {
16
+ // check volume at halfway point
17
+ assert(player.volume > 0.4);
18
+ assert(player.volume < 0.6);
19
+ }, volumeManager.volumeChangeDuration * 1000 / 2);
20
+
21
+ setTimeout(() => {
22
+ // check volume at end with 20ms of leeway
23
+ assert(player.volume == volumeManager.limitLevel);
24
+ testAwait.resolve();
25
+ }, volumeManager.volumeChangeDuration * 1000 + 20);
26
+
27
+ await testAwait.promise;
28
+ });
29
+
30
+ it('should ramp up volume', async () => {
31
+ const testAwait = new Deferred();
32
+ const player = new TestMediaPlayer();
33
+ player.volume = 1.0
34
+ const volumeManager = new VolumeManager(player);
35
+
36
+ // limit at start
37
+ volumeManager.startLimiting();
38
+
39
+ // when limited all the way, test ramp up
40
+ setTimeout(() => {
41
+ assert(player.volume == volumeManager.limitLevel);
42
+ volumeManager.stopLimiting();
43
+
44
+ setTimeout(() => {
45
+ // check volume at halfway point
46
+ assert(player.volume > 0.4);
47
+ assert(player.volume < 0.6);
48
+ }, volumeManager.volumeChangeDuration * 1000 / 2);
49
+
50
+ setTimeout(() => {
51
+ // check volume at end with 20ms of leeway
52
+ assert(player.volume == volumeManager.volume);
53
+ testAwait.resolve();
54
+ }, volumeManager.volumeChangeDuration * 1000 + 20);
55
+
56
+ }, volumeManager.volumeChangeDuration * 1000 + 20);
57
+
58
+ await testAwait.promise;
59
+ });
60
+
61
+ it('should ramp down halfway, then ramp up', async () => {
62
+ const testAwait = new Deferred();
63
+ const player = new TestMediaPlayer();
64
+ player.volume = 1.0
65
+
66
+ const volumeManager = new VolumeManager(player);
67
+
68
+ volumeManager.startLimiting();
69
+ setTimeout(() => {
70
+ // check volume at halfway point, begin ramping other direction
71
+ assert(player.volume > 0.4);
72
+ assert(player.volume < 0.6);
73
+ volumeManager.stopLimiting();
74
+
75
+ setTimeout(() => {
76
+ // check volume at end with 20ms of leeway
77
+ assert(player.volume == volumeManager.volume);
78
+ testAwait.resolve();
79
+ }, volumeManager.volumeChangeDuration * 1000 + 20);
80
+
81
+ }, volumeManager.volumeChangeDuration * 1000 / 2);
82
+
83
+ await testAwait.promise;
84
+ });
85
+
86
+ it('ramp using selected volume', async () => {
87
+ const testAwait = new Deferred();
88
+ const player = new TestMediaPlayer();
89
+ player.volume = 1.0
90
+
91
+ const volumeManager = new VolumeManager(player);
92
+
93
+ volumeManager.volume = 0.3;
94
+ setTimeout(() => {
95
+ // check volume at halfway point
96
+ assert(player.volume > 0.6);
97
+ assert(player.volume < 0.7);
98
+ }, volumeManager.volumeChangeDuration * 1000 / 2);
99
+
100
+ setTimeout(() => {
101
+ // check volume at end with 20ms of leeway
102
+ assert(player.volume == volumeManager.volume);
103
+ testAwait.resolve();
104
+ }, volumeManager.volumeChangeDuration * 1000 + 20);
105
+
106
+ await testAwait.promise;
107
+ });
108
+
109
+ it('enable limit, change selected volume, then disable limit', async () => {
110
+ const testAwait = new Deferred();
111
+ const player = new TestMediaPlayer();
112
+ player.volume = 1.0
113
+ const volumeManager = new VolumeManager(player);
114
+
115
+ volumeManager.startLimiting();
116
+ setTimeout(() => {
117
+ // check volume at halfway point, begin ramping other direction with lower selected volume
118
+ assert(player.volume > 0.4);
119
+ assert(player.volume < 0.6);
120
+
121
+ volumeManager.volume = 0.7;
122
+ volumeManager.stopLimiting();
123
+
124
+ setTimeout(() => {
125
+ // check volume at end with 20ms of leeway
126
+ assert(player.volume === volumeManager.volume);
127
+ testAwait.resolve();
128
+ }, volumeManager.volumeChangeDuration * 1000 + 20);
129
+
130
+ }, volumeManager.volumeChangeDuration * 1000 / 2);
131
+
132
+ await testAwait.promise;
133
+ });
134
+
135
+ it('ramp down with selected volume to 0.3, then up to 0.8', async () => {
136
+ const testAwait = new Deferred();
137
+ const player = new TestMediaPlayer();
138
+ player.volume = 1.0
139
+
140
+ const volumeManager = new VolumeManager(player);
141
+ volumeManager.volume = 0.3;
142
+
143
+ setTimeout(() => {
144
+ // check volume at halfway point
145
+ assert(player.volume > 0.6);
146
+ assert(player.volume < 0.7);
147
+ volumeManager.volume = 0.8;
148
+
149
+ setTimeout(() => {
150
+ // check volume at end with 20ms of leeway
151
+ assert(player.volume == volumeManager.volume);
152
+ testAwait.resolve();
153
+ }, volumeManager.volumeChangeDuration * 1000 + 20);
154
+ }, volumeManager.volumeChangeDuration * 1000 / 2);
155
+
156
+ await testAwait.promise;
157
+ });
158
+
159
+ it('test 50% limiting', async () => {
160
+ const testAwait = new Deferred();
161
+ const player = new TestMediaPlayer();
162
+ player.volume = 1.0
163
+
164
+ const volumeManager = new VolumeManager(player);
165
+ volumeManager.limitLevelType = LimitLevelType.percentage;
166
+ volumeManager.limitLevel = 0.5;
167
+
168
+ volumeManager.startLimiting();
169
+
170
+ setTimeout(() => {
171
+ // check volume at halfway point
172
+ assert(player.volume > 0.7);
173
+ assert(player.volume < 0.8);
174
+ }, volumeManager.volumeChangeDuration * 1000 / 2);
175
+
176
+ setTimeout(() => {
177
+ // check volume at end with 20ms of leeway
178
+ assert(player.volume == volumeManager.limitLevel * volumeManager.volume);
179
+ testAwait.resolve();
180
+ }, volumeManager.volumeChangeDuration * 1000 + 20);
181
+
182
+ await testAwait.promise;
183
+ });
184
+ });
185
+
186
+ // test implementation
187
+ class TestMediaPlayer implements IMediaPlayer {
188
+ currentSrc: string;
189
+ currentTime: number;
190
+ duration: number;
191
+ ended: boolean;
192
+ muted: boolean;
193
+ paused: boolean;
194
+ playbackRate: number;
195
+ src: string;
196
+ volume: number;
197
+ load(): void {
198
+ throw new Error("Method not implemented.");
199
+ }
200
+ pause(): void {
201
+ throw new Error("Method not implemented.");
202
+ }
203
+ play(): Promise<void> {
204
+ throw new Error("Method not implemented.");
205
+ }
206
+ addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | undefined): void {
207
+ throw new Error("Method not implemented.");
208
+ }
209
+ removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions | undefined): void {
210
+ throw new Error("Method not implemented.");
211
+ }
212
+ }
@@ -1,182 +0,0 @@
1
- /**
2
- * Copyright (c) Microsoft Corporation. All rights reserved.
3
- * Licensed under the Microsoft Live Share SDK License.
4
- */
5
-
6
- import { TimeInterval } from '@microsoft/live-share';
7
- import { IMediaPlayer } from './IMediaPlayer';
8
-
9
- export enum LevelType { fixed, percentage }
10
-
11
- export class VolumeLimiter {
12
- private readonly _player: IMediaPlayer;
13
-
14
- // Settings
15
- private readonly _duration = new TimeInterval(2000);
16
- private readonly _rampDownDuration = new TimeInterval(500);
17
- private readonly _rampUpDuration = new TimeInterval(500);
18
- private _level: number = 0.1
19
- private _levelType: LevelType = LevelType.fixed;
20
-
21
- // Running state
22
- private _running = false;
23
- private _endTime = 0;
24
- private _startTime = 0;
25
- private _startVolume = 0;
26
- private _inRamp = false;
27
-
28
- constructor(player: IMediaPlayer) {
29
- this._player = player;
30
- }
31
-
32
- /**
33
- * Time, in seconds, that the volume should be lowered.
34
- *
35
- * @remarks
36
- * Applications should periodically call `lowerVolume()` to keep the volume lowered.
37
- */
38
- public get duration(): number {
39
- return this._duration.seconds;
40
- }
41
-
42
- public set duration(value: number) {
43
- this._duration.seconds = Math.abs(value);
44
- }
45
-
46
- /**
47
- * Target level to lower volume to.
48
- *
49
- * @remarks
50
- * Expressed as a value between 0.0 and 1.0. The value is applied based upon the configured
51
- * `levelType`. The default value is 0.2.
52
- *
53
- * For a level type of `LevelType.fixed` the value is the exact level the volume will be
54
- * lowered to. The default value of 0.2 would cause the volume to be lowered to 0.2.
55
- *
56
- * For a level type of `LevelType.percentage` the value is the percentage by which the volume
57
- * level should be lowered to. The default value of 0.2 would cause the volume to be lowered
58
- * to 20% of its starting value.
59
- */
60
- public get level(): number {
61
- return this._level;
62
- }
63
-
64
- public set level(value: number) {
65
- if (value < 0 || value > 1.0) {
66
- throw new Error(`VolumeLimiter: cannot set level to ${value}. Level must be between 0.0 and 1.0.`);
67
- }
68
-
69
- this._level = value;
70
- }
71
-
72
- /**
73
- * The type of level represented by the `level` property.
74
- */
75
- public get levelType(): LevelType {
76
- return this._levelType;
77
- }
78
-
79
- public set levelType(value: LevelType) {
80
- this._levelType = value;
81
- }
82
-
83
- /**
84
- * Amount of time, in seconds, it should take to ramp the volume down to the desired level.
85
- */
86
- public get rampDownDuration(): number {
87
- return this._rampDownDuration.seconds;
88
- }
89
-
90
- public set rampDownDuration(value: number) {
91
- this._rampDownDuration.seconds = Math.abs(value);
92
- }
93
-
94
- /**
95
- * Amount of time, in seconds, it should take to ramp the volume back up to the original level.
96
- */
97
- public get rampUpDuration(): number {
98
- return this._rampUpDuration.seconds;
99
- }
100
-
101
- public set rampUpDuration(value: number) {
102
- this._rampUpDuration.seconds = Math.abs(value);
103
- }
104
-
105
- /**
106
- * Temporarily lowers an audio sources volume.
107
- */
108
- public lowerVolume(): void {
109
- this._endTime = new Date().getTime() + this._duration.milliseconds;
110
- if (!this._running) {
111
- this.startLimiter();
112
- }
113
- }
114
-
115
- private startLimiter(): void {
116
- const adjustVolume = () => {
117
- const now = new Date().getTime();
118
- if (now < this._endTime) {
119
- // Make no adjustment if muted
120
- if (!this._player.muted) {
121
- // Compute adjusted volume level
122
- let volume: number;
123
- const targetVolume = this.computeTargetVolume();
124
- const rampRange = this._startVolume - targetVolume;
125
- const rampUpStart = this._endTime - this._rampUpDuration.milliseconds;
126
- const rampDownEnd = this._startTime + this._rampDownDuration.milliseconds;
127
- if (now > rampUpStart) {
128
- // We're about to end so ramping up
129
- const progress = (now - rampUpStart) / this._rampUpDuration.milliseconds;
130
- volume = targetVolume + (rampRange * progress);
131
- this._player.volume = volume;
132
- this._inRamp = true;
133
- } else if (now < rampDownEnd) {
134
- // We just begun so ramping down
135
- const remaining = (rampDownEnd - now) / this._rampUpDuration.milliseconds;
136
- volume = targetVolume + (rampRange * remaining);
137
- this._player.volume = volume;
138
- this._inRamp = true;
139
- } else if (this._inRamp) {
140
- // We're at the end of a ramp
141
- volume = targetVolume;
142
- this._player.volume = volume;
143
- this._inRamp = false;
144
- }
145
- }
146
-
147
- // Schedule next animation frame
148
- this.scheduleAnimationFrame(adjustVolume);
149
- } else {
150
- // Reset volume to original level
151
- this._player.volume = this._startVolume;
152
- this._running = false;
153
- this._endTime = 0;
154
- this._startTime = 0;
155
- this._startVolume = 0;
156
- }
157
- };
158
-
159
- this._running = true;
160
- this._startTime = new Date().getTime();
161
- this._startVolume = this._player.volume;
162
- this.scheduleAnimationFrame(adjustVolume)
163
- }
164
-
165
- private scheduleAnimationFrame(callback: FrameRequestCallback): void {
166
- if (requestAnimationFrame) {
167
- requestAnimationFrame(callback);
168
- } else {
169
- setTimeout(callback, 20);
170
- }
171
- }
172
-
173
- private computeTargetVolume(): number {
174
- switch (this._levelType) {
175
- case LevelType.percentage:
176
- return this._startVolume * this._level;
177
- case LevelType.fixed:
178
- default:
179
- return this._level;
180
- }
181
- }
182
- }