@microsoft/live-share-media 0.5.0 → 0.5.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.
- package/.nycrc.json +10 -10
- package/LICENSE +86 -86
- package/README.md +137 -137
- package/package.json +38 -38
- package/src/EphemeralMediaSession.ts +248 -248
- package/src/EphemeralMediaSessionCoordinator.ts +461 -461
- package/src/EphemeralMediaSessionCoordinatorSuspension.ts +30 -30
- package/src/IMediaPlayer.ts +21 -21
- package/src/MediaPlayerSynchronizer.ts +449 -449
- package/src/MediaSessionActionThrottler.ts +14 -14
- package/src/MediaSessionExtensions.ts +42 -42
- package/src/RepeatedActionThrottler.ts +55 -55
- package/src/VolumeLimiter.ts +181 -181
- package/src/index.ts +15 -15
- package/src/internals/GroupCoordinatorState.ts +445 -445
- package/src/internals/GroupPlaybackPosition.ts +193 -193
- package/src/internals/GroupPlaybackTrack.ts +147 -147
- package/src/internals/GroupPlaybackTrackData.ts +90 -90
- package/src/internals/GroupTransportState.ts +127 -127
- package/src/internals/consts.ts +80 -80
- package/src/internals/index.ts +10 -10
- package/src/test/GroupPlaybackPosition.spec.ts +339 -339
- package/src/test/GroupPlaybackTrack.spec.ts +65 -65
- package/src/test/SharedMediaSession.spec.ts +113 -113
- package/src/test/SharedMediaSessionCoordinator.spec.ts +230 -230
- package/tsconfig.build.json +8 -8
- package/tsconfig.json +75 -75
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
-
* Licensed under the Microsoft Live Share SDK License.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { ExtendedMediaSessionActionDetails } from './MediaSessionExtensions';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Base class for action throttlers.
|
|
10
|
-
*/
|
|
11
|
-
export abstract class MediaSessionActionThrottler {
|
|
12
|
-
public abstract throttled(details: ExtendedMediaSessionActionDetails, handler?: MediaSessionActionHandler): void;
|
|
13
|
-
}
|
|
14
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the Microsoft Live Share SDK License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ExtendedMediaSessionActionDetails } from './MediaSessionExtensions';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Base class for action throttlers.
|
|
10
|
+
*/
|
|
11
|
+
export abstract class MediaSessionActionThrottler {
|
|
12
|
+
public abstract throttled(details: ExtendedMediaSessionActionDetails, handler?: MediaSessionActionHandler): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
-
* Licensed under the Microsoft Live Share SDK License.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export interface MediaSessionCoordinatorSuspension {
|
|
7
|
-
waitPoint?: CoordinationWaitPoint;
|
|
8
|
-
end(seekTo?: number): void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export type MediaSessionCoordinatorState = "closed" | "waiting" | "joined";
|
|
12
|
-
|
|
13
|
-
export enum MediaSessionCoordinatorEvents {
|
|
14
|
-
coordinatorstatechange = 'coordinatorstatechange',
|
|
15
|
-
triggeraction = 'triggeraction'
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export type ExtendedMediaSessionAction = MediaSessionAction | "settrack" | "wait" | "catchup" | "datachange" | "blocked";
|
|
19
|
-
|
|
20
|
-
export type ExtendedMediaSessionPlaybackState = MediaSessionPlaybackState | "suspended" | "waiting" | "ended";
|
|
21
|
-
|
|
22
|
-
export interface ExtendedMediaMetadata extends MediaMetadata {
|
|
23
|
-
trackIdentifier: string;
|
|
24
|
-
liveStream: boolean;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface CoordinationWaitPoint {
|
|
28
|
-
position: number;
|
|
29
|
-
reason?: string;
|
|
30
|
-
maxClients?: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface ExtendedMediaSessionActionDetails {
|
|
34
|
-
action: ExtendedMediaSessionAction;
|
|
35
|
-
fastSeek?: boolean | null;
|
|
36
|
-
seekOffset?: number | null;
|
|
37
|
-
seekTime?: number | null;
|
|
38
|
-
metadata?: ExtendedMediaMetadata | null;
|
|
39
|
-
suspension?: MediaSessionCoordinatorSuspension | null;
|
|
40
|
-
data?: object | null;
|
|
41
|
-
blocked?: ExtendedMediaSessionAction | null;
|
|
42
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the Microsoft Live Share SDK License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface MediaSessionCoordinatorSuspension {
|
|
7
|
+
waitPoint?: CoordinationWaitPoint;
|
|
8
|
+
end(seekTo?: number): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type MediaSessionCoordinatorState = "closed" | "waiting" | "joined";
|
|
12
|
+
|
|
13
|
+
export enum MediaSessionCoordinatorEvents {
|
|
14
|
+
coordinatorstatechange = 'coordinatorstatechange',
|
|
15
|
+
triggeraction = 'triggeraction'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type ExtendedMediaSessionAction = MediaSessionAction | "settrack" | "wait" | "catchup" | "datachange" | "blocked";
|
|
19
|
+
|
|
20
|
+
export type ExtendedMediaSessionPlaybackState = MediaSessionPlaybackState | "suspended" | "waiting" | "ended";
|
|
21
|
+
|
|
22
|
+
export interface ExtendedMediaMetadata extends MediaMetadata {
|
|
23
|
+
trackIdentifier: string;
|
|
24
|
+
liveStream: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CoordinationWaitPoint {
|
|
28
|
+
position: number;
|
|
29
|
+
reason?: string;
|
|
30
|
+
maxClients?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ExtendedMediaSessionActionDetails {
|
|
34
|
+
action: ExtendedMediaSessionAction;
|
|
35
|
+
fastSeek?: boolean | null;
|
|
36
|
+
seekOffset?: number | null;
|
|
37
|
+
seekTime?: number | null;
|
|
38
|
+
metadata?: ExtendedMediaMetadata | null;
|
|
39
|
+
suspension?: MediaSessionCoordinatorSuspension | null;
|
|
40
|
+
data?: object | null;
|
|
41
|
+
blocked?: ExtendedMediaSessionAction | null;
|
|
42
|
+
}
|
|
@@ -1,56 +1,56 @@
|
|
|
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 { MediaSessionActionThrottler } from './MediaSessionActionThrottler';
|
|
8
|
-
import { ExtendedMediaSessionActionDetails, ExtendedMediaSessionAction } from './MediaSessionExtensions';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* A Throttler that passes through all actions but will only let an action be repeated once every 2 seconds.
|
|
12
|
-
*/
|
|
13
|
-
export class RepeatedActionThrottler extends MediaSessionActionThrottler {
|
|
14
|
-
private static FILTERED_ACTIONS: ExtendedMediaSessionAction[] = ['play', 'pause', 'seekto', 'catchup'];
|
|
15
|
-
private _repeatInterval = new TimeInterval(2000);
|
|
16
|
-
private _lastActionSentTime?: number;
|
|
17
|
-
private _lastActionSent?: string;
|
|
18
|
-
|
|
19
|
-
public get repeatInterval(): number {
|
|
20
|
-
return this._repeatInterval.seconds;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
public set repeatInterval(value: number) {
|
|
24
|
-
this._repeatInterval.seconds = value;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
public throttled(details: ExtendedMediaSessionActionDetails, handler?: MediaSessionActionHandler): void {
|
|
28
|
-
if (handler) {
|
|
29
|
-
if (RepeatedActionThrottler.FILTERED_ACTIONS.indexOf(details.action) >= 0) {
|
|
30
|
-
const changeKey = this.getChangeKey(details);
|
|
31
|
-
const now = new Date().getTime();
|
|
32
|
-
if (this._lastActionSent && changeKey == this._lastActionSent) {
|
|
33
|
-
if (now - this._lastActionSentTime! >= this._repeatInterval.milliseconds) {
|
|
34
|
-
handler(details as MediaSessionActionDetails);
|
|
35
|
-
this._lastActionSentTime = now;
|
|
36
|
-
}
|
|
37
|
-
} else {
|
|
38
|
-
handler(details as MediaSessionActionDetails);
|
|
39
|
-
this._lastActionSent = changeKey;
|
|
40
|
-
this._lastActionSentTime = now;
|
|
41
|
-
}
|
|
42
|
-
} else {
|
|
43
|
-
handler(details as MediaSessionActionDetails);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
private getChangeKey(details: ExtendedMediaSessionActionDetails): string {
|
|
49
|
-
switch (details.action) {
|
|
50
|
-
case 'seekto':
|
|
51
|
-
return `seekto:${details.seekTime}`;
|
|
52
|
-
default:
|
|
53
|
-
return details.action;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
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 { MediaSessionActionThrottler } from './MediaSessionActionThrottler';
|
|
8
|
+
import { ExtendedMediaSessionActionDetails, ExtendedMediaSessionAction } from './MediaSessionExtensions';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A Throttler that passes through all actions but will only let an action be repeated once every 2 seconds.
|
|
12
|
+
*/
|
|
13
|
+
export class RepeatedActionThrottler extends MediaSessionActionThrottler {
|
|
14
|
+
private static FILTERED_ACTIONS: ExtendedMediaSessionAction[] = ['play', 'pause', 'seekto', 'catchup'];
|
|
15
|
+
private _repeatInterval = new TimeInterval(2000);
|
|
16
|
+
private _lastActionSentTime?: number;
|
|
17
|
+
private _lastActionSent?: string;
|
|
18
|
+
|
|
19
|
+
public get repeatInterval(): number {
|
|
20
|
+
return this._repeatInterval.seconds;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public set repeatInterval(value: number) {
|
|
24
|
+
this._repeatInterval.seconds = value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public throttled(details: ExtendedMediaSessionActionDetails, handler?: MediaSessionActionHandler): void {
|
|
28
|
+
if (handler) {
|
|
29
|
+
if (RepeatedActionThrottler.FILTERED_ACTIONS.indexOf(details.action) >= 0) {
|
|
30
|
+
const changeKey = this.getChangeKey(details);
|
|
31
|
+
const now = new Date().getTime();
|
|
32
|
+
if (this._lastActionSent && changeKey == this._lastActionSent) {
|
|
33
|
+
if (now - this._lastActionSentTime! >= this._repeatInterval.milliseconds) {
|
|
34
|
+
handler(details as MediaSessionActionDetails);
|
|
35
|
+
this._lastActionSentTime = now;
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
handler(details as MediaSessionActionDetails);
|
|
39
|
+
this._lastActionSent = changeKey;
|
|
40
|
+
this._lastActionSentTime = now;
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
handler(details as MediaSessionActionDetails);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private getChangeKey(details: ExtendedMediaSessionActionDetails): string {
|
|
49
|
+
switch (details.action) {
|
|
50
|
+
case 'seekto':
|
|
51
|
+
return `seekto:${details.seekTime}`;
|
|
52
|
+
default:
|
|
53
|
+
return details.action;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
56
|
}
|
package/src/VolumeLimiter.ts
CHANGED
|
@@ -1,182 +1,182 @@
|
|
|
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
|
-
}
|
|
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
182
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
-
* Licensed under the Microsoft Live Share SDK License.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export * from './EphemeralMediaSession';
|
|
8
|
-
export * from './EphemeralMediaSessionCoordinator';
|
|
9
|
-
export * from './EphemeralMediaSessionCoordinatorSuspension';
|
|
10
|
-
export * from './IMediaPlayer';
|
|
11
|
-
export * from './MediaPlayerSynchronizer';
|
|
12
|
-
export * from './MediaSessionActionThrottler';
|
|
13
|
-
export * from './MediaSessionExtensions';
|
|
14
|
-
export * from './RepeatedActionThrottler';
|
|
15
|
-
export * from './VolumeLimiter';
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the Microsoft Live Share SDK License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export * from './EphemeralMediaSession';
|
|
8
|
+
export * from './EphemeralMediaSessionCoordinator';
|
|
9
|
+
export * from './EphemeralMediaSessionCoordinatorSuspension';
|
|
10
|
+
export * from './IMediaPlayer';
|
|
11
|
+
export * from './MediaPlayerSynchronizer';
|
|
12
|
+
export * from './MediaSessionActionThrottler';
|
|
13
|
+
export * from './MediaSessionExtensions';
|
|
14
|
+
export * from './RepeatedActionThrottler';
|
|
15
|
+
export * from './VolumeLimiter';
|