synxed-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +16 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +16 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +103 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +59 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Synxed SDK
|
|
2
|
+
|
|
3
|
+
The official Synxed SDK for frontend developers to integrate high-quality music streaming into their applications.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/synxed-sdk)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **Simple Integration**: Play songs or playlists with just a few lines of code.
|
|
11
|
+
- **Real-time Streaming**: Powered by Socket.IO and Protobuf for low-latency control.
|
|
12
|
+
- **Framework Agnostic**: Works with React, Vue, Angular, or Vanilla JS.
|
|
13
|
+
- **Type Safe**: Fully written in TypeScript with comprehensive definitions.
|
|
14
|
+
- **Cross-Browser**: Robust audio playback using Howler.js.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install synxed-sdk
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { SynxedPlayer } from 'synxed-sdk';
|
|
26
|
+
|
|
27
|
+
const player = new SynxedPlayer({
|
|
28
|
+
apiKey: 'YOUR_SYNXED_API_KEY',
|
|
29
|
+
serverUrl: 'https://synxed-backend-production.up.railway.app/',
|
|
30
|
+
autoConnect: true
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Play a song by Catalog ID
|
|
34
|
+
player.playSong({ catalogTrackId: 'track-uuid-here' });
|
|
35
|
+
|
|
36
|
+
// Listen for state changes
|
|
37
|
+
player.on('stateChange', (state) => {
|
|
38
|
+
console.log('Player status:', state.status);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Listen for time updates
|
|
42
|
+
player.on('timeUpdate', ({ currentTime, duration }) => {
|
|
43
|
+
console.log(`Progress: ${currentTime} / ${duration}`);
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## API Reference
|
|
48
|
+
|
|
49
|
+
### `new SynxedPlayer(config)`
|
|
50
|
+
|
|
51
|
+
Initializes the player.
|
|
52
|
+
|
|
53
|
+
- `apiKey`: Your developer API key.
|
|
54
|
+
- `serverUrl`: The Synxed backend URL.
|
|
55
|
+
- `autoConnect`: (Optional) Whether to connect immediately.
|
|
56
|
+
|
|
57
|
+
### Methods
|
|
58
|
+
|
|
59
|
+
- `playSong(options)`: Plays a single track.
|
|
60
|
+
- `playPlaylist(options)`: Starts playback from a playlist code.
|
|
61
|
+
- `pause()`: Pauses the current track.
|
|
62
|
+
- `resume()`: Resumes playback.
|
|
63
|
+
- `stop()`: Stops playback and unloads the track.
|
|
64
|
+
- `skip()`: Skips to the next track in the queue.
|
|
65
|
+
- `seek(ms)`: Seeks to a specific position in milliseconds.
|
|
66
|
+
- `setVolume(0-1)`: Adjusts the player volume.
|
|
67
|
+
- `destroy()`: Cleans up the player and disconnects the socket.
|
|
68
|
+
|
|
69
|
+
### Events
|
|
70
|
+
|
|
71
|
+
- `stateChange`: Fired when the playback status changes (`idle`, `loading`, `playing`, `paused`, `error`).
|
|
72
|
+
- `timeUpdate`: Fired during playback with current position and duration.
|
|
73
|
+
- `trackChange`: Fired when the current track changes.
|
|
74
|
+
- `connected`: Fired when the socket connects successfully.
|
|
75
|
+
- `disconnected`: Fired when the socket disconnects.
|
|
76
|
+
- `error`: Fired on any player or connection error.
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
MIT
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';var fs=require('fs'),path=require('path'),url=require('url');var _documentCurrentScript=typeof document!=='undefined'?document.currentScript:null;var r=url.fileURLToPath(new URL(".",(typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.js', document.baseURI).href))));function i(){try{let e=path.join(r,"../package.json");return JSON.parse(fs.readFileSync(e,"utf8")).version}catch{return "unknown"}}console.log(`
|
|
3
|
+
Synxed SDK CLI v${i()}
|
|
4
|
+
----------------------------
|
|
5
|
+
This is the developer SDK for Synx.
|
|
6
|
+
|
|
7
|
+
To use the SDK in your project, install it locally:
|
|
8
|
+
npm install synxed-sdk
|
|
9
|
+
|
|
10
|
+
Usage in code:
|
|
11
|
+
import { SynxedPlayer } from 'synxed-sdk';
|
|
12
|
+
|
|
13
|
+
For more documentation, visit:
|
|
14
|
+
https://github.com/clementcyberknight/synxed-sdk
|
|
15
|
+
`);//# sourceMappingURL=cli.js.map
|
|
16
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"names":["__dirname","fileURLToPath","getVersion","pkgPath","join","readFileSync"],"mappings":";+JAKA,IAAMA,CAAAA,CAAYC,iBAAAA,CAAc,IAAI,GAAA,CAAI,GAAA,CAAK,wPAAe,CAAC,CAAA,CAE7D,SAASC,CAAAA,EAAa,CACpB,GAAI,CACF,IAAMC,EAAUC,SAAAA,CAAKJ,CAAAA,CAAW,iBAAiB,CAAA,CAEjD,OADY,IAAA,CAAK,KAAA,CAAMK,eAAAA,CAAaF,CAAAA,CAAS,MAAM,CAAC,CAAA,CACzC,OACb,CAAA,KAAY,CACV,OAAO,SACT,CACF,CAEA,OAAA,CAAQ,GAAA,CAAI;AAAA,kBAAA,EACQD,GAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAY/B,CAAA","file":"cli.js","sourcesContent":["#!/usr/bin/env node\nimport { readFileSync } from 'fs';\nimport { join } from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __dirname = fileURLToPath(new URL('.', import.meta.url));\n\nfunction getVersion() {\n try {\n const pkgPath = join(__dirname, '../package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));\n return pkg.version;\n } catch (e) {\n return 'unknown';\n }\n}\n\nconsole.log(`\n Synxed SDK CLI v${getVersion()}\n ----------------------------\n This is the developer SDK for Synx.\n \n To use the SDK in your project, install it locally:\n npm install synxed-sdk\n \n Usage in code:\n import { SynxedPlayer } from 'synxed-sdk';\n \n For more documentation, visit:\n https://github.com/clementcyberknight/synxed-sdk\n`);\n"]}
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {readFileSync}from'fs';import {join}from'path';import {fileURLToPath}from'url';var r=fileURLToPath(new URL(".",import.meta.url));function i(){try{let e=join(r,"../package.json");return JSON.parse(readFileSync(e,"utf8")).version}catch{return "unknown"}}console.log(`
|
|
3
|
+
Synxed SDK CLI v${i()}
|
|
4
|
+
----------------------------
|
|
5
|
+
This is the developer SDK for Synx.
|
|
6
|
+
|
|
7
|
+
To use the SDK in your project, install it locally:
|
|
8
|
+
npm install synxed-sdk
|
|
9
|
+
|
|
10
|
+
Usage in code:
|
|
11
|
+
import { SynxedPlayer } from 'synxed-sdk';
|
|
12
|
+
|
|
13
|
+
For more documentation, visit:
|
|
14
|
+
https://github.com/clementcyberknight/synxed-sdk
|
|
15
|
+
`);//# sourceMappingURL=cli.mjs.map
|
|
16
|
+
//# sourceMappingURL=cli.mjs.map
|
package/dist/cli.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"names":["__dirname","fileURLToPath","getVersion","pkgPath","join","readFileSync"],"mappings":";sFAKA,IAAMA,CAAAA,CAAYC,aAAAA,CAAc,IAAI,GAAA,CAAI,GAAA,CAAK,YAAY,GAAG,CAAC,CAAA,CAE7D,SAASC,CAAAA,EAAa,CACpB,GAAI,CACF,IAAMC,EAAUC,IAAAA,CAAKJ,CAAAA,CAAW,iBAAiB,CAAA,CAEjD,OADY,IAAA,CAAK,KAAA,CAAMK,YAAAA,CAAaF,CAAAA,CAAS,MAAM,CAAC,CAAA,CACzC,OACb,CAAA,KAAY,CACV,OAAO,SACT,CACF,CAEA,OAAA,CAAQ,GAAA,CAAI;AAAA,kBAAA,EACQD,GAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAY/B,CAAA","file":"cli.mjs","sourcesContent":["#!/usr/bin/env node\nimport { readFileSync } from 'fs';\nimport { join } from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __dirname = fileURLToPath(new URL('.', import.meta.url));\n\nfunction getVersion() {\n try {\n const pkgPath = join(__dirname, '../package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));\n return pkg.version;\n } catch (e) {\n return 'unknown';\n }\n}\n\nconsole.log(`\n Synxed SDK CLI v${getVersion()}\n ----------------------------\n This is the developer SDK for Synx.\n \n To use the SDK in your project, install it locally:\n npm install synxed-sdk\n \n Usage in code:\n import { SynxedPlayer } from 'synxed-sdk';\n \n For more documentation, visit:\n https://github.com/clementcyberknight/synxed-sdk\n`);\n"]}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
declare class EventEmitter<Events extends Record<string, any>> {
|
|
2
|
+
private listeners;
|
|
3
|
+
on<K extends keyof Events>(event: K, handler: Events[K]): void;
|
|
4
|
+
off<K extends keyof Events>(event: K, handler: Events[K]): void;
|
|
5
|
+
once<K extends keyof Events>(event: K, handler: Events[K]): void;
|
|
6
|
+
protected emit<K extends keyof Events>(event: K, ...args: Parameters<Events[K]>): void;
|
|
7
|
+
removeAllListeners(): void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface SynxedConfig {
|
|
11
|
+
apiKey: string;
|
|
12
|
+
serverUrl: string;
|
|
13
|
+
autoConnect?: boolean;
|
|
14
|
+
}
|
|
15
|
+
interface PlaySongOptions {
|
|
16
|
+
catalogTrackId?: string;
|
|
17
|
+
internalTrackId?: string;
|
|
18
|
+
listenerId?: string;
|
|
19
|
+
}
|
|
20
|
+
interface PlayPlaylistOptions {
|
|
21
|
+
playlistCode: string;
|
|
22
|
+
listenerId?: string;
|
|
23
|
+
}
|
|
24
|
+
interface TrackInfo {
|
|
25
|
+
id: string;
|
|
26
|
+
title?: string;
|
|
27
|
+
artist?: string;
|
|
28
|
+
duration?: number;
|
|
29
|
+
albumArt?: string;
|
|
30
|
+
}
|
|
31
|
+
interface PlayerState {
|
|
32
|
+
status: 'idle' | 'loading' | 'playing' | 'paused' | 'error';
|
|
33
|
+
currentTrack?: TrackInfo;
|
|
34
|
+
currentTime: number;
|
|
35
|
+
duration: number;
|
|
36
|
+
volume: number;
|
|
37
|
+
}
|
|
38
|
+
interface SynxedEvents {
|
|
39
|
+
stateChange: (state: PlayerState) => void;
|
|
40
|
+
timeUpdate: (time: {
|
|
41
|
+
currentTime: number;
|
|
42
|
+
duration: number;
|
|
43
|
+
}) => void;
|
|
44
|
+
trackChange: (track: TrackInfo) => void;
|
|
45
|
+
error: (error: Error) => void;
|
|
46
|
+
connected: () => void;
|
|
47
|
+
disconnected: (reason: string) => void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
declare class SynxedPlayer extends EventEmitter<SynxedEvents> {
|
|
51
|
+
private transport;
|
|
52
|
+
private audio;
|
|
53
|
+
private playlist;
|
|
54
|
+
private config;
|
|
55
|
+
private status;
|
|
56
|
+
constructor(config: SynxedConfig);
|
|
57
|
+
private setupListeners;
|
|
58
|
+
private connect;
|
|
59
|
+
playSong(options: PlaySongOptions): Promise<void>;
|
|
60
|
+
playPlaylist(options: PlayPlaylistOptions): Promise<void>;
|
|
61
|
+
pause(): void;
|
|
62
|
+
resume(): void;
|
|
63
|
+
stop(): void;
|
|
64
|
+
skip(): void;
|
|
65
|
+
seek(ms: number): void;
|
|
66
|
+
setVolume(volume: number): void;
|
|
67
|
+
private handleServerMessage;
|
|
68
|
+
private handleTrackEnded;
|
|
69
|
+
private updateStatus;
|
|
70
|
+
destroy(): void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
declare class SynxedError extends Error {
|
|
74
|
+
code?: string | undefined;
|
|
75
|
+
constructor(message: string, code?: string | undefined);
|
|
76
|
+
}
|
|
77
|
+
declare class SynxedConnectionError extends SynxedError {
|
|
78
|
+
constructor(message: string);
|
|
79
|
+
}
|
|
80
|
+
declare class SynxedPlaybackError extends SynxedError {
|
|
81
|
+
constructor(error: any);
|
|
82
|
+
}
|
|
83
|
+
declare class SynxedProtocolError extends SynxedError {
|
|
84
|
+
constructor(message: string);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
declare enum ContentKind {
|
|
88
|
+
UNSPECIFIED = 0,
|
|
89
|
+
SONG = 1,
|
|
90
|
+
PLAYLIST = 2,
|
|
91
|
+
CATEGORY = 3
|
|
92
|
+
}
|
|
93
|
+
declare enum ErrorCode {
|
|
94
|
+
UNSPECIFIED = 0,
|
|
95
|
+
UNAUTHORIZED = 1,
|
|
96
|
+
VALIDATION_ERROR = 2,
|
|
97
|
+
NOT_FOUND = 3,
|
|
98
|
+
PROCESSING = 4,
|
|
99
|
+
SERVICE_UNAVAILABLE = 5,
|
|
100
|
+
BAD_PROTOBUF = 6
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export { ContentKind, ErrorCode, type PlayPlaylistOptions, type PlaySongOptions, type PlayerState, type SynxedConfig, SynxedConnectionError, SynxedError, type SynxedEvents, SynxedPlaybackError, SynxedPlayer, SynxedProtocolError, type TrackInfo };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
declare class EventEmitter<Events extends Record<string, any>> {
|
|
2
|
+
private listeners;
|
|
3
|
+
on<K extends keyof Events>(event: K, handler: Events[K]): void;
|
|
4
|
+
off<K extends keyof Events>(event: K, handler: Events[K]): void;
|
|
5
|
+
once<K extends keyof Events>(event: K, handler: Events[K]): void;
|
|
6
|
+
protected emit<K extends keyof Events>(event: K, ...args: Parameters<Events[K]>): void;
|
|
7
|
+
removeAllListeners(): void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface SynxedConfig {
|
|
11
|
+
apiKey: string;
|
|
12
|
+
serverUrl: string;
|
|
13
|
+
autoConnect?: boolean;
|
|
14
|
+
}
|
|
15
|
+
interface PlaySongOptions {
|
|
16
|
+
catalogTrackId?: string;
|
|
17
|
+
internalTrackId?: string;
|
|
18
|
+
listenerId?: string;
|
|
19
|
+
}
|
|
20
|
+
interface PlayPlaylistOptions {
|
|
21
|
+
playlistCode: string;
|
|
22
|
+
listenerId?: string;
|
|
23
|
+
}
|
|
24
|
+
interface TrackInfo {
|
|
25
|
+
id: string;
|
|
26
|
+
title?: string;
|
|
27
|
+
artist?: string;
|
|
28
|
+
duration?: number;
|
|
29
|
+
albumArt?: string;
|
|
30
|
+
}
|
|
31
|
+
interface PlayerState {
|
|
32
|
+
status: 'idle' | 'loading' | 'playing' | 'paused' | 'error';
|
|
33
|
+
currentTrack?: TrackInfo;
|
|
34
|
+
currentTime: number;
|
|
35
|
+
duration: number;
|
|
36
|
+
volume: number;
|
|
37
|
+
}
|
|
38
|
+
interface SynxedEvents {
|
|
39
|
+
stateChange: (state: PlayerState) => void;
|
|
40
|
+
timeUpdate: (time: {
|
|
41
|
+
currentTime: number;
|
|
42
|
+
duration: number;
|
|
43
|
+
}) => void;
|
|
44
|
+
trackChange: (track: TrackInfo) => void;
|
|
45
|
+
error: (error: Error) => void;
|
|
46
|
+
connected: () => void;
|
|
47
|
+
disconnected: (reason: string) => void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
declare class SynxedPlayer extends EventEmitter<SynxedEvents> {
|
|
51
|
+
private transport;
|
|
52
|
+
private audio;
|
|
53
|
+
private playlist;
|
|
54
|
+
private config;
|
|
55
|
+
private status;
|
|
56
|
+
constructor(config: SynxedConfig);
|
|
57
|
+
private setupListeners;
|
|
58
|
+
private connect;
|
|
59
|
+
playSong(options: PlaySongOptions): Promise<void>;
|
|
60
|
+
playPlaylist(options: PlayPlaylistOptions): Promise<void>;
|
|
61
|
+
pause(): void;
|
|
62
|
+
resume(): void;
|
|
63
|
+
stop(): void;
|
|
64
|
+
skip(): void;
|
|
65
|
+
seek(ms: number): void;
|
|
66
|
+
setVolume(volume: number): void;
|
|
67
|
+
private handleServerMessage;
|
|
68
|
+
private handleTrackEnded;
|
|
69
|
+
private updateStatus;
|
|
70
|
+
destroy(): void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
declare class SynxedError extends Error {
|
|
74
|
+
code?: string | undefined;
|
|
75
|
+
constructor(message: string, code?: string | undefined);
|
|
76
|
+
}
|
|
77
|
+
declare class SynxedConnectionError extends SynxedError {
|
|
78
|
+
constructor(message: string);
|
|
79
|
+
}
|
|
80
|
+
declare class SynxedPlaybackError extends SynxedError {
|
|
81
|
+
constructor(error: any);
|
|
82
|
+
}
|
|
83
|
+
declare class SynxedProtocolError extends SynxedError {
|
|
84
|
+
constructor(message: string);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
declare enum ContentKind {
|
|
88
|
+
UNSPECIFIED = 0,
|
|
89
|
+
SONG = 1,
|
|
90
|
+
PLAYLIST = 2,
|
|
91
|
+
CATEGORY = 3
|
|
92
|
+
}
|
|
93
|
+
declare enum ErrorCode {
|
|
94
|
+
UNSPECIFIED = 0,
|
|
95
|
+
UNAUTHORIZED = 1,
|
|
96
|
+
VALIDATION_ERROR = 2,
|
|
97
|
+
NOT_FOUND = 3,
|
|
98
|
+
PROCESSING = 4,
|
|
99
|
+
SERVICE_UNAVAILABLE = 5,
|
|
100
|
+
BAD_PROTOBUF = 6
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export { ContentKind, ErrorCode, type PlayPlaylistOptions, type PlaySongOptions, type PlayerState, type SynxedConfig, SynxedConnectionError, SynxedError, type SynxedEvents, SynxedPlaybackError, SynxedPlayer, SynxedProtocolError, type TrackInfo };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use strict';var socket_ioClient=require('socket.io-client'),k=require('protobufjs'),howler=require('howler');function _interopNamespace(e){if(e&&e.__esModule)return e;var n=Object.create(null);if(e){Object.keys(e).forEach(function(k){if(k!=='default'){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}})}n.default=e;return Object.freeze(n)}var k__namespace=/*#__PURE__*/_interopNamespace(k);var I=`
|
|
2
|
+
syntax = "proto3";
|
|
3
|
+
|
|
4
|
+
enum ContentKind {
|
|
5
|
+
CONTENT_KIND_UNSPECIFIED = 0;
|
|
6
|
+
CONTENT_KIND_SONG = 1;
|
|
7
|
+
CONTENT_KIND_PLAYLIST = 2;
|
|
8
|
+
CONTENT_KIND_CATEGORY = 3;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
enum ErrorCode {
|
|
12
|
+
ERROR_CODE_UNSPECIFIED = 0;
|
|
13
|
+
UNAUTHORIZED = 1;
|
|
14
|
+
VALIDATION_ERROR = 2;
|
|
15
|
+
NOT_FOUND = 3;
|
|
16
|
+
PROCESSING = 4;
|
|
17
|
+
SERVICE_UNAVAILABLE = 5;
|
|
18
|
+
BAD_PROTOBUF = 6;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
message SdkClientInit {
|
|
22
|
+
ContentKind content_kind = 1;
|
|
23
|
+
string catalog_track_id = 2;
|
|
24
|
+
string internal_track_id = 3;
|
|
25
|
+
string playlist_code = 4;
|
|
26
|
+
string listener_id = 5;
|
|
27
|
+
string device_type = 6;
|
|
28
|
+
string country_code = 7;
|
|
29
|
+
string region = 8;
|
|
30
|
+
string protocol_version = 9;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
message SdkClientEnvelope {
|
|
34
|
+
oneof payload {
|
|
35
|
+
SdkClientInit init = 1;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
message SdkServerInitAck {
|
|
40
|
+
string session_id = 1;
|
|
41
|
+
string playback_url = 2;
|
|
42
|
+
bool is_hls = 3;
|
|
43
|
+
uint32 heartbeat_interval_ms = 4;
|
|
44
|
+
string content_summary = 5;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
message SdkServerError {
|
|
48
|
+
ErrorCode code = 1;
|
|
49
|
+
string message = 2;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
message SdkServerEnvelope {
|
|
53
|
+
oneof payload {
|
|
54
|
+
SdkServerInitAck init_ack = 1;
|
|
55
|
+
SdkServerError error = 2;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
`,f=k__namespace.parse(I).root,v=f.lookupType("SdkClientEnvelope"),y=f.lookupType("SdkServerEnvelope"),g=(e=>(e[e.UNSPECIFIED=0]="UNSPECIFIED",e[e.SONG=1]="SONG",e[e.PLAYLIST=2]="PLAYLIST",e[e.CATEGORY=3]="CATEGORY",e))(g||{}),S=(n=>(n[n.UNSPECIFIED=0]="UNSPECIFIED",n[n.UNAUTHORIZED=1]="UNAUTHORIZED",n[n.VALIDATION_ERROR=2]="VALIDATION_ERROR",n[n.NOT_FOUND=3]="NOT_FOUND",n[n.PROCESSING=4]="PROCESSING",n[n.SERVICE_UNAVAILABLE=5]="SERVICE_UNAVAILABLE",n[n.BAD_PROTOBUF=6]="BAD_PROTOBUF",n))(S||{});var c=class{static encodeClientEnvelope(i){let t=v.create(i);return v.encode(t).finish()}static decodeServerEnvelope(i){let t=y.decode(i);return y.toObject(t,{enums:String,longs:String,bytes:String,defaults:true,oneofs:true})}};var r=class{constructor(){this.listeners=new Map;}on(i,t){this.listeners.has(i)||this.listeners.set(i,new Set),this.listeners.get(i).add(t);}off(i,t){let s=this.listeners.get(i);s&&s.delete(t);}once(i,t){let s=((...e)=>{t(...e),this.off(i,s);});this.on(i,s);}emit(i,...t){let s=this.listeners.get(i);s&&s.forEach(e=>e(...t));}removeAllListeners(){this.listeners.clear();}};var l=class extends r{constructor(){super();this.socket=null;this.apiKey=null;this.serverUrl=null;}connect(t,s){this.socket?.connected||(this.apiKey=t,this.serverUrl=s,this.socket=socket_ioClient.io(`${s}/sdk`,{auth:{apiKey:t},transports:["websocket"]}),this.socket.on("connect",()=>{this.emit("connected");}),this.socket.on("disconnect",e=>{this.emit("disconnected",e);}),this.socket.on("connect_error",e=>{this.emit("error",e);}),this.socket.on("d",e=>{try{let d=e instanceof Uint8Array?e:new Uint8Array(e),T=c.decodeServerEnvelope(d);this.emit("message",T);}catch(d){this.emit("error",new Error(`Failed to decode message: ${d}`));}}));}disconnect(){this.socket&&(this.socket.disconnect(),this.socket=null);}sendInit(t){if(!this.socket?.connected)throw new Error("Socket not connected");let s={init:t},e=c.encodeClientEnvelope(s);this.socket.emit("d",e);}get isConnected(){return this.socket?.connected||false}};var u=class extends r{constructor(){super();this.howl=null;this.updateTimer=null;}load(t,s){this.stop(),this.emit("loading"),this.howl=new howler.Howl({src:[t],html5:true,format:s?["m3u8"]:void 0,onload:()=>{this.emit("loaded");},onloaderror:(e,d)=>{this.emit("error",d);},onplay:()=>{this.emit("playing"),this.startTimeUpdateLoop();},onpause:()=>{this.emit("paused"),this.stopTimeUpdateLoop();},onstop:()=>{this.emit("stopped"),this.stopTimeUpdateLoop();},onend:()=>{this.emit("ended"),this.stopTimeUpdateLoop();}});}play(){this.howl?.play();}pause(){this.howl?.pause();}stop(){this.howl?.stop(),this.howl?.unload(),this.howl=null;}seek(t){this.howl?.seek(t/1e3);}setVolume(t){this.howl?.volume(t);}get duration(){return (this.howl?.duration()||0)*1e3}get currentTime(){return (this.howl?.seek()||0)*1e3}startTimeUpdateLoop(){this.stopTimeUpdateLoop();let t=()=>{this.howl?.playing()&&(this.emit("timeupdate",{currentTime:this.currentTime,duration:this.duration}),this.updateTimer=requestAnimationFrame(t));};this.updateTimer=requestAnimationFrame(t);}stopTimeUpdateLoop(){this.updateTimer!==null&&(cancelAnimationFrame(this.updateTimer),this.updateTimer=null);}};var p=class extends r{constructor(){super();this.queue=[];this.currentIndex=-1;}setQueue(t){this.queue=t,this.currentIndex=t.length>0?0:-1,this.emit("queueUpdated",this.queue);}getCurrentTrack(){return this.currentIndex>=0&&this.currentIndex<this.queue.length?this.queue[this.currentIndex]:null}next(){if(this.currentIndex<this.queue.length-1){this.currentIndex++;let t=this.getCurrentTrack();return this.emit("trackChanged",t),t}return null}previous(){if(this.currentIndex>0){this.currentIndex--;let t=this.getCurrentTrack();return this.emit("trackChanged",t),t}return null}skipTo(t){if(t>=0&&t<this.queue.length){this.currentIndex=t;let s=this.getCurrentTrack();return this.emit("trackChanged",s),s}return null}get hasNext(){return this.currentIndex<this.queue.length-1}get hasPrevious(){return this.currentIndex>0}};var a=class extends Error{constructor(t,s){super(t);this.code=s;this.name="SynxedError";}},h=class extends a{constructor(i){super(i),this.name="SynxedConnectionError";}},m=class extends a{constructor(i){super(typeof i=="string"?i:"Playback failed"),this.name="SynxedPlaybackError";}},E=class extends a{constructor(i){super(i),this.name="SynxedProtocolError";}};var x=class extends r{constructor(t){super();this.status="idle";this.config=t,this.transport=new l,this.audio=new u,this.playlist=new p,this.setupListeners(),t.autoConnect&&this.connect();}setupListeners(){this.transport.on("connected",()=>this.emit("connected")),this.transport.on("disconnected",t=>this.emit("disconnected",t)),this.transport.on("error",t=>this.emit("error",new h(t.message))),this.transport.on("message",t=>this.handleServerMessage(t)),this.audio.on("playing",()=>this.updateStatus("playing")),this.audio.on("paused",()=>this.updateStatus("paused")),this.audio.on("stopped",()=>this.updateStatus("idle")),this.audio.on("loading",()=>this.updateStatus("loading")),this.audio.on("error",t=>this.emit("error",new m(t))),this.audio.on("timeupdate",t=>this.emit("timeUpdate",t)),this.audio.on("ended",()=>this.handleTrackEnded());}connect(){this.transport.connect(this.config.apiKey,this.config.serverUrl);}async playSong(t){this.transport.isConnected||this.connect(),this.transport.sendInit({content_kind:1,catalog_track_id:t.catalogTrackId,internal_track_id:t.internalTrackId,listener_id:t.listenerId});}async playPlaylist(t){this.transport.isConnected||this.connect(),this.transport.sendInit({content_kind:2,playlist_code:t.playlistCode,listener_id:t.listenerId});}pause(){this.audio.pause();}resume(){this.audio.play();}stop(){this.audio.stop();}skip(){let t=this.playlist.next();t&&this.playSong({catalogTrackId:t.id});}seek(t){this.audio.seek(t);}setVolume(t){this.audio.setVolume(t);}handleServerMessage(t){if(t.init_ack){let s=t.init_ack;if(this.audio.load(s.playback_url,s.is_hls),this.audio.play(),s.content_summary)try{let e=JSON.parse(s.content_summary);e.tracks&&this.playlist.setQueue(e.tracks);}catch{}}else t.error&&this.emit("error",new a(t.error.message,t.error.code));}handleTrackEnded(){this.playlist.hasNext?this.skip():this.updateStatus("idle");}updateStatus(t){this.status=t,this.emit("stateChange",{status:t,currentTime:this.audio.currentTime,duration:this.audio.duration,volume:1});}destroy(){this.audio.stop(),this.transport.disconnect(),this.removeAllListeners();}};exports.ContentKind=g;exports.ErrorCode=S;exports.SynxedConnectionError=h;exports.SynxedError=a;exports.SynxedPlaybackError=m;exports.SynxedPlayer=x;exports.SynxedProtocolError=E;//# sourceMappingURL=index.js.map
|
|
59
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/proto/sdk-streaming.ts","../src/transport/ProtocolCodec.ts","../src/core/EventEmitter.ts","../src/transport/TransportManager.ts","../src/audio/AudioEngine.ts","../src/playlist/PlaylistManager.ts","../src/core/errors.ts","../src/core/SynxedPlayer.ts"],"names":["schema","root","k","SdkClientEnvelope","SdkServerEnvelope","ContentKind","ErrorCode","ProtocolCodec","payload","message","data","decoded","EventEmitter","event","handler","set","onceHandler","args","TransportManager","apiKey","serverUrl","io","reason","error","uint8Array","err","params","bytes","AudioEngine","url","isHls","Howl","id","ms","volume","update","PlaylistManager","tracks","track","index","SynxedError","code","SynxedConnectionError","SynxedPlaybackError","SynxedProtocolError","SynxedPlayer","config","time","options","nextTrack","ack","summary","status"],"mappings":"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","file":"index.js","sourcesContent":["import * as protobuf from 'protobufjs';\n\n/**\n * Protobuf schema definition for the SDK.\n * Matches the backend contract in src/modules/streaming/sdk-streaming.proto\n */\nconst schema = `\nsyntax = \"proto3\";\n\nenum ContentKind {\n CONTENT_KIND_UNSPECIFIED = 0;\n CONTENT_KIND_SONG = 1;\n CONTENT_KIND_PLAYLIST = 2;\n CONTENT_KIND_CATEGORY = 3;\n}\n\nenum ErrorCode {\n ERROR_CODE_UNSPECIFIED = 0;\n UNAUTHORIZED = 1;\n VALIDATION_ERROR = 2;\n NOT_FOUND = 3;\n PROCESSING = 4;\n SERVICE_UNAVAILABLE = 5;\n BAD_PROTOBUF = 6;\n}\n\nmessage SdkClientInit {\n ContentKind content_kind = 1;\n string catalog_track_id = 2;\n string internal_track_id = 3;\n string playlist_code = 4;\n string listener_id = 5;\n string device_type = 6;\n string country_code = 7;\n string region = 8;\n string protocol_version = 9;\n}\n\nmessage SdkClientEnvelope {\n oneof payload {\n SdkClientInit init = 1;\n }\n}\n\nmessage SdkServerInitAck {\n string session_id = 1;\n string playback_url = 2;\n bool is_hls = 3;\n uint32 heartbeat_interval_ms = 4;\n string content_summary = 5;\n}\n\nmessage SdkServerError {\n ErrorCode code = 1;\n string message = 2;\n}\n\nmessage SdkServerEnvelope {\n oneof payload {\n SdkServerInitAck init_ack = 1;\n SdkServerError error = 2;\n }\n}\n`;\n\nconst root = protobuf.parse(schema).root;\n\nexport const SdkClientEnvelope = root.lookupType('SdkClientEnvelope');\nexport const SdkServerEnvelope = root.lookupType('SdkServerEnvelope');\n\nexport enum ContentKind {\n UNSPECIFIED = 0,\n SONG = 1,\n PLAYLIST = 2,\n CATEGORY = 3,\n}\n\nexport enum ErrorCode {\n UNSPECIFIED = 0,\n UNAUTHORIZED = 1,\n VALIDATION_ERROR = 2,\n NOT_FOUND = 3,\n PROCESSING = 4,\n SERVICE_UNAVAILABLE = 5,\n BAD_PROTOBUF = 6,\n}\n","import { SdkClientEnvelope, SdkServerEnvelope } from '../proto/sdk-streaming';\n\nexport class ProtocolCodec {\n /**\n * Encodes a client envelope into a binary payload.\n */\n static encodeClientEnvelope(payload: any): Uint8Array {\n const message = SdkClientEnvelope.create(payload);\n return SdkClientEnvelope.encode(message).finish();\n }\n\n /**\n * Decodes a binary payload into a server envelope.\n */\n static decodeServerEnvelope(data: Uint8Array): any {\n const decoded = SdkServerEnvelope.decode(data);\n return SdkServerEnvelope.toObject(decoded, {\n enums: String,\n longs: String,\n bytes: String,\n defaults: true,\n oneofs: true,\n });\n }\n}\n","type Handler = (...args: any[]) => void;\n\nexport class EventEmitter<Events extends Record<string, any>> {\n private listeners: Map<keyof Events, Set<Handler>> = new Map();\n\n on<K extends keyof Events>(event: K, handler: Events[K]): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(handler);\n }\n\n off<K extends keyof Events>(event: K, handler: Events[K]): void {\n const set = this.listeners.get(event);\n if (set) {\n set.delete(handler);\n }\n }\n\n once<K extends keyof Events>(event: K, handler: Events[K]): void {\n const onceHandler = ((...args: any[]) => {\n handler(...args);\n this.off(event, onceHandler as Events[K]);\n }) as Events[K];\n this.on(event, onceHandler);\n }\n\n protected emit<K extends keyof Events>(event: K, ...args: Parameters<Events[K]>): void {\n const set = this.listeners.get(event);\n if (set) {\n set.forEach((handler) => handler(...args));\n }\n }\n\n removeAllListeners(): void {\n this.listeners.clear();\n }\n}\n","import { io, Socket } from 'socket.io-client';\nimport { ProtocolCodec } from './ProtocolCodec';\nimport { EventEmitter } from '../core/EventEmitter';\n\nexport interface TransportEvents {\n connected: () => void;\n disconnected: (reason: string) => void;\n error: (error: any) => void;\n message: (payload: any) => void;\n}\n\nexport class TransportManager extends EventEmitter<TransportEvents> {\n private socket: Socket | null = null;\n private apiKey: string | null = null;\n private serverUrl: string | null = null;\n\n constructor() {\n super();\n }\n\n /**\n * Connects to the Synxed SDK namespace.\n */\n connect(apiKey: string, serverUrl: string): void {\n if (this.socket?.connected) return;\n\n this.apiKey = apiKey;\n this.serverUrl = serverUrl;\n\n this.socket = io(`${serverUrl}/sdk`, {\n auth: { apiKey },\n transports: ['websocket'],\n });\n\n this.socket.on('connect', () => {\n this.emit('connected');\n });\n\n this.socket.on('disconnect', (reason) => {\n this.emit('disconnected', reason);\n });\n\n this.socket.on('connect_error', (error) => {\n this.emit('error', error);\n });\n\n this.socket.on('d', (data: ArrayBuffer | Uint8Array) => {\n try {\n const uint8Array = data instanceof Uint8Array ? data : new Uint8Array(data);\n const payload = ProtocolCodec.decodeServerEnvelope(uint8Array);\n this.emit('message', payload);\n } catch (err) {\n this.emit('error', new Error(`Failed to decode message: ${err}`));\n }\n });\n }\n\n /**\n * Disconnects from the server.\n */\n disconnect(): void {\n if (this.socket) {\n this.socket.disconnect();\n this.socket = null;\n }\n }\n\n /**\n * Sends an init message to the backend.\n */\n sendInit(params: any): void {\n if (!this.socket?.connected) {\n throw new Error('Socket not connected');\n }\n\n const payload = { init: params };\n const bytes = ProtocolCodec.encodeClientEnvelope(payload);\n this.socket.emit('d', bytes);\n }\n\n /**\n * Checks if the socket is connected.\n */\n get isConnected(): boolean {\n return this.socket?.connected || false;\n }\n}\n","import { Howl } from 'howler';\nimport { EventEmitter } from '../core/EventEmitter';\n\nexport interface AudioEvents {\n playing: () => void;\n paused: () => void;\n stopped: () => void;\n ended: () => void;\n loading: () => void;\n loaded: () => void;\n error: (error: any) => void;\n timeupdate: (data: { currentTime: number; duration: number }) => void;\n}\n\nexport class AudioEngine extends EventEmitter<AudioEvents> {\n private howl: Howl | null = null;\n private updateTimer: number | null = null;\n\n constructor() {\n super();\n }\n\n /**\n * Loads a track URL.\n */\n load(url: string, isHls: boolean): void {\n this.stop();\n this.emit('loading');\n\n // For HLS, we'd ideally use hls.js. \n // Howler doesn't natively support HLS streams well without custom logic.\n // For now, we'll use HTML5 audio mode which works for some HLS streams natively.\n this.howl = new Howl({\n src: [url],\n html5: true, // Required for streaming large files\n format: isHls ? ['m3u8'] : undefined,\n onload: () => {\n this.emit('loaded');\n },\n onloaderror: (id, err) => {\n this.emit('error', err);\n },\n onplay: () => {\n this.emit('playing');\n this.startTimeUpdateLoop();\n },\n onpause: () => {\n this.emit('paused');\n this.stopTimeUpdateLoop();\n },\n onstop: () => {\n this.emit('stopped');\n this.stopTimeUpdateLoop();\n },\n onend: () => {\n this.emit('ended');\n this.stopTimeUpdateLoop();\n },\n });\n }\n\n play(): void {\n this.howl?.play();\n }\n\n pause(): void {\n this.howl?.pause();\n }\n\n stop(): void {\n this.howl?.stop();\n this.howl?.unload();\n this.howl = null;\n }\n\n seek(ms: number): void {\n this.howl?.seek(ms / 1000);\n }\n\n setVolume(volume: number): void {\n this.howl?.volume(volume);\n }\n\n get duration(): number {\n return (this.howl?.duration() || 0) * 1000;\n }\n\n get currentTime(): number {\n return (this.howl?.seek() as number || 0) * 1000;\n }\n\n private startTimeUpdateLoop(): void {\n this.stopTimeUpdateLoop();\n const update = () => {\n if (this.howl?.playing()) {\n this.emit('timeupdate', {\n currentTime: this.currentTime,\n duration: this.duration,\n });\n this.updateTimer = requestAnimationFrame(update);\n }\n };\n this.updateTimer = requestAnimationFrame(update);\n }\n\n private stopTimeUpdateLoop(): void {\n if (this.updateTimer !== null) {\n cancelAnimationFrame(this.updateTimer);\n this.updateTimer = null;\n }\n }\n}\n","import { EventEmitter } from '../core/EventEmitter';\n\nexport interface PlaylistEvents {\n trackChanged: (track: any) => void;\n queueUpdated: (tracks: any[]) => void;\n}\n\nexport class PlaylistManager extends EventEmitter<PlaylistEvents> {\n private queue: any[] = [];\n private currentIndex: number = -1;\n\n constructor() {\n super();\n }\n\n setQueue(tracks: any[]): void {\n this.queue = tracks;\n this.currentIndex = tracks.length > 0 ? 0 : -1;\n this.emit('queueUpdated', this.queue);\n }\n\n getCurrentTrack(): any | null {\n if (this.currentIndex >= 0 && this.currentIndex < this.queue.length) {\n return this.queue[this.currentIndex];\n }\n return null;\n }\n\n next(): any | null {\n if (this.currentIndex < this.queue.length - 1) {\n this.currentIndex++;\n const track = this.getCurrentTrack();\n this.emit('trackChanged', track);\n return track;\n }\n return null;\n }\n\n previous(): any | null {\n if (this.currentIndex > 0) {\n this.currentIndex--;\n const track = this.getCurrentTrack();\n this.emit('trackChanged', track);\n return track;\n }\n return null;\n }\n\n skipTo(index: number): any | null {\n if (index >= 0 && index < this.queue.length) {\n this.currentIndex = index;\n const track = this.getCurrentTrack();\n this.emit('trackChanged', track);\n return track;\n }\n return null;\n }\n\n get hasNext(): boolean {\n return this.currentIndex < this.queue.length - 1;\n }\n\n get hasPrevious(): boolean {\n return this.currentIndex > 0;\n }\n}\n","export class SynxedError extends Error {\n constructor(message: string, public code?: string) {\n super(message);\n this.name = 'SynxedError';\n }\n}\n\nexport class SynxedConnectionError extends SynxedError {\n constructor(message: string) {\n super(message);\n this.name = 'SynxedConnectionError';\n }\n}\n\nexport class SynxedPlaybackError extends SynxedError {\n constructor(error: any) {\n super(typeof error === 'string' ? error : 'Playback failed');\n this.name = 'SynxedPlaybackError';\n }\n}\n\nexport class SynxedProtocolError extends SynxedError {\n constructor(message: string) {\n super(message);\n this.name = 'SynxedProtocolError';\n }\n}\n","import { TransportManager } from '../transport/TransportManager';\nimport { AudioEngine } from '../audio/AudioEngine';\nimport { PlaylistManager } from '../playlist/PlaylistManager';\nimport { EventEmitter } from './EventEmitter';\nimport { ContentKind } from '../proto/sdk-streaming';\nimport { SynxedConfig, PlayerState, PlaySongOptions, PlayPlaylistOptions, SynxedEvents } from '../types';\nimport { SynxedError, SynxedPlaybackError, SynxedConnectionError } from './errors';\n\nexport class SynxedPlayer extends EventEmitter<SynxedEvents> {\n private transport: TransportManager;\n private audio: AudioEngine;\n private playlist: PlaylistManager;\n private config: SynxedConfig;\n private status: PlayerState['status'] = 'idle';\n\n constructor(config: SynxedConfig) {\n super();\n this.config = config;\n this.transport = new TransportManager();\n this.audio = new AudioEngine();\n this.playlist = new PlaylistManager();\n\n this.setupListeners();\n\n if (config.autoConnect) {\n this.connect();\n }\n }\n\n private setupListeners(): void {\n // Transport Listeners\n this.transport.on('connected', () => this.emit('connected'));\n this.transport.on('disconnected', (reason) => this.emit('disconnected', reason));\n this.transport.on('error', (error) => this.emit('error', new SynxedConnectionError(error.message)));\n this.transport.on('message', (payload) => this.handleServerMessage(payload));\n\n // Audio Listeners\n this.audio.on('playing', () => this.updateStatus('playing'));\n this.audio.on('paused', () => this.updateStatus('paused'));\n this.audio.on('stopped', () => this.updateStatus('idle'));\n this.audio.on('loading', () => this.updateStatus('loading'));\n this.audio.on('error', (error) => this.emit('error', new SynxedPlaybackError(error)));\n this.audio.on('timeupdate', (time) => this.emit('timeUpdate', time));\n this.audio.on('ended', () => this.handleTrackEnded());\n }\n\n private connect(): void {\n this.transport.connect(this.config.apiKey, this.config.serverUrl);\n }\n\n async playSong(options: PlaySongOptions): Promise<void> {\n if (!this.transport.isConnected) this.connect();\n \n this.transport.sendInit({\n content_kind: ContentKind.SONG,\n catalog_track_id: options.catalogTrackId,\n internal_track_id: options.internalTrackId,\n listener_id: options.listenerId,\n });\n }\n\n async playPlaylist(options: PlayPlaylistOptions): Promise<void> {\n if (!this.transport.isConnected) this.connect();\n\n this.transport.sendInit({\n content_kind: ContentKind.PLAYLIST,\n playlist_code: options.playlistCode,\n listener_id: options.listenerId,\n });\n }\n\n pause(): void {\n this.audio.pause();\n }\n\n resume(): void {\n this.audio.play();\n }\n\n stop(): void {\n this.audio.stop();\n }\n\n skip(): void {\n const nextTrack = this.playlist.next();\n if (nextTrack) {\n // In current backend, we need to re-init for each track\n // This logic will be refined once backend handles queues\n this.playSong({ catalogTrackId: nextTrack.id }); \n }\n }\n\n seek(ms: number): void {\n this.audio.seek(ms);\n }\n\n setVolume(volume: number): void {\n this.audio.setVolume(volume);\n }\n\n private handleServerMessage(payload: any): void {\n if (payload.init_ack) {\n const ack = payload.init_ack;\n this.audio.load(ack.playback_url, ack.is_hls);\n this.audio.play();\n \n // Parse content_summary if it's a playlist\n if (ack.content_summary) {\n try {\n const summary = JSON.parse(ack.content_summary);\n if (summary.tracks) {\n this.playlist.setQueue(summary.tracks);\n }\n } catch (e) {\n // Ignore parse errors for now\n }\n }\n } else if (payload.error) {\n this.emit('error', new SynxedError(payload.error.message, payload.error.code));\n }\n }\n\n private handleTrackEnded(): void {\n if (this.playlist.hasNext) {\n this.skip();\n } else {\n this.updateStatus('idle');\n }\n }\n\n private updateStatus(status: PlayerState['status']): void {\n this.status = status;\n this.emit('stateChange', {\n status,\n currentTime: this.audio.currentTime,\n duration: this.audio.duration,\n volume: 1, // Store volume locally if needed\n });\n }\n\n destroy(): void {\n this.audio.stop();\n this.transport.disconnect();\n this.removeAllListeners();\n }\n}\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {io}from'socket.io-client';import*as k from'protobufjs';import {Howl}from'howler';var I=`
|
|
2
|
+
syntax = "proto3";
|
|
3
|
+
|
|
4
|
+
enum ContentKind {
|
|
5
|
+
CONTENT_KIND_UNSPECIFIED = 0;
|
|
6
|
+
CONTENT_KIND_SONG = 1;
|
|
7
|
+
CONTENT_KIND_PLAYLIST = 2;
|
|
8
|
+
CONTENT_KIND_CATEGORY = 3;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
enum ErrorCode {
|
|
12
|
+
ERROR_CODE_UNSPECIFIED = 0;
|
|
13
|
+
UNAUTHORIZED = 1;
|
|
14
|
+
VALIDATION_ERROR = 2;
|
|
15
|
+
NOT_FOUND = 3;
|
|
16
|
+
PROCESSING = 4;
|
|
17
|
+
SERVICE_UNAVAILABLE = 5;
|
|
18
|
+
BAD_PROTOBUF = 6;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
message SdkClientInit {
|
|
22
|
+
ContentKind content_kind = 1;
|
|
23
|
+
string catalog_track_id = 2;
|
|
24
|
+
string internal_track_id = 3;
|
|
25
|
+
string playlist_code = 4;
|
|
26
|
+
string listener_id = 5;
|
|
27
|
+
string device_type = 6;
|
|
28
|
+
string country_code = 7;
|
|
29
|
+
string region = 8;
|
|
30
|
+
string protocol_version = 9;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
message SdkClientEnvelope {
|
|
34
|
+
oneof payload {
|
|
35
|
+
SdkClientInit init = 1;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
message SdkServerInitAck {
|
|
40
|
+
string session_id = 1;
|
|
41
|
+
string playback_url = 2;
|
|
42
|
+
bool is_hls = 3;
|
|
43
|
+
uint32 heartbeat_interval_ms = 4;
|
|
44
|
+
string content_summary = 5;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
message SdkServerError {
|
|
48
|
+
ErrorCode code = 1;
|
|
49
|
+
string message = 2;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
message SdkServerEnvelope {
|
|
53
|
+
oneof payload {
|
|
54
|
+
SdkServerInitAck init_ack = 1;
|
|
55
|
+
SdkServerError error = 2;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
`,f=k.parse(I).root,v=f.lookupType("SdkClientEnvelope"),y=f.lookupType("SdkServerEnvelope"),g=(e=>(e[e.UNSPECIFIED=0]="UNSPECIFIED",e[e.SONG=1]="SONG",e[e.PLAYLIST=2]="PLAYLIST",e[e.CATEGORY=3]="CATEGORY",e))(g||{}),S=(n=>(n[n.UNSPECIFIED=0]="UNSPECIFIED",n[n.UNAUTHORIZED=1]="UNAUTHORIZED",n[n.VALIDATION_ERROR=2]="VALIDATION_ERROR",n[n.NOT_FOUND=3]="NOT_FOUND",n[n.PROCESSING=4]="PROCESSING",n[n.SERVICE_UNAVAILABLE=5]="SERVICE_UNAVAILABLE",n[n.BAD_PROTOBUF=6]="BAD_PROTOBUF",n))(S||{});var c=class{static encodeClientEnvelope(i){let t=v.create(i);return v.encode(t).finish()}static decodeServerEnvelope(i){let t=y.decode(i);return y.toObject(t,{enums:String,longs:String,bytes:String,defaults:true,oneofs:true})}};var r=class{constructor(){this.listeners=new Map;}on(i,t){this.listeners.has(i)||this.listeners.set(i,new Set),this.listeners.get(i).add(t);}off(i,t){let s=this.listeners.get(i);s&&s.delete(t);}once(i,t){let s=((...e)=>{t(...e),this.off(i,s);});this.on(i,s);}emit(i,...t){let s=this.listeners.get(i);s&&s.forEach(e=>e(...t));}removeAllListeners(){this.listeners.clear();}};var l=class extends r{constructor(){super();this.socket=null;this.apiKey=null;this.serverUrl=null;}connect(t,s){this.socket?.connected||(this.apiKey=t,this.serverUrl=s,this.socket=io(`${s}/sdk`,{auth:{apiKey:t},transports:["websocket"]}),this.socket.on("connect",()=>{this.emit("connected");}),this.socket.on("disconnect",e=>{this.emit("disconnected",e);}),this.socket.on("connect_error",e=>{this.emit("error",e);}),this.socket.on("d",e=>{try{let d=e instanceof Uint8Array?e:new Uint8Array(e),T=c.decodeServerEnvelope(d);this.emit("message",T);}catch(d){this.emit("error",new Error(`Failed to decode message: ${d}`));}}));}disconnect(){this.socket&&(this.socket.disconnect(),this.socket=null);}sendInit(t){if(!this.socket?.connected)throw new Error("Socket not connected");let s={init:t},e=c.encodeClientEnvelope(s);this.socket.emit("d",e);}get isConnected(){return this.socket?.connected||false}};var u=class extends r{constructor(){super();this.howl=null;this.updateTimer=null;}load(t,s){this.stop(),this.emit("loading"),this.howl=new Howl({src:[t],html5:true,format:s?["m3u8"]:void 0,onload:()=>{this.emit("loaded");},onloaderror:(e,d)=>{this.emit("error",d);},onplay:()=>{this.emit("playing"),this.startTimeUpdateLoop();},onpause:()=>{this.emit("paused"),this.stopTimeUpdateLoop();},onstop:()=>{this.emit("stopped"),this.stopTimeUpdateLoop();},onend:()=>{this.emit("ended"),this.stopTimeUpdateLoop();}});}play(){this.howl?.play();}pause(){this.howl?.pause();}stop(){this.howl?.stop(),this.howl?.unload(),this.howl=null;}seek(t){this.howl?.seek(t/1e3);}setVolume(t){this.howl?.volume(t);}get duration(){return (this.howl?.duration()||0)*1e3}get currentTime(){return (this.howl?.seek()||0)*1e3}startTimeUpdateLoop(){this.stopTimeUpdateLoop();let t=()=>{this.howl?.playing()&&(this.emit("timeupdate",{currentTime:this.currentTime,duration:this.duration}),this.updateTimer=requestAnimationFrame(t));};this.updateTimer=requestAnimationFrame(t);}stopTimeUpdateLoop(){this.updateTimer!==null&&(cancelAnimationFrame(this.updateTimer),this.updateTimer=null);}};var p=class extends r{constructor(){super();this.queue=[];this.currentIndex=-1;}setQueue(t){this.queue=t,this.currentIndex=t.length>0?0:-1,this.emit("queueUpdated",this.queue);}getCurrentTrack(){return this.currentIndex>=0&&this.currentIndex<this.queue.length?this.queue[this.currentIndex]:null}next(){if(this.currentIndex<this.queue.length-1){this.currentIndex++;let t=this.getCurrentTrack();return this.emit("trackChanged",t),t}return null}previous(){if(this.currentIndex>0){this.currentIndex--;let t=this.getCurrentTrack();return this.emit("trackChanged",t),t}return null}skipTo(t){if(t>=0&&t<this.queue.length){this.currentIndex=t;let s=this.getCurrentTrack();return this.emit("trackChanged",s),s}return null}get hasNext(){return this.currentIndex<this.queue.length-1}get hasPrevious(){return this.currentIndex>0}};var a=class extends Error{constructor(t,s){super(t);this.code=s;this.name="SynxedError";}},h=class extends a{constructor(i){super(i),this.name="SynxedConnectionError";}},m=class extends a{constructor(i){super(typeof i=="string"?i:"Playback failed"),this.name="SynxedPlaybackError";}},E=class extends a{constructor(i){super(i),this.name="SynxedProtocolError";}};var x=class extends r{constructor(t){super();this.status="idle";this.config=t,this.transport=new l,this.audio=new u,this.playlist=new p,this.setupListeners(),t.autoConnect&&this.connect();}setupListeners(){this.transport.on("connected",()=>this.emit("connected")),this.transport.on("disconnected",t=>this.emit("disconnected",t)),this.transport.on("error",t=>this.emit("error",new h(t.message))),this.transport.on("message",t=>this.handleServerMessage(t)),this.audio.on("playing",()=>this.updateStatus("playing")),this.audio.on("paused",()=>this.updateStatus("paused")),this.audio.on("stopped",()=>this.updateStatus("idle")),this.audio.on("loading",()=>this.updateStatus("loading")),this.audio.on("error",t=>this.emit("error",new m(t))),this.audio.on("timeupdate",t=>this.emit("timeUpdate",t)),this.audio.on("ended",()=>this.handleTrackEnded());}connect(){this.transport.connect(this.config.apiKey,this.config.serverUrl);}async playSong(t){this.transport.isConnected||this.connect(),this.transport.sendInit({content_kind:1,catalog_track_id:t.catalogTrackId,internal_track_id:t.internalTrackId,listener_id:t.listenerId});}async playPlaylist(t){this.transport.isConnected||this.connect(),this.transport.sendInit({content_kind:2,playlist_code:t.playlistCode,listener_id:t.listenerId});}pause(){this.audio.pause();}resume(){this.audio.play();}stop(){this.audio.stop();}skip(){let t=this.playlist.next();t&&this.playSong({catalogTrackId:t.id});}seek(t){this.audio.seek(t);}setVolume(t){this.audio.setVolume(t);}handleServerMessage(t){if(t.init_ack){let s=t.init_ack;if(this.audio.load(s.playback_url,s.is_hls),this.audio.play(),s.content_summary)try{let e=JSON.parse(s.content_summary);e.tracks&&this.playlist.setQueue(e.tracks);}catch{}}else t.error&&this.emit("error",new a(t.error.message,t.error.code));}handleTrackEnded(){this.playlist.hasNext?this.skip():this.updateStatus("idle");}updateStatus(t){this.status=t,this.emit("stateChange",{status:t,currentTime:this.audio.currentTime,duration:this.audio.duration,volume:1});}destroy(){this.audio.stop(),this.transport.disconnect(),this.removeAllListeners();}};export{g as ContentKind,S as ErrorCode,h as SynxedConnectionError,a as SynxedError,m as SynxedPlaybackError,x as SynxedPlayer,E as SynxedProtocolError};//# sourceMappingURL=index.mjs.map
|
|
59
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/proto/sdk-streaming.ts","../src/transport/ProtocolCodec.ts","../src/core/EventEmitter.ts","../src/transport/TransportManager.ts","../src/audio/AudioEngine.ts","../src/playlist/PlaylistManager.ts","../src/core/errors.ts","../src/core/SynxedPlayer.ts"],"names":["schema","root","SdkClientEnvelope","SdkServerEnvelope","ContentKind","ErrorCode","ProtocolCodec","payload","message","data","decoded","EventEmitter","event","handler","set","onceHandler","args","TransportManager","apiKey","serverUrl","io","reason","error","uint8Array","err","params","bytes","AudioEngine","url","isHls","Howl","id","ms","volume","update","PlaylistManager","tracks","track","index","SynxedError","code","SynxedConnectionError","SynxedPlaybackError","SynxedProtocolError","SynxedPlayer","config","time","options","nextTrack","ack","summary","status"],"mappings":"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","file":"index.mjs","sourcesContent":["import * as protobuf from 'protobufjs';\n\n/**\n * Protobuf schema definition for the SDK.\n * Matches the backend contract in src/modules/streaming/sdk-streaming.proto\n */\nconst schema = `\nsyntax = \"proto3\";\n\nenum ContentKind {\n CONTENT_KIND_UNSPECIFIED = 0;\n CONTENT_KIND_SONG = 1;\n CONTENT_KIND_PLAYLIST = 2;\n CONTENT_KIND_CATEGORY = 3;\n}\n\nenum ErrorCode {\n ERROR_CODE_UNSPECIFIED = 0;\n UNAUTHORIZED = 1;\n VALIDATION_ERROR = 2;\n NOT_FOUND = 3;\n PROCESSING = 4;\n SERVICE_UNAVAILABLE = 5;\n BAD_PROTOBUF = 6;\n}\n\nmessage SdkClientInit {\n ContentKind content_kind = 1;\n string catalog_track_id = 2;\n string internal_track_id = 3;\n string playlist_code = 4;\n string listener_id = 5;\n string device_type = 6;\n string country_code = 7;\n string region = 8;\n string protocol_version = 9;\n}\n\nmessage SdkClientEnvelope {\n oneof payload {\n SdkClientInit init = 1;\n }\n}\n\nmessage SdkServerInitAck {\n string session_id = 1;\n string playback_url = 2;\n bool is_hls = 3;\n uint32 heartbeat_interval_ms = 4;\n string content_summary = 5;\n}\n\nmessage SdkServerError {\n ErrorCode code = 1;\n string message = 2;\n}\n\nmessage SdkServerEnvelope {\n oneof payload {\n SdkServerInitAck init_ack = 1;\n SdkServerError error = 2;\n }\n}\n`;\n\nconst root = protobuf.parse(schema).root;\n\nexport const SdkClientEnvelope = root.lookupType('SdkClientEnvelope');\nexport const SdkServerEnvelope = root.lookupType('SdkServerEnvelope');\n\nexport enum ContentKind {\n UNSPECIFIED = 0,\n SONG = 1,\n PLAYLIST = 2,\n CATEGORY = 3,\n}\n\nexport enum ErrorCode {\n UNSPECIFIED = 0,\n UNAUTHORIZED = 1,\n VALIDATION_ERROR = 2,\n NOT_FOUND = 3,\n PROCESSING = 4,\n SERVICE_UNAVAILABLE = 5,\n BAD_PROTOBUF = 6,\n}\n","import { SdkClientEnvelope, SdkServerEnvelope } from '../proto/sdk-streaming';\n\nexport class ProtocolCodec {\n /**\n * Encodes a client envelope into a binary payload.\n */\n static encodeClientEnvelope(payload: any): Uint8Array {\n const message = SdkClientEnvelope.create(payload);\n return SdkClientEnvelope.encode(message).finish();\n }\n\n /**\n * Decodes a binary payload into a server envelope.\n */\n static decodeServerEnvelope(data: Uint8Array): any {\n const decoded = SdkServerEnvelope.decode(data);\n return SdkServerEnvelope.toObject(decoded, {\n enums: String,\n longs: String,\n bytes: String,\n defaults: true,\n oneofs: true,\n });\n }\n}\n","type Handler = (...args: any[]) => void;\n\nexport class EventEmitter<Events extends Record<string, any>> {\n private listeners: Map<keyof Events, Set<Handler>> = new Map();\n\n on<K extends keyof Events>(event: K, handler: Events[K]): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(handler);\n }\n\n off<K extends keyof Events>(event: K, handler: Events[K]): void {\n const set = this.listeners.get(event);\n if (set) {\n set.delete(handler);\n }\n }\n\n once<K extends keyof Events>(event: K, handler: Events[K]): void {\n const onceHandler = ((...args: any[]) => {\n handler(...args);\n this.off(event, onceHandler as Events[K]);\n }) as Events[K];\n this.on(event, onceHandler);\n }\n\n protected emit<K extends keyof Events>(event: K, ...args: Parameters<Events[K]>): void {\n const set = this.listeners.get(event);\n if (set) {\n set.forEach((handler) => handler(...args));\n }\n }\n\n removeAllListeners(): void {\n this.listeners.clear();\n }\n}\n","import { io, Socket } from 'socket.io-client';\nimport { ProtocolCodec } from './ProtocolCodec';\nimport { EventEmitter } from '../core/EventEmitter';\n\nexport interface TransportEvents {\n connected: () => void;\n disconnected: (reason: string) => void;\n error: (error: any) => void;\n message: (payload: any) => void;\n}\n\nexport class TransportManager extends EventEmitter<TransportEvents> {\n private socket: Socket | null = null;\n private apiKey: string | null = null;\n private serverUrl: string | null = null;\n\n constructor() {\n super();\n }\n\n /**\n * Connects to the Synxed SDK namespace.\n */\n connect(apiKey: string, serverUrl: string): void {\n if (this.socket?.connected) return;\n\n this.apiKey = apiKey;\n this.serverUrl = serverUrl;\n\n this.socket = io(`${serverUrl}/sdk`, {\n auth: { apiKey },\n transports: ['websocket'],\n });\n\n this.socket.on('connect', () => {\n this.emit('connected');\n });\n\n this.socket.on('disconnect', (reason) => {\n this.emit('disconnected', reason);\n });\n\n this.socket.on('connect_error', (error) => {\n this.emit('error', error);\n });\n\n this.socket.on('d', (data: ArrayBuffer | Uint8Array) => {\n try {\n const uint8Array = data instanceof Uint8Array ? data : new Uint8Array(data);\n const payload = ProtocolCodec.decodeServerEnvelope(uint8Array);\n this.emit('message', payload);\n } catch (err) {\n this.emit('error', new Error(`Failed to decode message: ${err}`));\n }\n });\n }\n\n /**\n * Disconnects from the server.\n */\n disconnect(): void {\n if (this.socket) {\n this.socket.disconnect();\n this.socket = null;\n }\n }\n\n /**\n * Sends an init message to the backend.\n */\n sendInit(params: any): void {\n if (!this.socket?.connected) {\n throw new Error('Socket not connected');\n }\n\n const payload = { init: params };\n const bytes = ProtocolCodec.encodeClientEnvelope(payload);\n this.socket.emit('d', bytes);\n }\n\n /**\n * Checks if the socket is connected.\n */\n get isConnected(): boolean {\n return this.socket?.connected || false;\n }\n}\n","import { Howl } from 'howler';\nimport { EventEmitter } from '../core/EventEmitter';\n\nexport interface AudioEvents {\n playing: () => void;\n paused: () => void;\n stopped: () => void;\n ended: () => void;\n loading: () => void;\n loaded: () => void;\n error: (error: any) => void;\n timeupdate: (data: { currentTime: number; duration: number }) => void;\n}\n\nexport class AudioEngine extends EventEmitter<AudioEvents> {\n private howl: Howl | null = null;\n private updateTimer: number | null = null;\n\n constructor() {\n super();\n }\n\n /**\n * Loads a track URL.\n */\n load(url: string, isHls: boolean): void {\n this.stop();\n this.emit('loading');\n\n // For HLS, we'd ideally use hls.js. \n // Howler doesn't natively support HLS streams well without custom logic.\n // For now, we'll use HTML5 audio mode which works for some HLS streams natively.\n this.howl = new Howl({\n src: [url],\n html5: true, // Required for streaming large files\n format: isHls ? ['m3u8'] : undefined,\n onload: () => {\n this.emit('loaded');\n },\n onloaderror: (id, err) => {\n this.emit('error', err);\n },\n onplay: () => {\n this.emit('playing');\n this.startTimeUpdateLoop();\n },\n onpause: () => {\n this.emit('paused');\n this.stopTimeUpdateLoop();\n },\n onstop: () => {\n this.emit('stopped');\n this.stopTimeUpdateLoop();\n },\n onend: () => {\n this.emit('ended');\n this.stopTimeUpdateLoop();\n },\n });\n }\n\n play(): void {\n this.howl?.play();\n }\n\n pause(): void {\n this.howl?.pause();\n }\n\n stop(): void {\n this.howl?.stop();\n this.howl?.unload();\n this.howl = null;\n }\n\n seek(ms: number): void {\n this.howl?.seek(ms / 1000);\n }\n\n setVolume(volume: number): void {\n this.howl?.volume(volume);\n }\n\n get duration(): number {\n return (this.howl?.duration() || 0) * 1000;\n }\n\n get currentTime(): number {\n return (this.howl?.seek() as number || 0) * 1000;\n }\n\n private startTimeUpdateLoop(): void {\n this.stopTimeUpdateLoop();\n const update = () => {\n if (this.howl?.playing()) {\n this.emit('timeupdate', {\n currentTime: this.currentTime,\n duration: this.duration,\n });\n this.updateTimer = requestAnimationFrame(update);\n }\n };\n this.updateTimer = requestAnimationFrame(update);\n }\n\n private stopTimeUpdateLoop(): void {\n if (this.updateTimer !== null) {\n cancelAnimationFrame(this.updateTimer);\n this.updateTimer = null;\n }\n }\n}\n","import { EventEmitter } from '../core/EventEmitter';\n\nexport interface PlaylistEvents {\n trackChanged: (track: any) => void;\n queueUpdated: (tracks: any[]) => void;\n}\n\nexport class PlaylistManager extends EventEmitter<PlaylistEvents> {\n private queue: any[] = [];\n private currentIndex: number = -1;\n\n constructor() {\n super();\n }\n\n setQueue(tracks: any[]): void {\n this.queue = tracks;\n this.currentIndex = tracks.length > 0 ? 0 : -1;\n this.emit('queueUpdated', this.queue);\n }\n\n getCurrentTrack(): any | null {\n if (this.currentIndex >= 0 && this.currentIndex < this.queue.length) {\n return this.queue[this.currentIndex];\n }\n return null;\n }\n\n next(): any | null {\n if (this.currentIndex < this.queue.length - 1) {\n this.currentIndex++;\n const track = this.getCurrentTrack();\n this.emit('trackChanged', track);\n return track;\n }\n return null;\n }\n\n previous(): any | null {\n if (this.currentIndex > 0) {\n this.currentIndex--;\n const track = this.getCurrentTrack();\n this.emit('trackChanged', track);\n return track;\n }\n return null;\n }\n\n skipTo(index: number): any | null {\n if (index >= 0 && index < this.queue.length) {\n this.currentIndex = index;\n const track = this.getCurrentTrack();\n this.emit('trackChanged', track);\n return track;\n }\n return null;\n }\n\n get hasNext(): boolean {\n return this.currentIndex < this.queue.length - 1;\n }\n\n get hasPrevious(): boolean {\n return this.currentIndex > 0;\n }\n}\n","export class SynxedError extends Error {\n constructor(message: string, public code?: string) {\n super(message);\n this.name = 'SynxedError';\n }\n}\n\nexport class SynxedConnectionError extends SynxedError {\n constructor(message: string) {\n super(message);\n this.name = 'SynxedConnectionError';\n }\n}\n\nexport class SynxedPlaybackError extends SynxedError {\n constructor(error: any) {\n super(typeof error === 'string' ? error : 'Playback failed');\n this.name = 'SynxedPlaybackError';\n }\n}\n\nexport class SynxedProtocolError extends SynxedError {\n constructor(message: string) {\n super(message);\n this.name = 'SynxedProtocolError';\n }\n}\n","import { TransportManager } from '../transport/TransportManager';\nimport { AudioEngine } from '../audio/AudioEngine';\nimport { PlaylistManager } from '../playlist/PlaylistManager';\nimport { EventEmitter } from './EventEmitter';\nimport { ContentKind } from '../proto/sdk-streaming';\nimport { SynxedConfig, PlayerState, PlaySongOptions, PlayPlaylistOptions, SynxedEvents } from '../types';\nimport { SynxedError, SynxedPlaybackError, SynxedConnectionError } from './errors';\n\nexport class SynxedPlayer extends EventEmitter<SynxedEvents> {\n private transport: TransportManager;\n private audio: AudioEngine;\n private playlist: PlaylistManager;\n private config: SynxedConfig;\n private status: PlayerState['status'] = 'idle';\n\n constructor(config: SynxedConfig) {\n super();\n this.config = config;\n this.transport = new TransportManager();\n this.audio = new AudioEngine();\n this.playlist = new PlaylistManager();\n\n this.setupListeners();\n\n if (config.autoConnect) {\n this.connect();\n }\n }\n\n private setupListeners(): void {\n // Transport Listeners\n this.transport.on('connected', () => this.emit('connected'));\n this.transport.on('disconnected', (reason) => this.emit('disconnected', reason));\n this.transport.on('error', (error) => this.emit('error', new SynxedConnectionError(error.message)));\n this.transport.on('message', (payload) => this.handleServerMessage(payload));\n\n // Audio Listeners\n this.audio.on('playing', () => this.updateStatus('playing'));\n this.audio.on('paused', () => this.updateStatus('paused'));\n this.audio.on('stopped', () => this.updateStatus('idle'));\n this.audio.on('loading', () => this.updateStatus('loading'));\n this.audio.on('error', (error) => this.emit('error', new SynxedPlaybackError(error)));\n this.audio.on('timeupdate', (time) => this.emit('timeUpdate', time));\n this.audio.on('ended', () => this.handleTrackEnded());\n }\n\n private connect(): void {\n this.transport.connect(this.config.apiKey, this.config.serverUrl);\n }\n\n async playSong(options: PlaySongOptions): Promise<void> {\n if (!this.transport.isConnected) this.connect();\n \n this.transport.sendInit({\n content_kind: ContentKind.SONG,\n catalog_track_id: options.catalogTrackId,\n internal_track_id: options.internalTrackId,\n listener_id: options.listenerId,\n });\n }\n\n async playPlaylist(options: PlayPlaylistOptions): Promise<void> {\n if (!this.transport.isConnected) this.connect();\n\n this.transport.sendInit({\n content_kind: ContentKind.PLAYLIST,\n playlist_code: options.playlistCode,\n listener_id: options.listenerId,\n });\n }\n\n pause(): void {\n this.audio.pause();\n }\n\n resume(): void {\n this.audio.play();\n }\n\n stop(): void {\n this.audio.stop();\n }\n\n skip(): void {\n const nextTrack = this.playlist.next();\n if (nextTrack) {\n // In current backend, we need to re-init for each track\n // This logic will be refined once backend handles queues\n this.playSong({ catalogTrackId: nextTrack.id }); \n }\n }\n\n seek(ms: number): void {\n this.audio.seek(ms);\n }\n\n setVolume(volume: number): void {\n this.audio.setVolume(volume);\n }\n\n private handleServerMessage(payload: any): void {\n if (payload.init_ack) {\n const ack = payload.init_ack;\n this.audio.load(ack.playback_url, ack.is_hls);\n this.audio.play();\n \n // Parse content_summary if it's a playlist\n if (ack.content_summary) {\n try {\n const summary = JSON.parse(ack.content_summary);\n if (summary.tracks) {\n this.playlist.setQueue(summary.tracks);\n }\n } catch (e) {\n // Ignore parse errors for now\n }\n }\n } else if (payload.error) {\n this.emit('error', new SynxedError(payload.error.message, payload.error.code));\n }\n }\n\n private handleTrackEnded(): void {\n if (this.playlist.hasNext) {\n this.skip();\n } else {\n this.updateStatus('idle');\n }\n }\n\n private updateStatus(status: PlayerState['status']): void {\n this.status = status;\n this.emit('stateChange', {\n status,\n currentTime: this.audio.currentTime,\n duration: this.audio.duration,\n volume: 1, // Store volume locally if needed\n });\n }\n\n destroy(): void {\n this.audio.stop();\n this.transport.disconnect();\n this.removeAllListeners();\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "synxed-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Synxed music SDK — integrate streaming music playback into any web app",
|
|
5
|
+
"bin": {
|
|
6
|
+
"synxed": "dist/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"module": "./dist/index.mjs",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.mjs",
|
|
15
|
+
"require": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"dev": "tsup --watch",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"lint": "tsc --noEmit",
|
|
27
|
+
"prepublishOnly": "npm run build"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"socket.io-client": "^4.7.5",
|
|
31
|
+
"protobufjs": "^7.3.0",
|
|
32
|
+
"howler": "^2.2.4"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"typescript": "^5.5.4",
|
|
36
|
+
"tsup": "^8.2.3",
|
|
37
|
+
"vitest": "^2.0.5",
|
|
38
|
+
"@types/howler": "^2.2.11"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"hls.js": "^1.5.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependenciesMeta": {
|
|
44
|
+
"hls.js": {
|
|
45
|
+
"optional": true
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|