@axsnull/audio-sync-engine 1.0.3
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/LICENSE +21 -0
- package/README.md +0 -0
- package/dist/audio-clock.d.ts +14 -0
- package/dist/audio-clock.d.ts.map +1 -0
- package/dist/audio-clock.js +47 -0
- package/dist/audio-clock.js.map +1 -0
- package/dist/audio-sync-engine.d.ts +34 -0
- package/dist/audio-sync-engine.d.ts.map +1 -0
- package/dist/audio-sync-engine.js +117 -0
- package/dist/audio-sync-engine.js.map +1 -0
- package/dist/buffer-manager.d.ts +22 -0
- package/dist/buffer-manager.d.ts.map +1 -0
- package/dist/buffer-manager.js +42 -0
- package/dist/buffer-manager.js.map +1 -0
- package/dist/clock-sync.d.ts +25 -0
- package/dist/clock-sync.d.ts.map +1 -0
- package/dist/clock-sync.js +42 -0
- package/dist/clock-sync.js.map +1 -0
- package/dist/drift-corrector.d.ts +26 -0
- package/dist/drift-corrector.d.ts.map +1 -0
- package/dist/drift-corrector.js +73 -0
- package/dist/drift-corrector.js.map +1 -0
- package/dist/drift-detector.d.ts +20 -0
- package/dist/drift-detector.d.ts.map +1 -0
- package/dist/drift-detector.js +34 -0
- package/dist/drift-detector.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/periodic-sync.d.ts +16 -0
- package/dist/periodic-sync.d.ts.map +1 -0
- package/dist/periodic-sync.js +41 -0
- package/dist/periodic-sync.js.map +1 -0
- package/dist/player-state-sync.d.ts +35 -0
- package/dist/player-state-sync.d.ts.map +1 -0
- package/dist/player-state-sync.js +55 -0
- package/dist/player-state-sync.js.map +1 -0
- package/dist/synchronized-start.d.ts +10 -0
- package/dist/synchronized-start.d.ts.map +1 -0
- package/dist/synchronized-start.js +25 -0
- package/dist/synchronized-start.js.map +1 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 axsnull
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
Binary file
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class AudioClock {
|
|
2
|
+
private audioContext;
|
|
3
|
+
private audioStartTime;
|
|
4
|
+
private isPlaying;
|
|
5
|
+
constructor();
|
|
6
|
+
start(): Promise<void>;
|
|
7
|
+
stop(): void;
|
|
8
|
+
getCurrentTime(): number;
|
|
9
|
+
getAudioContext(): AudioContext | null;
|
|
10
|
+
isAudioPlaying(): boolean;
|
|
11
|
+
getAudioStartTime(): number;
|
|
12
|
+
setAudioStartTime(startTime: number): void;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=audio-clock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-clock.d.ts","sourceRoot":"","sources":["../src/audio-clock.ts"],"names":[],"mappings":"AAAA,qBAAa,UAAU;IACtB,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,SAAS,CAAkB;;IAQ7B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAa5B,IAAI,IAAI,IAAI;IAKZ,cAAc,IAAI,MAAM;IAQxB,eAAe,IAAI,YAAY,GAAG,IAAI;IAItC,cAAc,IAAI,OAAO;IAIzB,iBAAiB,IAAI,MAAM;IAI3B,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;CAG1C"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AudioClock = void 0;
|
|
4
|
+
class AudioClock {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.audioContext = null;
|
|
7
|
+
this.audioStartTime = 0;
|
|
8
|
+
this.isPlaying = false;
|
|
9
|
+
if (typeof window !== 'undefined' && window.AudioContext) {
|
|
10
|
+
this.audioContext = new AudioContext();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
async start() {
|
|
14
|
+
if (!this.audioContext) {
|
|
15
|
+
throw new Error('AudioContext not available');
|
|
16
|
+
}
|
|
17
|
+
if (this.audioContext.state === 'suspended') {
|
|
18
|
+
await this.audioContext.resume();
|
|
19
|
+
}
|
|
20
|
+
this.audioStartTime = Date.now();
|
|
21
|
+
this.isPlaying = true;
|
|
22
|
+
}
|
|
23
|
+
stop() {
|
|
24
|
+
this.isPlaying = false;
|
|
25
|
+
this.audioStartTime = 0;
|
|
26
|
+
}
|
|
27
|
+
getCurrentTime() {
|
|
28
|
+
if (!this.audioContext || !this.isPlaying) {
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
return this.audioStartTime + this.audioContext.currentTime * 1000;
|
|
32
|
+
}
|
|
33
|
+
getAudioContext() {
|
|
34
|
+
return this.audioContext;
|
|
35
|
+
}
|
|
36
|
+
isAudioPlaying() {
|
|
37
|
+
return this.isPlaying;
|
|
38
|
+
}
|
|
39
|
+
getAudioStartTime() {
|
|
40
|
+
return this.audioStartTime;
|
|
41
|
+
}
|
|
42
|
+
setAudioStartTime(startTime) {
|
|
43
|
+
this.audioStartTime = startTime;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.AudioClock = AudioClock;
|
|
47
|
+
//# sourceMappingURL=audio-clock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-clock.js","sourceRoot":"","sources":["../src/audio-clock.ts"],"names":[],"mappings":";;;AAAA,MAAa,UAAU;IAKtB;QAJQ,iBAAY,GAAwB,IAAI,CAAC;QACzC,mBAAc,GAAW,CAAC,CAAC;QAC3B,cAAS,GAAY,KAAK,CAAC;QAGlC,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YAC1D,IAAI,CAAC,YAAY,GAAG,IAAI,YAAY,EAAE,CAAC;QACxC,CAAC;IACF,CAAC;IAED,KAAK,CAAC,KAAK;QACV,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAC/C,CAAC;QAED,IAAI,IAAI,CAAC,YAAY,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;YAC7C,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC;QAClC,CAAC;QAED,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACjC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;IACvB,CAAC;IAED,IAAI;QACH,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACvB,IAAI,CAAC,cAAc,GAAG,CAAC,CAAC;IACzB,CAAC;IAED,cAAc;QACb,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YAC3C,OAAO,CAAC,CAAC;QACV,CAAC;QAED,OAAO,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,GAAG,IAAI,CAAC;IACnE,CAAC;IAED,eAAe;QACd,OAAO,IAAI,CAAC,YAAY,CAAC;IAC1B,CAAC;IAED,cAAc;QACb,OAAO,IAAI,CAAC,SAAS,CAAC;IACvB,CAAC;IAED,iBAAiB;QAChB,OAAO,IAAI,CAAC,cAAc,CAAC;IAC5B,CAAC;IAED,iBAAiB,CAAC,SAAiB;QAClC,IAAI,CAAC,cAAc,GAAG,SAAS,CAAC;IACjC,CAAC;CACD;AApDD,gCAoDC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { PlayerState, TrackMetadata } from './player-state-sync';
|
|
2
|
+
export interface AudioSyncEngineConfig {
|
|
3
|
+
syncIntervalMs?: number;
|
|
4
|
+
bufferMinSeconds?: number;
|
|
5
|
+
bufferMaxSeconds?: number;
|
|
6
|
+
bufferTargetSeconds?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare class AudioSyncEngine {
|
|
9
|
+
private readonly clockSync;
|
|
10
|
+
private readonly audioClock;
|
|
11
|
+
private readonly driftDetector;
|
|
12
|
+
private readonly driftCorrector;
|
|
13
|
+
private readonly playerStateSync;
|
|
14
|
+
private readonly periodicSync;
|
|
15
|
+
private readonly synchronizedStart;
|
|
16
|
+
private readonly bufferManager;
|
|
17
|
+
private currentAudio;
|
|
18
|
+
private loadTrackFn;
|
|
19
|
+
constructor(_sendPing: (timestamp: number) => Promise<number>, config?: AudioSyncEngineConfig);
|
|
20
|
+
initialize(audio: HTMLAudioElement, loadTrack: (trackId: string) => Promise<TrackMetadata>): Promise<void>;
|
|
21
|
+
handlePlayerUpdate(state: PlayerState): Promise<void>;
|
|
22
|
+
performPeriodicSync(): Promise<void>;
|
|
23
|
+
scheduleSynchronizedStart(serverStartAt: number): Promise<{
|
|
24
|
+
startAt: number;
|
|
25
|
+
delay: number;
|
|
26
|
+
}>;
|
|
27
|
+
syncTime(): Promise<void>;
|
|
28
|
+
getSyncedNow(): number;
|
|
29
|
+
getClockOffset(): number;
|
|
30
|
+
getClockLatency(): number;
|
|
31
|
+
stop(): void;
|
|
32
|
+
private createAudioAdapter;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=audio-sync-engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-sync-engine.d.ts","sourceRoot":"","sources":["../src/audio-sync-engine.ts"],"names":[],"mappings":"AAIA,OAAO,EAAmB,WAAW,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAKlF,MAAM,WAAW,qBAAqB;IACrC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,qBAAa,eAAe;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAoB;IACtD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAE9C,OAAO,CAAC,YAAY,CAAiC;IACrD,OAAO,CAAC,WAAW,CAA8D;gBAGhF,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,EACjD,MAAM,GAAE,qBAA0B;IAuB7B,UAAU,CAAC,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ1G,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBrD,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAoBpC,yBAAyB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAkB7F,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAI/B,YAAY,IAAI,MAAM;IAItB,cAAc,IAAI,MAAM;IAIxB,eAAe,IAAI,MAAM;IAIzB,IAAI,IAAI,IAAI;IAMZ,OAAO,CAAC,kBAAkB;CAgB1B"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AudioSyncEngine = void 0;
|
|
4
|
+
const clock_sync_1 = require("./clock-sync");
|
|
5
|
+
const audio_clock_1 = require("./audio-clock");
|
|
6
|
+
const drift_detector_1 = require("./drift-detector");
|
|
7
|
+
const drift_corrector_1 = require("./drift-corrector");
|
|
8
|
+
const player_state_sync_1 = require("./player-state-sync");
|
|
9
|
+
const periodic_sync_1 = require("./periodic-sync");
|
|
10
|
+
const synchronized_start_1 = require("./synchronized-start");
|
|
11
|
+
const buffer_manager_1 = require("./buffer-manager");
|
|
12
|
+
class AudioSyncEngine {
|
|
13
|
+
constructor(_sendPing, config = {}) {
|
|
14
|
+
this.currentAudio = null;
|
|
15
|
+
this.loadTrackFn = null;
|
|
16
|
+
this.clockSync = new clock_sync_1.ClockSync(_sendPing);
|
|
17
|
+
this.audioClock = new audio_clock_1.AudioClock();
|
|
18
|
+
this.driftDetector = new drift_detector_1.DriftDetector(this.clockSync);
|
|
19
|
+
this.driftCorrector = new drift_corrector_1.DriftCorrector();
|
|
20
|
+
this.playerStateSync = new player_state_sync_1.PlayerStateSync();
|
|
21
|
+
this.synchronizedStart = new synchronized_start_1.SynchronizedStart();
|
|
22
|
+
const bufferConfig = {
|
|
23
|
+
minBufferSeconds: config.bufferMinSeconds ?? 3,
|
|
24
|
+
maxBufferSeconds: config.bufferMaxSeconds ?? 10,
|
|
25
|
+
targetBufferSeconds: config.bufferTargetSeconds ?? 5
|
|
26
|
+
};
|
|
27
|
+
this.bufferManager = new buffer_manager_1.BufferManager(bufferConfig);
|
|
28
|
+
const syncConfig = {
|
|
29
|
+
intervalMs: config.syncIntervalMs ?? 1500,
|
|
30
|
+
enabled: true
|
|
31
|
+
};
|
|
32
|
+
this.periodicSync = new periodic_sync_1.PeriodicSync(syncConfig, () => this.performPeriodicSync());
|
|
33
|
+
}
|
|
34
|
+
async initialize(audio, loadTrack) {
|
|
35
|
+
this.currentAudio = audio;
|
|
36
|
+
this.loadTrackFn = loadTrack;
|
|
37
|
+
await this.clockSync.sync();
|
|
38
|
+
this.periodicSync.start();
|
|
39
|
+
}
|
|
40
|
+
async handlePlayerUpdate(state) {
|
|
41
|
+
if (!this.currentAudio || !this.loadTrackFn) {
|
|
42
|
+
throw new Error('AudioSyncEngine not initialized');
|
|
43
|
+
}
|
|
44
|
+
await this.clockSync.sync();
|
|
45
|
+
const audioAdapter = {
|
|
46
|
+
currentTime: this.currentAudio.currentTime,
|
|
47
|
+
src: this.currentAudio.src,
|
|
48
|
+
play: () => this.currentAudio?.play(),
|
|
49
|
+
pause: () => this.currentAudio?.pause(),
|
|
50
|
+
setCurrentTime: (value) => {
|
|
51
|
+
if (this.currentAudio) {
|
|
52
|
+
this.currentAudio.currentTime = value;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
this.playerStateSync.handlePlayerUpdate(state, audioAdapter, this.loadTrackFn);
|
|
57
|
+
}
|
|
58
|
+
async performPeriodicSync() {
|
|
59
|
+
if (!this.currentAudio) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const lastState = this.playerStateSync.getLastState();
|
|
63
|
+
if (!lastState || !lastState.isPlaying) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const actualPosition = this.currentAudio.currentTime * 1000;
|
|
67
|
+
const driftResult = this.driftDetector.calculateDrift({ positionMs: lastState.positionMs, updatedAt: lastState.updatedAt, isPlaying: lastState.isPlaying }, actualPosition);
|
|
68
|
+
const audioAdapter = this.createAudioAdapter(this.currentAudio);
|
|
69
|
+
this.driftCorrector.correct(driftResult.drift, audioAdapter);
|
|
70
|
+
}
|
|
71
|
+
async scheduleSynchronizedStart(serverStartAt) {
|
|
72
|
+
if (!this.currentAudio) {
|
|
73
|
+
throw new Error('AudioSyncEngine not initialized');
|
|
74
|
+
}
|
|
75
|
+
const schedule = this.synchronizedStart.scheduleStart(serverStartAt, () => this.clockSync.syncedNow(), () => {
|
|
76
|
+
if (this.currentAudio) {
|
|
77
|
+
this.currentAudio.play();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return schedule;
|
|
81
|
+
}
|
|
82
|
+
async syncTime() {
|
|
83
|
+
await this.clockSync.sync();
|
|
84
|
+
}
|
|
85
|
+
getSyncedNow() {
|
|
86
|
+
return this.clockSync.syncedNow();
|
|
87
|
+
}
|
|
88
|
+
getClockOffset() {
|
|
89
|
+
return this.clockSync.getOffset();
|
|
90
|
+
}
|
|
91
|
+
getClockLatency() {
|
|
92
|
+
return this.clockSync.getLatency();
|
|
93
|
+
}
|
|
94
|
+
stop() {
|
|
95
|
+
this.periodicSync.stop();
|
|
96
|
+
this.audioClock.stop();
|
|
97
|
+
this.bufferManager.reset();
|
|
98
|
+
}
|
|
99
|
+
createAudioAdapter(audio) {
|
|
100
|
+
return {
|
|
101
|
+
get currentTime() {
|
|
102
|
+
return audio.currentTime;
|
|
103
|
+
},
|
|
104
|
+
set currentTime(value) {
|
|
105
|
+
audio.currentTime = value;
|
|
106
|
+
},
|
|
107
|
+
get playbackRate() {
|
|
108
|
+
return audio.playbackRate;
|
|
109
|
+
},
|
|
110
|
+
set playbackRate(value) {
|
|
111
|
+
audio.playbackRate = value;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
exports.AudioSyncEngine = AudioSyncEngine;
|
|
117
|
+
//# sourceMappingURL=audio-sync-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-sync-engine.js","sourceRoot":"","sources":["../src/audio-sync-engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface BufferConfig {
|
|
2
|
+
minBufferSeconds: number;
|
|
3
|
+
maxBufferSeconds: number;
|
|
4
|
+
targetBufferSeconds: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class BufferManager {
|
|
7
|
+
private readonly config;
|
|
8
|
+
private currentStreamUrl;
|
|
9
|
+
private isBuffering;
|
|
10
|
+
constructor(config: BufferConfig);
|
|
11
|
+
shouldReloadStream(newStreamUrl: string): boolean;
|
|
12
|
+
onStreamLoaded(streamUrl: string): void;
|
|
13
|
+
onStreamError(): void;
|
|
14
|
+
getBufferStatus(): {
|
|
15
|
+
currentStreamUrl: string | null;
|
|
16
|
+
isBuffering: boolean;
|
|
17
|
+
};
|
|
18
|
+
reset(): void;
|
|
19
|
+
isWithinBufferRange(bufferedSeconds: number): boolean;
|
|
20
|
+
getTargetBuffer(): number;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=buffer-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buffer-manager.d.ts","sourceRoot":"","sources":["../src/buffer-manager.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;CAC5B;AAED,qBAAa,aAAa;IAIb,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,WAAW,CAAS;gBAEC,MAAM,EAAE,YAAY;IAEjD,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAQjD,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKvC,aAAa,IAAI,IAAI;IAIrB,eAAe,IAAI;QAClB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;QAChC,WAAW,EAAE,OAAO,CAAC;KACrB;IAOD,KAAK,IAAI,IAAI;IAKb,mBAAmB,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO;IAOrD,eAAe,IAAI,MAAM;CAGzB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BufferManager = void 0;
|
|
4
|
+
class BufferManager {
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.config = config;
|
|
7
|
+
this.currentStreamUrl = null;
|
|
8
|
+
this.isBuffering = false;
|
|
9
|
+
}
|
|
10
|
+
shouldReloadStream(newStreamUrl) {
|
|
11
|
+
if (!this.currentStreamUrl) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
return newStreamUrl !== this.currentStreamUrl;
|
|
15
|
+
}
|
|
16
|
+
onStreamLoaded(streamUrl) {
|
|
17
|
+
this.currentStreamUrl = streamUrl;
|
|
18
|
+
this.isBuffering = false;
|
|
19
|
+
}
|
|
20
|
+
onStreamError() {
|
|
21
|
+
this.isBuffering = true;
|
|
22
|
+
}
|
|
23
|
+
getBufferStatus() {
|
|
24
|
+
return {
|
|
25
|
+
currentStreamUrl: this.currentStreamUrl,
|
|
26
|
+
isBuffering: this.isBuffering
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
reset() {
|
|
30
|
+
this.currentStreamUrl = null;
|
|
31
|
+
this.isBuffering = false;
|
|
32
|
+
}
|
|
33
|
+
isWithinBufferRange(bufferedSeconds) {
|
|
34
|
+
return (bufferedSeconds >= this.config.minBufferSeconds &&
|
|
35
|
+
bufferedSeconds <= this.config.maxBufferSeconds);
|
|
36
|
+
}
|
|
37
|
+
getTargetBuffer() {
|
|
38
|
+
return this.config.targetBufferSeconds;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.BufferManager = BufferManager;
|
|
42
|
+
//# sourceMappingURL=buffer-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buffer-manager.js","sourceRoot":"","sources":["../src/buffer-manager.ts"],"names":[],"mappings":";;;AAMA,MAAa,aAAa;IAIzB,YAA6B,MAAoB;QAApB,WAAM,GAAN,MAAM,CAAc;QAHzC,qBAAgB,GAAkB,IAAI,CAAC;QACvC,gBAAW,GAAG,KAAK,CAAC;IAEwB,CAAC;IAErD,kBAAkB,CAAC,YAAoB;QACtC,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC5B,OAAO,IAAI,CAAC;QACb,CAAC;QAED,OAAO,YAAY,KAAK,IAAI,CAAC,gBAAgB,CAAC;IAC/C,CAAC;IAED,cAAc,CAAC,SAAiB;QAC/B,IAAI,CAAC,gBAAgB,GAAG,SAAS,CAAC;QAClC,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;IAC1B,CAAC;IAED,aAAa;QACZ,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;IACzB,CAAC;IAED,eAAe;QAId,OAAO;YACN,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;YACvC,WAAW,EAAE,IAAI,CAAC,WAAW;SAC7B,CAAC;IACH,CAAC;IAED,KAAK;QACJ,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QAC7B,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;IAC1B,CAAC;IAED,mBAAmB,CAAC,eAAuB;QAC1C,OAAO,CACN,eAAe,IAAI,IAAI,CAAC,MAAM,CAAC,gBAAgB;YAC/C,eAAe,IAAI,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAC/C,CAAC;IACH,CAAC;IAED,eAAe;QACd,OAAO,IAAI,CAAC,MAAM,CAAC,mBAAmB,CAAC;IACxC,CAAC;CACD;AAhDD,sCAgDC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface ClockSyncResult {
|
|
2
|
+
offset: number;
|
|
3
|
+
latency: number;
|
|
4
|
+
syncedNow: number;
|
|
5
|
+
}
|
|
6
|
+
export interface PingPongMessage {
|
|
7
|
+
type: 'ping' | 'pong';
|
|
8
|
+
clientTimestamp: number;
|
|
9
|
+
serverTimestamp?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare class ClockSync {
|
|
12
|
+
private readonly sendPing;
|
|
13
|
+
private offset;
|
|
14
|
+
private latency;
|
|
15
|
+
private lastSync;
|
|
16
|
+
private readonly syncInterval;
|
|
17
|
+
constructor(sendPing: (timestamp: number) => Promise<number>);
|
|
18
|
+
sync(): Promise<ClockSyncResult>;
|
|
19
|
+
syncedNow(): number;
|
|
20
|
+
getOffset(): number;
|
|
21
|
+
getLatency(): number;
|
|
22
|
+
getLastSync(): number;
|
|
23
|
+
isStale(): boolean;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=clock-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clock-sync.d.ts","sourceRoot":"","sources":["../src/clock-sync.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,SAAS;IAMT,OAAO,CAAC,QAAQ,CAAC,QAAQ;IALrC,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiB;gBAEjB,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC;IAEvE,IAAI,IAAI,OAAO,CAAC,eAAe,CAAC;IAgBtC,SAAS,IAAI,MAAM;IAInB,SAAS,IAAI,MAAM;IAInB,UAAU,IAAI,MAAM;IAIpB,WAAW,IAAI,MAAM;IAIrB,OAAO,IAAI,OAAO;CAGlB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ClockSync = void 0;
|
|
4
|
+
class ClockSync {
|
|
5
|
+
constructor(sendPing) {
|
|
6
|
+
this.sendPing = sendPing;
|
|
7
|
+
this.offset = 0;
|
|
8
|
+
this.latency = 0;
|
|
9
|
+
this.lastSync = 0;
|
|
10
|
+
this.syncInterval = 30000;
|
|
11
|
+
}
|
|
12
|
+
async sync() {
|
|
13
|
+
const t1 = Date.now();
|
|
14
|
+
const t3 = await this.sendPing(t1);
|
|
15
|
+
const t4 = Date.now();
|
|
16
|
+
this.latency = (t4 - t1) / 2;
|
|
17
|
+
this.offset = t3 - (t1 + this.latency);
|
|
18
|
+
this.lastSync = Date.now();
|
|
19
|
+
return {
|
|
20
|
+
offset: this.offset,
|
|
21
|
+
latency: this.latency,
|
|
22
|
+
syncedNow: this.syncedNow()
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
syncedNow() {
|
|
26
|
+
return Date.now() + this.offset;
|
|
27
|
+
}
|
|
28
|
+
getOffset() {
|
|
29
|
+
return this.offset;
|
|
30
|
+
}
|
|
31
|
+
getLatency() {
|
|
32
|
+
return this.latency;
|
|
33
|
+
}
|
|
34
|
+
getLastSync() {
|
|
35
|
+
return this.lastSync;
|
|
36
|
+
}
|
|
37
|
+
isStale() {
|
|
38
|
+
return Date.now() - this.lastSync > this.syncInterval;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.ClockSync = ClockSync;
|
|
42
|
+
//# sourceMappingURL=clock-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clock-sync.js","sourceRoot":"","sources":["../src/clock-sync.ts"],"names":[],"mappings":";;;AAYA,MAAa,SAAS;IAMrB,YAA6B,QAAgD;QAAhD,aAAQ,GAAR,QAAQ,CAAwC;QALrE,WAAM,GAAW,CAAC,CAAC;QACnB,YAAO,GAAW,CAAC,CAAC;QACpB,aAAQ,GAAW,CAAC,CAAC;QACZ,iBAAY,GAAW,KAAK,CAAC;IAEkC,CAAC;IAEjF,KAAK,CAAC,IAAI;QACT,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACtB,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACnC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEtB,IAAI,CAAC,OAAO,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE3B,OAAO;YACN,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE;SAC3B,CAAC;IACH,CAAC;IAED,SAAS;QACR,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;IACjC,CAAC;IAED,SAAS;QACR,OAAO,IAAI,CAAC,MAAM,CAAC;IACpB,CAAC;IAED,UAAU;QACT,OAAO,IAAI,CAAC,OAAO,CAAC;IACrB,CAAC;IAED,WAAW;QACV,OAAO,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC;IAED,OAAO;QACN,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC;IACvD,CAAC;CACD;AA3CD,8BA2CC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface CorrectionResult {
|
|
2
|
+
applied: boolean;
|
|
3
|
+
method: 'ignore' | 'soft' | 'hard';
|
|
4
|
+
adjustment: number;
|
|
5
|
+
playbackRate?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface AudioElement {
|
|
8
|
+
get currentTime(): number;
|
|
9
|
+
set currentTime(value: number);
|
|
10
|
+
get playbackRate(): number;
|
|
11
|
+
set playbackRate(value: number);
|
|
12
|
+
}
|
|
13
|
+
export declare class DriftCorrector {
|
|
14
|
+
private readonly softCorrectionThreshold;
|
|
15
|
+
private readonly hardCorrectionThreshold;
|
|
16
|
+
private readonly slowRate;
|
|
17
|
+
private readonly fastRate;
|
|
18
|
+
private isCorrecting;
|
|
19
|
+
private currentPlaybackRate;
|
|
20
|
+
correct(drift: number, audio: AudioElement): CorrectionResult;
|
|
21
|
+
private applySoftCorrection;
|
|
22
|
+
private applyHardCorrection;
|
|
23
|
+
resetPlaybackRate(audio: AudioElement): void;
|
|
24
|
+
isCorrectingDrift(): boolean;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=drift-corrector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drift-corrector.d.ts","sourceRoot":"","sources":["../src/drift-corrector.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,WAAW,IAAI,MAAM,CAAC;IAC1B,IAAI,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE;IAC/B,IAAI,YAAY,IAAI,MAAM,CAAC;IAC3B,IAAI,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE;CAChC;AAED,qBAAa,cAAc;IAC1B,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAM;IAC9C,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAO;IAC/C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IAEjC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,mBAAmB,CAAO;IAElC,OAAO,CACN,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,YAAY,GACjB,gBAAgB;IAkBnB,OAAO,CAAC,mBAAmB;IA6B3B,OAAO,CAAC,mBAAmB;IAa3B,iBAAiB,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAQ5C,iBAAiB,IAAI,OAAO;CAG5B"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DriftCorrector = void 0;
|
|
4
|
+
class DriftCorrector {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.softCorrectionThreshold = 50;
|
|
7
|
+
this.hardCorrectionThreshold = 300;
|
|
8
|
+
this.slowRate = 0.98;
|
|
9
|
+
this.fastRate = 1.02;
|
|
10
|
+
this.isCorrecting = false;
|
|
11
|
+
this.currentPlaybackRate = 1.0;
|
|
12
|
+
}
|
|
13
|
+
correct(drift, audio) {
|
|
14
|
+
const absDrift = Math.abs(drift);
|
|
15
|
+
if (absDrift < this.softCorrectionThreshold) {
|
|
16
|
+
return {
|
|
17
|
+
applied: false,
|
|
18
|
+
method: 'ignore',
|
|
19
|
+
adjustment: 0
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (absDrift < this.hardCorrectionThreshold) {
|
|
23
|
+
return this.applySoftCorrection(drift, audio);
|
|
24
|
+
}
|
|
25
|
+
return this.applyHardCorrection(drift, audio);
|
|
26
|
+
}
|
|
27
|
+
applySoftCorrection(drift, audio) {
|
|
28
|
+
if (this.isCorrecting) {
|
|
29
|
+
return {
|
|
30
|
+
applied: false,
|
|
31
|
+
method: 'ignore',
|
|
32
|
+
adjustment: 0
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
this.isCorrecting = true;
|
|
36
|
+
const targetRate = drift > 0 ? this.slowRate : this.fastRate;
|
|
37
|
+
audio.playbackRate = targetRate;
|
|
38
|
+
this.currentPlaybackRate = targetRate;
|
|
39
|
+
setTimeout(() => {
|
|
40
|
+
audio.playbackRate = 1.0;
|
|
41
|
+
this.currentPlaybackRate = 1.0;
|
|
42
|
+
this.isCorrecting = false;
|
|
43
|
+
}, 2000);
|
|
44
|
+
return {
|
|
45
|
+
applied: true,
|
|
46
|
+
method: 'soft',
|
|
47
|
+
adjustment: drift,
|
|
48
|
+
playbackRate: targetRate
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
applyHardCorrection(drift, audio) {
|
|
52
|
+
const currentPosition = audio.currentTime;
|
|
53
|
+
const correctedPosition = currentPosition - drift;
|
|
54
|
+
audio.currentTime = correctedPosition;
|
|
55
|
+
return {
|
|
56
|
+
applied: true,
|
|
57
|
+
method: 'hard',
|
|
58
|
+
adjustment: -drift
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
resetPlaybackRate(audio) {
|
|
62
|
+
if (this.currentPlaybackRate !== 1.0) {
|
|
63
|
+
audio.playbackRate = 1.0;
|
|
64
|
+
this.currentPlaybackRate = 1.0;
|
|
65
|
+
this.isCorrecting = false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
isCorrectingDrift() {
|
|
69
|
+
return this.isCorrecting;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
exports.DriftCorrector = DriftCorrector;
|
|
73
|
+
//# sourceMappingURL=drift-corrector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drift-corrector.js","sourceRoot":"","sources":["../src/drift-corrector.ts"],"names":[],"mappings":";;;AAcA,MAAa,cAAc;IAA3B;QACkB,4BAAuB,GAAG,EAAE,CAAC;QAC7B,4BAAuB,GAAG,GAAG,CAAC;QAC9B,aAAQ,GAAG,IAAI,CAAC;QAChB,aAAQ,GAAG,IAAI,CAAC;QAEzB,iBAAY,GAAG,KAAK,CAAC;QACrB,wBAAmB,GAAG,GAAG,CAAC;IA4EnC,CAAC;IA1EA,OAAO,CACN,KAAa,EACb,KAAmB;QAEnB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAEjC,IAAI,QAAQ,GAAG,IAAI,CAAC,uBAAuB,EAAE,CAAC;YAC7C,OAAO;gBACN,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,QAAQ;gBAChB,UAAU,EAAE,CAAC;aACb,CAAC;QACH,CAAC;QAED,IAAI,QAAQ,GAAG,IAAI,CAAC,uBAAuB,EAAE,CAAC;YAC7C,OAAO,IAAI,CAAC,mBAAmB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC/C,CAAC;QAED,OAAO,IAAI,CAAC,mBAAmB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC/C,CAAC;IAEO,mBAAmB,CAAC,KAAa,EAAE,KAAmB;QAC7D,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,OAAO;gBACN,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,QAAQ;gBAChB,UAAU,EAAE,CAAC;aACb,CAAC;QACH,CAAC;QAED,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,MAAM,UAAU,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC;QAE7D,KAAK,CAAC,YAAY,GAAG,UAAU,CAAC;QAChC,IAAI,CAAC,mBAAmB,GAAG,UAAU,CAAC;QAEtC,UAAU,CAAC,GAAG,EAAE;YACf,KAAK,CAAC,YAAY,GAAG,GAAG,CAAC;YACzB,IAAI,CAAC,mBAAmB,GAAG,GAAG,CAAC;YAC/B,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;QAC3B,CAAC,EAAE,IAAI,CAAC,CAAC;QAET,OAAO;YACN,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,MAAM;YACd,UAAU,EAAE,KAAK;YACjB,YAAY,EAAE,UAAU;SACxB,CAAC;IACH,CAAC;IAEO,mBAAmB,CAAC,KAAa,EAAE,KAAmB;QAC7D,MAAM,eAAe,GAAG,KAAK,CAAC,WAAW,CAAC;QAC1C,MAAM,iBAAiB,GAAG,eAAe,GAAG,KAAK,CAAC;QAElD,KAAK,CAAC,WAAW,GAAG,iBAAiB,CAAC;QAEtC,OAAO;YACN,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,MAAM;YACd,UAAU,EAAE,CAAC,KAAK;SAClB,CAAC;IACH,CAAC;IAED,iBAAiB,CAAC,KAAmB;QACpC,IAAI,IAAI,CAAC,mBAAmB,KAAK,GAAG,EAAE,CAAC;YACtC,KAAK,CAAC,YAAY,GAAG,GAAG,CAAC;YACzB,IAAI,CAAC,mBAAmB,GAAG,GAAG,CAAC;YAC/B,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;QAC3B,CAAC;IACF,CAAC;IAED,iBAAiB;QAChB,OAAO,IAAI,CAAC,YAAY,CAAC;IAC1B,CAAC;CACD;AAnFD,wCAmFC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface DriftResult {
|
|
2
|
+
drift: number;
|
|
3
|
+
expectedPosition: number;
|
|
4
|
+
actualPosition: number;
|
|
5
|
+
timestamp: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ServerState {
|
|
8
|
+
positionMs: number;
|
|
9
|
+
updatedAt: number;
|
|
10
|
+
isPlaying: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare class DriftDetector {
|
|
13
|
+
private readonly clockSync;
|
|
14
|
+
constructor(clockSync: {
|
|
15
|
+
syncedNow: () => number;
|
|
16
|
+
});
|
|
17
|
+
calculateDrift(serverState: ServerState, actualPosition: number): DriftResult;
|
|
18
|
+
getDriftSeverity(drift: number): 'ignore' | 'soft' | 'hard';
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=drift-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drift-detector.d.ts","sourceRoot":"","sources":["../src/drift-detector.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;CACnB;AAED,qBAAa,aAAa;IACb,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE;QAAE,SAAS,EAAE,MAAM,MAAM,CAAA;KAAE;IAEnE,cAAc,CACb,WAAW,EAAE,WAAW,EACxB,cAAc,EAAE,MAAM,GACpB,WAAW;IAiBd,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM;CAa3D"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DriftDetector = void 0;
|
|
4
|
+
class DriftDetector {
|
|
5
|
+
constructor(clockSync) {
|
|
6
|
+
this.clockSync = clockSync;
|
|
7
|
+
}
|
|
8
|
+
calculateDrift(serverState, actualPosition) {
|
|
9
|
+
const syncedNow = this.clockSync.syncedNow();
|
|
10
|
+
const elapsed = syncedNow - serverState.updatedAt;
|
|
11
|
+
const expectedPosition = serverState.isPlaying
|
|
12
|
+
? serverState.positionMs + elapsed
|
|
13
|
+
: serverState.positionMs;
|
|
14
|
+
const drift = actualPosition - expectedPosition;
|
|
15
|
+
return {
|
|
16
|
+
drift,
|
|
17
|
+
expectedPosition,
|
|
18
|
+
actualPosition,
|
|
19
|
+
timestamp: syncedNow
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
getDriftSeverity(drift) {
|
|
23
|
+
const absDrift = Math.abs(drift);
|
|
24
|
+
if (absDrift < 50) {
|
|
25
|
+
return 'ignore';
|
|
26
|
+
}
|
|
27
|
+
if (absDrift < 300) {
|
|
28
|
+
return 'soft';
|
|
29
|
+
}
|
|
30
|
+
return 'hard';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.DriftDetector = DriftDetector;
|
|
34
|
+
//# sourceMappingURL=drift-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drift-detector.js","sourceRoot":"","sources":["../src/drift-detector.ts"],"names":[],"mappings":";;;AAaA,MAAa,aAAa;IACzB,YAA6B,SAAsC;QAAtC,cAAS,GAAT,SAAS,CAA6B;IAAG,CAAC;IAEvE,cAAc,CACb,WAAwB,EACxB,cAAsB;QAEtB,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC;QAC7C,MAAM,OAAO,GAAG,SAAS,GAAG,WAAW,CAAC,SAAS,CAAC;QAClD,MAAM,gBAAgB,GAAG,WAAW,CAAC,SAAS;YAC7C,CAAC,CAAC,WAAW,CAAC,UAAU,GAAG,OAAO;YAClC,CAAC,CAAC,WAAW,CAAC,UAAU,CAAC;QAE1B,MAAM,KAAK,GAAG,cAAc,GAAG,gBAAgB,CAAC;QAEhD,OAAO;YACN,KAAK;YACL,gBAAgB;YAChB,cAAc;YACd,SAAS,EAAE,SAAS;SACpB,CAAC;IACH,CAAC;IAED,gBAAgB,CAAC,KAAa;QAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAEjC,IAAI,QAAQ,GAAG,EAAE,EAAE,CAAC;YACnB,OAAO,QAAQ,CAAC;QACjB,CAAC;QAED,IAAI,QAAQ,GAAG,GAAG,EAAE,CAAC;YACpB,OAAO,MAAM,CAAC;QACf,CAAC;QAED,OAAO,MAAM,CAAC;IACf,CAAC;CACD;AApCD,sCAoCC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { ClockSync, type ClockSyncResult, type PingPongMessage } from './clock-sync';
|
|
2
|
+
export { AudioClock } from './audio-clock';
|
|
3
|
+
export { DriftDetector, type DriftResult, type ServerState } from './drift-detector';
|
|
4
|
+
export { DriftCorrector, type CorrectionResult, type AudioElement } from './drift-corrector';
|
|
5
|
+
export { PlayerStateSync, type PlayerState, type TrackMetadata } from './player-state-sync';
|
|
6
|
+
export { PeriodicSync, type SyncConfig } from './periodic-sync';
|
|
7
|
+
export { SynchronizedStart, type StartSchedule } from './synchronized-start';
|
|
8
|
+
export { BufferManager, type BufferConfig } from './buffer-manager';
|
|
9
|
+
export { AudioSyncEngine, type AudioSyncEngineConfig } from './audio-sync-engine';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EAAE,cAAc,EAAE,KAAK,gBAAgB,EAAE,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC7F,OAAO,EAAE,eAAe,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC5F,OAAO,EAAE,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AudioSyncEngine = exports.BufferManager = exports.SynchronizedStart = exports.PeriodicSync = exports.PlayerStateSync = exports.DriftCorrector = exports.DriftDetector = exports.AudioClock = exports.ClockSync = void 0;
|
|
4
|
+
var clock_sync_1 = require("./clock-sync");
|
|
5
|
+
Object.defineProperty(exports, "ClockSync", { enumerable: true, get: function () { return clock_sync_1.ClockSync; } });
|
|
6
|
+
var audio_clock_1 = require("./audio-clock");
|
|
7
|
+
Object.defineProperty(exports, "AudioClock", { enumerable: true, get: function () { return audio_clock_1.AudioClock; } });
|
|
8
|
+
var drift_detector_1 = require("./drift-detector");
|
|
9
|
+
Object.defineProperty(exports, "DriftDetector", { enumerable: true, get: function () { return drift_detector_1.DriftDetector; } });
|
|
10
|
+
var drift_corrector_1 = require("./drift-corrector");
|
|
11
|
+
Object.defineProperty(exports, "DriftCorrector", { enumerable: true, get: function () { return drift_corrector_1.DriftCorrector; } });
|
|
12
|
+
var player_state_sync_1 = require("./player-state-sync");
|
|
13
|
+
Object.defineProperty(exports, "PlayerStateSync", { enumerable: true, get: function () { return player_state_sync_1.PlayerStateSync; } });
|
|
14
|
+
var periodic_sync_1 = require("./periodic-sync");
|
|
15
|
+
Object.defineProperty(exports, "PeriodicSync", { enumerable: true, get: function () { return periodic_sync_1.PeriodicSync; } });
|
|
16
|
+
var synchronized_start_1 = require("./synchronized-start");
|
|
17
|
+
Object.defineProperty(exports, "SynchronizedStart", { enumerable: true, get: function () { return synchronized_start_1.SynchronizedStart; } });
|
|
18
|
+
var buffer_manager_1 = require("./buffer-manager");
|
|
19
|
+
Object.defineProperty(exports, "BufferManager", { enumerable: true, get: function () { return buffer_manager_1.BufferManager; } });
|
|
20
|
+
var audio_sync_engine_1 = require("./audio-sync-engine");
|
|
21
|
+
Object.defineProperty(exports, "AudioSyncEngine", { enumerable: true, get: function () { return audio_sync_engine_1.AudioSyncEngine; } });
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,2CAAqF;AAA5E,uGAAA,SAAS,OAAA;AAClB,6CAA2C;AAAlC,yGAAA,UAAU,OAAA;AACnB,mDAAqF;AAA5E,+GAAA,aAAa,OAAA;AACtB,qDAA6F;AAApF,iHAAA,cAAc,OAAA;AACvB,yDAA4F;AAAnF,oHAAA,eAAe,OAAA;AACxB,iDAAgE;AAAvD,6GAAA,YAAY,OAAA;AACrB,2DAA6E;AAApE,uHAAA,iBAAiB,OAAA;AAC1B,mDAAoE;AAA3D,+GAAA,aAAa,OAAA;AACtB,yDAAkF;AAAzE,oHAAA,eAAe,OAAA"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface SyncConfig {
|
|
2
|
+
intervalMs: number;
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare class PeriodicSync {
|
|
6
|
+
private readonly config;
|
|
7
|
+
private readonly syncFn;
|
|
8
|
+
private intervalId;
|
|
9
|
+
private isRunning;
|
|
10
|
+
constructor(config: SyncConfig, syncFn: () => void);
|
|
11
|
+
start(): void;
|
|
12
|
+
stop(): void;
|
|
13
|
+
isSyncing(): boolean;
|
|
14
|
+
updateConfig(config: Partial<SyncConfig>): void;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=periodic-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"periodic-sync.d.ts","sourceRoot":"","sources":["../src/periodic-sync.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CACjB;AAED,qBAAa,YAAY;IAKvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IALxB,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,SAAS,CAAS;gBAGR,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,IAAI;IAGpC,KAAK,IAAI,IAAI;IAWb,IAAI,IAAI,IAAI;IAQZ,SAAS,IAAI,OAAO;IAIpB,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI;CAS/C"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PeriodicSync = void 0;
|
|
4
|
+
class PeriodicSync {
|
|
5
|
+
constructor(config, syncFn) {
|
|
6
|
+
this.config = config;
|
|
7
|
+
this.syncFn = syncFn;
|
|
8
|
+
this.intervalId = null;
|
|
9
|
+
this.isRunning = false;
|
|
10
|
+
}
|
|
11
|
+
start() {
|
|
12
|
+
if (this.isRunning || !this.config.enabled) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
this.isRunning = true;
|
|
16
|
+
this.intervalId = window.setInterval(() => {
|
|
17
|
+
this.syncFn();
|
|
18
|
+
}, this.config.intervalMs);
|
|
19
|
+
}
|
|
20
|
+
stop() {
|
|
21
|
+
if (this.intervalId) {
|
|
22
|
+
window.clearInterval(this.intervalId);
|
|
23
|
+
this.intervalId = null;
|
|
24
|
+
}
|
|
25
|
+
this.isRunning = false;
|
|
26
|
+
}
|
|
27
|
+
isSyncing() {
|
|
28
|
+
return this.isRunning;
|
|
29
|
+
}
|
|
30
|
+
updateConfig(config) {
|
|
31
|
+
Object.assign(this.config, config);
|
|
32
|
+
if (this.isRunning && !config.enabled) {
|
|
33
|
+
this.stop();
|
|
34
|
+
}
|
|
35
|
+
else if (!this.isRunning && config.enabled) {
|
|
36
|
+
this.start();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.PeriodicSync = PeriodicSync;
|
|
41
|
+
//# sourceMappingURL=periodic-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"periodic-sync.js","sourceRoot":"","sources":["../src/periodic-sync.ts"],"names":[],"mappings":";;;AAKA,MAAa,YAAY;IAIxB,YACkB,MAAkB,EAClB,MAAkB;QADlB,WAAM,GAAN,MAAM,CAAY;QAClB,WAAM,GAAN,MAAM,CAAY;QAL5B,eAAU,GAAkB,IAAI,CAAC;QACjC,cAAS,GAAG,KAAK,CAAC;IAKvB,CAAC;IAEJ,KAAK;QACJ,IAAI,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAC5C,OAAO;QACR,CAAC;QAED,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE;YACzC,IAAI,CAAC,MAAM,EAAE,CAAC;QACf,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC5B,CAAC;IAED,IAAI;QACH,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACtC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACxB,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;IACxB,CAAC;IAED,SAAS;QACR,OAAO,IAAI,CAAC,SAAS,CAAC;IACvB,CAAC;IAED,YAAY,CAAC,MAA2B;QACvC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAEnC,IAAI,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACvC,IAAI,CAAC,IAAI,EAAE,CAAC;QACb,CAAC;aAAM,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YAC9C,IAAI,CAAC,KAAK,EAAE,CAAC;QACd,CAAC;IACF,CAAC;CACD;AAzCD,oCAyCC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface PlayerState {
|
|
2
|
+
userId: string;
|
|
3
|
+
activeDeviceId: string | null;
|
|
4
|
+
trackId: string | null;
|
|
5
|
+
isPlaying: boolean;
|
|
6
|
+
positionMs: number;
|
|
7
|
+
updatedAt: number;
|
|
8
|
+
lastServerSyncAt: number;
|
|
9
|
+
queue?: string[];
|
|
10
|
+
currentIndex?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface TrackMetadata {
|
|
13
|
+
id: string;
|
|
14
|
+
streamUrl: string;
|
|
15
|
+
duration: number;
|
|
16
|
+
source: 's3' | 'cdn' | 'proxy';
|
|
17
|
+
}
|
|
18
|
+
export declare class PlayerStateSync {
|
|
19
|
+
private currentTrackId;
|
|
20
|
+
private currentStreamUrl;
|
|
21
|
+
private lastState;
|
|
22
|
+
handlePlayerUpdate(state: PlayerState, audio: {
|
|
23
|
+
currentTime: number;
|
|
24
|
+
src: string;
|
|
25
|
+
play: () => void;
|
|
26
|
+
pause: () => void;
|
|
27
|
+
setCurrentTime: (value: number) => void;
|
|
28
|
+
}, loadTrack: (trackId: string) => Promise<TrackMetadata>): void;
|
|
29
|
+
private loadNewTrack;
|
|
30
|
+
private syncPosition;
|
|
31
|
+
getCurrentTrackId(): string | null;
|
|
32
|
+
getCurrentStreamUrl(): string | null;
|
|
33
|
+
getLastState(): PlayerState | null;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=player-state-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player-state-sync.d.ts","sourceRoot":"","sources":["../src/player-state-sync.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,IAAI,GAAG,KAAK,GAAG,OAAO,CAAC;CAC/B;AAED,qBAAa,eAAe;IAC3B,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,SAAS,CAA4B;IAE7C,kBAAkB,CACjB,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,IAAI,CAAC;QAAC,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,EACzH,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,GACpD,IAAI;YAgBO,YAAY;IAgB1B,OAAO,CAAC,YAAY;IAgBpB,iBAAiB,IAAI,MAAM,GAAG,IAAI;IAIlC,mBAAmB,IAAI,MAAM,GAAG,IAAI;IAIpC,YAAY,IAAI,WAAW,GAAG,IAAI;CAGlC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PlayerStateSync = void 0;
|
|
4
|
+
class PlayerStateSync {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.currentTrackId = null;
|
|
7
|
+
this.currentStreamUrl = null;
|
|
8
|
+
this.lastState = null;
|
|
9
|
+
}
|
|
10
|
+
handlePlayerUpdate(state, audio, loadTrack) {
|
|
11
|
+
this.lastState = state;
|
|
12
|
+
if (state.trackId !== this.currentTrackId && state.trackId) {
|
|
13
|
+
this.loadNewTrack(state.trackId, audio, loadTrack);
|
|
14
|
+
}
|
|
15
|
+
if (state.isPlaying) {
|
|
16
|
+
audio.play();
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
audio.pause();
|
|
20
|
+
}
|
|
21
|
+
this.syncPosition(state.positionMs, state.updatedAt, state.isPlaying, audio);
|
|
22
|
+
}
|
|
23
|
+
async loadNewTrack(trackId, audio, loadTrack) {
|
|
24
|
+
try {
|
|
25
|
+
const metadata = await loadTrack(trackId);
|
|
26
|
+
this.currentTrackId = trackId;
|
|
27
|
+
this.currentStreamUrl = metadata.streamUrl;
|
|
28
|
+
audio.src = metadata.streamUrl;
|
|
29
|
+
audio.currentTime = 0;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.error('Failed to load track:', error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
syncPosition(serverPosition, serverUpdatedAt, isPlaying, audio) {
|
|
36
|
+
if (!isPlaying) {
|
|
37
|
+
audio.setCurrentTime(serverPosition / 1000);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const elapsed = Date.now() - serverUpdatedAt;
|
|
41
|
+
const expectedPosition = serverPosition + elapsed;
|
|
42
|
+
audio.setCurrentTime(expectedPosition / 1000);
|
|
43
|
+
}
|
|
44
|
+
getCurrentTrackId() {
|
|
45
|
+
return this.currentTrackId;
|
|
46
|
+
}
|
|
47
|
+
getCurrentStreamUrl() {
|
|
48
|
+
return this.currentStreamUrl;
|
|
49
|
+
}
|
|
50
|
+
getLastState() {
|
|
51
|
+
return this.lastState;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.PlayerStateSync = PlayerStateSync;
|
|
55
|
+
//# sourceMappingURL=player-state-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player-state-sync.js","sourceRoot":"","sources":["../src/player-state-sync.ts"],"names":[],"mappings":";;;AAmBA,MAAa,eAAe;IAA5B;QACS,mBAAc,GAAkB,IAAI,CAAC;QACrC,qBAAgB,GAAkB,IAAI,CAAC;QACvC,cAAS,GAAuB,IAAI,CAAC;IAiE9C,CAAC;IA/DA,kBAAkB,CACjB,KAAkB,EAClB,KAAyH,EACzH,SAAsD;QAEtD,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QAEvB,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC,cAAc,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YAC5D,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QACpD,CAAC;QAED,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YACrB,KAAK,CAAC,IAAI,EAAE,CAAC;QACd,CAAC;aAAM,CAAC;YACP,KAAK,CAAC,KAAK,EAAE,CAAC;QACf,CAAC;QAED,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAC9E,CAAC;IAEO,KAAK,CAAC,YAAY,CACzB,OAAe,EACf,KAA2C,EAC3C,SAAsD;QAEtD,IAAI,CAAC;YACJ,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;YAC1C,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC;YAC9B,IAAI,CAAC,gBAAgB,GAAG,QAAQ,CAAC,SAAS,CAAC;YAC3C,KAAK,CAAC,GAAG,GAAG,QAAQ,CAAC,SAAS,CAAC;YAC/B,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC;QACvB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,KAAK,CAAC,CAAC;QAC/C,CAAC;IACF,CAAC;IAEO,YAAY,CACnB,cAAsB,EACtB,eAAuB,EACvB,SAAkB,EAClB,KAAuE;QAEvE,IAAI,CAAC,SAAS,EAAE,CAAC;YAChB,KAAK,CAAC,cAAc,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;YAC5C,OAAO;QACR,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,eAAe,CAAC;QAC7C,MAAM,gBAAgB,GAAG,cAAc,GAAG,OAAO,CAAC;QAClD,KAAK,CAAC,cAAc,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IAC/C,CAAC;IAED,iBAAiB;QAChB,OAAO,IAAI,CAAC,cAAc,CAAC;IAC5B,CAAC;IAED,mBAAmB;QAClB,OAAO,IAAI,CAAC,gBAAgB,CAAC;IAC9B,CAAC;IAED,YAAY;QACX,OAAO,IAAI,CAAC,SAAS,CAAC;IACvB,CAAC;CACD;AApED,0CAoEC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface StartSchedule {
|
|
2
|
+
startAt: number;
|
|
3
|
+
delay: number;
|
|
4
|
+
}
|
|
5
|
+
export declare class SynchronizedStart {
|
|
6
|
+
scheduleStart(serverStartAt: number, syncedNow: () => number, startFn: () => void): StartSchedule;
|
|
7
|
+
cancelStart(timeoutId: number): void;
|
|
8
|
+
calculateDelay(serverStartAt: number, syncedNow: () => number): number;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=synchronized-start.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synchronized-start.d.ts","sourceRoot":"","sources":["../src/synchronized-start.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,iBAAiB;IAC7B,aAAa,CACZ,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,MAAM,EACvB,OAAO,EAAE,MAAM,IAAI,GACjB,aAAa;IAchB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIpC,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,MAAM,GAAG,MAAM;CAItE"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SynchronizedStart = void 0;
|
|
4
|
+
class SynchronizedStart {
|
|
5
|
+
scheduleStart(serverStartAt, syncedNow, startFn) {
|
|
6
|
+
const now = syncedNow();
|
|
7
|
+
const delay = Math.max(0, serverStartAt - now);
|
|
8
|
+
setTimeout(() => {
|
|
9
|
+
startFn();
|
|
10
|
+
}, delay);
|
|
11
|
+
return {
|
|
12
|
+
startAt: serverStartAt,
|
|
13
|
+
delay
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
cancelStart(timeoutId) {
|
|
17
|
+
clearTimeout(timeoutId);
|
|
18
|
+
}
|
|
19
|
+
calculateDelay(serverStartAt, syncedNow) {
|
|
20
|
+
const now = syncedNow();
|
|
21
|
+
return Math.max(0, serverStartAt - now);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.SynchronizedStart = SynchronizedStart;
|
|
25
|
+
//# sourceMappingURL=synchronized-start.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synchronized-start.js","sourceRoot":"","sources":["../src/synchronized-start.ts"],"names":[],"mappings":";;;AAKA,MAAa,iBAAiB;IAC7B,aAAa,CACZ,aAAqB,EACrB,SAAuB,EACvB,OAAmB;QAEnB,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,aAAa,GAAG,GAAG,CAAC,CAAC;QAE/C,UAAU,CAAC,GAAG,EAAE;YACf,OAAO,EAAE,CAAC;QACX,CAAC,EAAE,KAAK,CAAC,CAAC;QAEV,OAAO;YACN,OAAO,EAAE,aAAa;YACtB,KAAK;SACL,CAAC;IACH,CAAC;IAED,WAAW,CAAC,SAAiB;QAC5B,YAAY,CAAC,SAAS,CAAC,CAAC;IACzB,CAAC;IAED,cAAc,CAAC,aAAqB,EAAE,SAAuB;QAC5D,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,aAAa,GAAG,GAAG,CAAC,CAAC;IACzC,CAAC;CACD;AA3BD,8CA2BC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@axsnull/audio-sync-engine",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "Production-grade client-side audio synchronization engine for distributed playback systems",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"prepublishOnly": "npm run build",
|
|
13
|
+
"clean": "rm -rf dist"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"audio",
|
|
17
|
+
"sync",
|
|
18
|
+
"playback",
|
|
19
|
+
"audio-sync",
|
|
20
|
+
"websocket",
|
|
21
|
+
"real-time",
|
|
22
|
+
"drift-correction",
|
|
23
|
+
"clock-sync",
|
|
24
|
+
"audio-engine"
|
|
25
|
+
],
|
|
26
|
+
"author": "axsnull",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/axsnull/axsnull-audio-sync-engine.git"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/axsnull/axsnull-audio-sync-engine/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/axsnull/axsnull-audio-sync-engine#readme",
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"typescript": "^5.0.0"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=16.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|