@ciphore/radiocli 0.2.4 → 0.2.6
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/CHANGELOG.md +103 -2
- package/CONTRIBUTING.md +28 -0
- package/README.md +108 -253
- package/SECURITY.md +23 -1
- package/dist/agent/alarm-service.js +10 -7
- package/dist/agent/cli.js +16 -1
- package/dist/agent/command-schema.js +47 -0
- package/dist/agent/headless-host.js +226 -81
- package/dist/agent/launcher.js +2 -1
- package/dist/agent/mcp-server.js +10 -5
- package/dist/agent/service.js +75 -40
- package/dist/agent/session.js +214 -24
- package/dist/agent/tui-session.js +213 -0
- package/dist/alarms/cli.js +19 -3
- package/dist/alarms/occurrence-cache.js +25 -0
- package/dist/alarms/runner.js +87 -23
- package/dist/alarms/schedule.js +29 -1
- package/dist/alarms/scheduler.js +11 -3
- package/dist/alarms/system-volume-linux.js +160 -0
- package/dist/alarms/system-volume-ownership.js +76 -8
- package/dist/alarms/system-volume.js +44 -23
- package/dist/cli/arguments.js +212 -0
- package/dist/cli/completion.js +44 -0
- package/dist/cli/doctor.js +156 -0
- package/dist/cli/interactive.js +79 -0
- package/dist/cli/listener.js +227 -0
- package/dist/cli/output.js +121 -0
- package/dist/cli/render-tui.js +34 -0
- package/dist/cli.js +182 -199
- package/dist/platform/capabilities.js +8 -1
- package/dist/platform/desktop-media.js +61 -0
- package/dist/platform/diagnostics.js +273 -0
- package/dist/platform/json-worker.js +111 -0
- package/dist/platform/launch-command.js +9 -6
- package/dist/platform/mpris.js +356 -0
- package/dist/platform/network.js +69 -1
- package/dist/platform/omarchy.js +147 -0
- package/dist/platform/packages.js +7 -1
- package/dist/platform/runtime.js +1 -1
- package/dist/platform/terminals.js +24 -4
- package/dist/player/airplay-discovery.js +144 -61
- package/dist/player/airplay-policy.js +15 -0
- package/dist/player/airplay-process.js +63 -0
- package/dist/player/airplay-readiness.js +42 -0
- package/dist/player/airplay-sender-health.js +49 -49
- package/dist/player/airplay-sender-package.js +5 -0
- package/dist/player/airplay-sender-patch.js +102 -4
- package/dist/player/airplay-setup.js +51 -0
- package/dist/player/airplay-worker.js +97 -41
- package/dist/player/backend-install.js +9 -6
- package/dist/player/ffmpeg-input.js +37 -0
- package/dist/player/mpv-desktop-media.js +194 -0
- package/dist/player/mpv-ipc-client.js +51 -2
- package/dist/player/player-controller.js +390 -73
- package/dist/player/radio-engine.js +282 -0
- package/dist/playlists/playlist.js +18 -16
- package/dist/providers/cache.js +291 -75
- package/dist/providers/discovery-session.js +111 -0
- package/dist/providers/provider-manager.js +106 -28
- package/dist/providers/radio-browser.js +272 -41
- package/dist/providers/radio-garden.js +4 -2
- package/dist/setup-playback.js +152 -0
- package/dist/setup.js +88 -21
- package/dist/storage/file-lock.js +130 -0
- package/dist/storage/read-settings.js +33 -0
- package/dist/storage/replace-file.js +20 -0
- package/dist/storage/repository.js +247 -0
- package/dist/storage/station-identity.js +21 -0
- package/dist/storage/station-schema.js +27 -0
- package/dist/storage/store.js +314 -76
- package/dist/types.js +22 -1
- package/dist/ui/AdaptiveContent.js +21 -24
- package/dist/ui/App.js +708 -454
- package/dist/ui/AppContent.js +5 -5
- package/dist/ui/airplay-settings.js +12 -5
- package/dist/ui/bidi-ranges.js +45 -0
- package/dist/ui/components/AdaptiveMarquee.js +16 -9
- package/dist/ui/components/ReceiverVisualRow.js +39 -0
- package/dist/ui/format.js +37 -4
- package/dist/ui/help-content.js +5 -2
- package/dist/ui/rtl-output.js +137 -0
- package/dist/ui/screen-items.js +10 -4
- package/dist/ui/screens/AirPlaySettingsScreen.js +3 -3
- package/dist/ui/screens/AlarmsScreen.js +5 -2
- package/dist/ui/screens/HelpScreen.js +3 -3
- package/dist/ui/screens/NowPlayingScreen.js +2 -21
- package/dist/ui/screens/SettingsScreen.js +16 -16
- package/dist/ui/screens/StatsScreen.js +1 -1
- package/dist/ui/text-direction.js +9 -0
- package/dist/ui/theme.js +46 -0
- package/dist/ui/use-alarm-tui.js +24 -24
- package/dist/ui/use-app-input.js +42 -8
- package/dist/ui/use-command-executor.js +21 -14
- package/dist/ui/visualizers/boat-lake-kernel.js +325 -0
- package/dist/ui/visualizers/boat-lake.js +161 -0
- package/dist/ui/visualizers/gallop.js +2 -2
- package/dist/ui/visualizers/receiver-style-registry.js +3 -1
- package/dist/ui/visualizers/receiver-visualizers.js +6 -108
- package/dist/ui/visualizers/sumi-ocean.js +183 -0
- package/dist/ui/visualizers/terminal-cell.js +45 -0
- package/dist/update-check.js +2 -1
- package/docs/THIRD_PARTY_NOTICES.md +6 -4
- package/docs/boat-lake.md +88 -0
- package/docs/sumi-ocean.md +80 -0
- package/package.json +36 -12
- package/scripts/build-airplay-native.mjs +22 -0
- package/vendor/node-airtunes2/LICENSE +208 -0
- package/vendor/node-airtunes2/RADIOCLI_PATCH.md +29 -0
- package/vendor/node-airtunes2/README.md +225 -0
- package/vendor/node-airtunes2/UPSTREAM.json +50 -0
- package/vendor/node-airtunes2/binding.gyp +41 -0
- package/vendor/node-airtunes2/lib/ap2_test.js +4 -0
- package/vendor/node-airtunes2/lib/atv.js +307 -0
- package/vendor/node-airtunes2/lib/atvAuthenticator.js +136 -0
- package/vendor/node-airtunes2/lib/audio_out.js +69 -0
- package/vendor/node-airtunes2/lib/circular_buffer.js +133 -0
- package/vendor/node-airtunes2/lib/config.js +26 -0
- package/vendor/node-airtunes2/lib/device_airtunes.js +532 -0
- package/vendor/node-airtunes2/lib/devices.js +266 -0
- package/vendor/node-airtunes2/lib/ed25519.js +26 -0
- package/vendor/node-airtunes2/lib/homekit/credentials.js +86 -0
- package/vendor/node-airtunes2/lib/homekit/encryption.js +69 -0
- package/vendor/node-airtunes2/lib/homekit/number.js +44 -0
- package/vendor/node-airtunes2/lib/homekit/tlv.js +95 -0
- package/vendor/node-airtunes2/lib/http.js +189 -0
- package/vendor/node-airtunes2/lib/index.d.ts +141 -0
- package/vendor/node-airtunes2/lib/index.js +127 -0
- package/vendor/node-airtunes2/lib/ntp.js +37 -0
- package/vendor/node-airtunes2/lib/num_util.js +21 -0
- package/vendor/node-airtunes2/lib/packet_pool.js +43 -0
- package/vendor/node-airtunes2/lib/rtsp.js +1807 -0
- package/vendor/node-airtunes2/lib/srp.js +158 -0
- package/vendor/node-airtunes2/lib/udp_servers.js +214 -0
- package/vendor/node-airtunes2/lib/util.js +12 -0
- package/vendor/node-airtunes2/package.json +31 -0
- package/vendor/node-airtunes2/src/CADebugMacros.h +257 -0
- package/vendor/node-airtunes2/src/CAHostTimeBase.cpp +105 -0
- package/vendor/node-airtunes2/src/CAHostTimeBase.h +224 -0
- package/vendor/node-airtunes2/src/aes_utils.c +864 -0
- package/vendor/node-airtunes2/src/aes_utils.h +24 -0
- package/vendor/node-airtunes2/src/base64.c +136 -0
- package/vendor/node-airtunes2/src/base64.h +42 -0
- package/vendor/node-airtunes2/src/bindings.cc +29 -0
- package/vendor/node-airtunes2/src/codec.cc +170 -0
- package/vendor/node-airtunes2/src/coreaudio.cc +332 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
const net = require('net')
|
|
2
|
+
|
|
3
|
+
// ...
|
|
4
|
+
|
|
5
|
+
const HttpMessage = (parseStartLine, writeStartLine) => {
|
|
6
|
+
const instance = {}
|
|
7
|
+
|
|
8
|
+
instance.parse = (buffer) => {
|
|
9
|
+
const messageObject = {}
|
|
10
|
+
|
|
11
|
+
// ...
|
|
12
|
+
|
|
13
|
+
let bodyIndex = buffer.indexOf('\r\n\r\n')
|
|
14
|
+
let headerString = buffer.slice(0, bodyIndex).toString()
|
|
15
|
+
let body = buffer.slice(bodyIndex + 4)
|
|
16
|
+
|
|
17
|
+
headerString.replace(/\r\n/g, '\n')
|
|
18
|
+
|
|
19
|
+
const lines = headerString.split('\n')
|
|
20
|
+
|
|
21
|
+
bodyIndex += 2
|
|
22
|
+
|
|
23
|
+
// ...
|
|
24
|
+
|
|
25
|
+
let line = lines.shift()
|
|
26
|
+
parseStartLine(line, messageObject)
|
|
27
|
+
|
|
28
|
+
// ...
|
|
29
|
+
|
|
30
|
+
messageObject.headers = {}
|
|
31
|
+
|
|
32
|
+
line = lines.shift()
|
|
33
|
+
while (line) {
|
|
34
|
+
const headerName = line.substr(0, line.indexOf(':'))
|
|
35
|
+
const headerValue = line.substr(line.indexOf(':') + 1)
|
|
36
|
+
|
|
37
|
+
messageObject.headers[headerName] = headerValue.trim()
|
|
38
|
+
|
|
39
|
+
line = lines.shift()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ...
|
|
43
|
+
|
|
44
|
+
if (messageObject.headers['Content-Length'] && messageObject.headers['Content-Length'] != 0) {
|
|
45
|
+
messageObject.body = body
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return messageObject
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
instance.write = (messageObject) => {
|
|
52
|
+
let messageString = writeStartLine(messageObject)
|
|
53
|
+
messageString += '\r\n'
|
|
54
|
+
|
|
55
|
+
if (messageObject.body) {
|
|
56
|
+
messageObject.headers['Content-Length'] = Buffer.byteLength(messageObject.body)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const header in messageObject.headers) {
|
|
60
|
+
messageString += `${header}: ${messageObject.headers[header]}\r\n`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
messageString += '\r\n'
|
|
64
|
+
|
|
65
|
+
const buffer = Buffer.from(messageString)
|
|
66
|
+
|
|
67
|
+
if (!messageObject.body) {
|
|
68
|
+
return buffer
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return Buffer.concat([buffer, messageObject.body], buffer.length + messageObject.body.length)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return instance
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const HttpRequest
|
|
78
|
+
= () => HttpMessage(
|
|
79
|
+
() => {}, // currently not parsing requests.
|
|
80
|
+
messageObject => `${messageObject.method} ${messageObject.path} HTTP/1.1`,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const HttpResponse
|
|
84
|
+
= () => HttpMessage(
|
|
85
|
+
(line, messageObject) => messageObject.statusCode = parseInt(line.split(' ')[1]),
|
|
86
|
+
() => {}, // currently not writing responses.
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
// ...
|
|
90
|
+
|
|
91
|
+
class HttpClient {
|
|
92
|
+
constructor() {
|
|
93
|
+
this.resolveQueue = []
|
|
94
|
+
this.pendingResponse = null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ....
|
|
98
|
+
|
|
99
|
+
parseResponse(data) {
|
|
100
|
+
const res = HttpResponse().parse(data)
|
|
101
|
+
if (res.headers['Content-Length'] > 0) {
|
|
102
|
+
const remaining = res.headers['Content-Length'] - res.body.byteLength
|
|
103
|
+
if (remaining > 0) {
|
|
104
|
+
// not all data for this response's corresponding request was read. Create a pending response object
|
|
105
|
+
// to use for further reads.
|
|
106
|
+
this.pendingResponse = {
|
|
107
|
+
res,
|
|
108
|
+
remaining,
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!this.pendingResponse) {
|
|
114
|
+
const rr = this.resolveQueue.shift()
|
|
115
|
+
res.statusCode === 200
|
|
116
|
+
? rr.resolve(res)
|
|
117
|
+
: rr.resolve(null)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ...
|
|
122
|
+
|
|
123
|
+
connect(host, port = 80) {
|
|
124
|
+
this.host = host
|
|
125
|
+
|
|
126
|
+
return new Promise((resolve) => {
|
|
127
|
+
this.socket = net.connect(
|
|
128
|
+
{
|
|
129
|
+
host,
|
|
130
|
+
port,
|
|
131
|
+
},
|
|
132
|
+
resolve,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
this.socket.on('data', (data) => {
|
|
136
|
+
if (!this.pendingResponse) {
|
|
137
|
+
// there is no response pending, parse the data.
|
|
138
|
+
this.parseResponse(data)
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
// incoming data for the pending response.
|
|
142
|
+
this.pendingResponse.res.body = Buffer.concat(
|
|
143
|
+
[this.pendingResponse.res.body, data],
|
|
144
|
+
data.byteLength + this.pendingResponse.res.body.byteLength,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
this.pendingResponse.remaining -= data.byteLength
|
|
148
|
+
if (this.pendingResponse.remaining === 0) {
|
|
149
|
+
// all remaining data for the pending response has been read; resolve the promise for the
|
|
150
|
+
// corresponding request.
|
|
151
|
+
const rr = this.resolveQueue.shift()
|
|
152
|
+
this.pendingResponse.res.statusCode === 200
|
|
153
|
+
? rr.resolve(this.pendingResponse.res)
|
|
154
|
+
: rr.reject(new Error(`HTTP status: ${this.pendingResponse.res.statusCode}`))
|
|
155
|
+
|
|
156
|
+
this.pendingResponse = null
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
request(method, path, headers, body) {
|
|
164
|
+
headers = headers || {}
|
|
165
|
+
// headers['Host'] = `${this.host}:${this.socket.remotePort}`;
|
|
166
|
+
|
|
167
|
+
const data = HttpRequest().write({
|
|
168
|
+
method,
|
|
169
|
+
path,
|
|
170
|
+
headers,
|
|
171
|
+
body,
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
// ...
|
|
175
|
+
|
|
176
|
+
return new Promise((resolve, reject) => {
|
|
177
|
+
this.resolveQueue.push({ resolve, reject })
|
|
178
|
+
this.socket.write(data)
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
close() {
|
|
183
|
+
this.socket.end()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ...
|
|
188
|
+
|
|
189
|
+
module.exports = () => new HttpClient()
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Path: node_modules/airtunes2/lib/index.d.ts
|
|
2
|
+
declare module 'airtunes2' {
|
|
3
|
+
export = AirTunes.Client;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
declare namespace AirTunes {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* AirTunes Casting Device
|
|
10
|
+
*/
|
|
11
|
+
class CastDevice {
|
|
12
|
+
name: string
|
|
13
|
+
host: string
|
|
14
|
+
port: number
|
|
15
|
+
addresses: string | string[]
|
|
16
|
+
txt: string[]
|
|
17
|
+
airplay2: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* AirTunes Controller
|
|
22
|
+
*/
|
|
23
|
+
class Controller {
|
|
24
|
+
key: string
|
|
25
|
+
setPasscode(passcode: string): void
|
|
26
|
+
setVolume(volume: number, cb?: Function): void
|
|
27
|
+
stop(cb?: Function): void
|
|
28
|
+
on(event: 'status', cb: (status: string) => void): void
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* AirTunes Device
|
|
33
|
+
* @param port The port of the device as reported by Zeroconf (default: 5000)
|
|
34
|
+
* @param volume The initial volume, which must be between 0 and 100. (default: 50)
|
|
35
|
+
* @param password The password for the device, if required. Bonjour indicates if the device demands a password.
|
|
36
|
+
*/
|
|
37
|
+
interface AirPlayDevice {
|
|
38
|
+
port: number
|
|
39
|
+
volume: number
|
|
40
|
+
password?: string
|
|
41
|
+
txt: string
|
|
42
|
+
airplay2: boolean
|
|
43
|
+
debug: boolean
|
|
44
|
+
forceAlac: boolean
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface CircularBuffer {
|
|
48
|
+
write(data: Buffer): void
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* AirTunes Server
|
|
53
|
+
*/
|
|
54
|
+
class Client {
|
|
55
|
+
constructor();
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Add a airtunes device
|
|
59
|
+
* @param host The IP address of the device reported by Zeroconf (ipv4)
|
|
60
|
+
* @param options The options for the device (port, volume, password)
|
|
61
|
+
* @param mode The mode of the device (0 = local, 1 = remote, 2 = dual)
|
|
62
|
+
* @param txt The txt record of the device
|
|
63
|
+
*/
|
|
64
|
+
add(host: string, options: AirPlayDevice, mode?: number, txt?: string): Controller;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Stop all devices
|
|
68
|
+
*/
|
|
69
|
+
stopAll(cb: Function): void
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Stop a device
|
|
73
|
+
*/
|
|
74
|
+
stop(deviceKey: string, cb?: Function): void
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* End the circular buffer
|
|
78
|
+
*/
|
|
79
|
+
end(): void
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Set the volume of a device
|
|
83
|
+
* @param deviceKey The key of the device
|
|
84
|
+
* @param volume The volume to set (0-100)
|
|
85
|
+
* @param cb The callback function
|
|
86
|
+
*/
|
|
87
|
+
setVolume(deviceKey: string, volume: string, cb?: Function): void
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Set the current playback position of a device
|
|
91
|
+
* @param deviceKey The key of the device
|
|
92
|
+
* @param progress The progress to set (0-1)
|
|
93
|
+
* @param duration The duration of the track
|
|
94
|
+
* @param callback The callback function
|
|
95
|
+
*/
|
|
96
|
+
setProgress(deviceKey: string, progress: number, duration: number, callback?: Function): void
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Set the playback state of a device
|
|
100
|
+
* @param deviceKey The key of the device
|
|
101
|
+
* @param name The name of the track
|
|
102
|
+
* @param artist The artist of the track
|
|
103
|
+
* @param album The album of the track
|
|
104
|
+
* @param callback The callback function
|
|
105
|
+
*/
|
|
106
|
+
setTrackInfo(deviceKey: string, name: string, artist: string, album: string, callback?: Function): void
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Reset the AirTunes client
|
|
110
|
+
*/
|
|
111
|
+
reset(): void
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Set the artwork of a device
|
|
115
|
+
* @param deviceKey The key of the device
|
|
116
|
+
* @param art The artwork to set
|
|
117
|
+
* @param contentType The content type of the artwork
|
|
118
|
+
* @param callback The callback function
|
|
119
|
+
*/
|
|
120
|
+
setArtwork(deviceKey: string, art: string | Buffer, contentType: string, callback?: Function): void
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Set the passcode of a device
|
|
125
|
+
* @param deviceKey The key of the device
|
|
126
|
+
* @param passcode The passcode to set
|
|
127
|
+
*/
|
|
128
|
+
setPasscode(deviceKey: string, passcode: string): void
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
// Untyped methods
|
|
132
|
+
addCoreAudio(options: Object): void
|
|
133
|
+
write(data: string): void
|
|
134
|
+
|
|
135
|
+
// Untyped properties
|
|
136
|
+
circularBuffer: CircularBuffer
|
|
137
|
+
devices: unknown
|
|
138
|
+
writable: boolean
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const Stream = require('stream')
|
|
2
|
+
const util = require('util')
|
|
3
|
+
const Devices = require('./devices')
|
|
4
|
+
const config = require('./config')
|
|
5
|
+
const CircularBuffer = require('./circular_buffer')
|
|
6
|
+
const AudioOut = require('./audio_out')
|
|
7
|
+
// const mdns = require("mdns-js");
|
|
8
|
+
// var net = require('net');
|
|
9
|
+
function AirTunes() {
|
|
10
|
+
var self = this
|
|
11
|
+
|
|
12
|
+
Stream.call(this)
|
|
13
|
+
|
|
14
|
+
var audioOut = new AudioOut()
|
|
15
|
+
this.devices = new Devices(audioOut)
|
|
16
|
+
|
|
17
|
+
this.devices.init()
|
|
18
|
+
this.devices.on('status', function (key, status, desc) {
|
|
19
|
+
self.emit('device', key, status, desc)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
this.circularBuffer = new CircularBuffer(config.packets_in_buffer, config.packet_size)
|
|
23
|
+
|
|
24
|
+
this.circularBuffer.on('status', function (status) {
|
|
25
|
+
self.emit('buffer', status)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
audioOut.init(this.devices, this.circularBuffer)
|
|
29
|
+
|
|
30
|
+
this.circularBuffer.on('drain', function () {
|
|
31
|
+
self.emit('drain')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
this.circularBuffer.on('error', function (err) {
|
|
35
|
+
self.emit('error', err)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
this.writable = true
|
|
39
|
+
|
|
40
|
+
// let x = mdns.tcp("dacp");
|
|
41
|
+
// let txt_record = {
|
|
42
|
+
// Ver: "131075",
|
|
43
|
+
// DbId: "63B5E5C0C201542E",
|
|
44
|
+
// txtvers: "1",
|
|
45
|
+
// OSsi: "0x1F5"
|
|
46
|
+
// };
|
|
47
|
+
|
|
48
|
+
// this.remoteBroadcast= mdns.createAdvertisement(x, 5648, {
|
|
49
|
+
// name: "iTunes_Ctrl_04F8191D99BEC6E9",
|
|
50
|
+
// txt: txt_record,
|
|
51
|
+
// });
|
|
52
|
+
// this.remoteBroadcast.start();
|
|
53
|
+
|
|
54
|
+
// this.remoteserver = net.createServer(function(sock) {
|
|
55
|
+
// // We have a connection - a socket object is assigned to the connection automatically
|
|
56
|
+
// // Add a 'data' event handler to this instance of socket
|
|
57
|
+
// sock.on('data', function(data) {
|
|
58
|
+
// console.log('DATA FROM' + sock.remoteAddress + ': ' + data);
|
|
59
|
+
// // Write the data back to the socket, the client will receive it as data from the server
|
|
60
|
+
// sock.write("HTTP/1.1 204 No Content" + "\r\n" +
|
|
61
|
+
// "DAAP-Server: iTunes/10.6 (Mac OS X)" + "\r\n" +
|
|
62
|
+
// "Content-Type: application/x-dmap-tagged" + "\r\n" +
|
|
63
|
+
// "Content-Length: 0\r\n\r\n"
|
|
64
|
+
// );
|
|
65
|
+
// });
|
|
66
|
+
|
|
67
|
+
// // Add a 'close' event handler to this instance of socket
|
|
68
|
+
// sock.on('close', function(data) {
|
|
69
|
+
// console.log('CLOSED: ' + sock.remoteAddress +' '+ sock.remotePort);
|
|
70
|
+
// });
|
|
71
|
+
|
|
72
|
+
// }).listen(5648);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
util.inherits(AirTunes, Stream)
|
|
76
|
+
|
|
77
|
+
AirTunes.prototype.add = function (host, options, mode = 0, txt = '') {
|
|
78
|
+
return this.devices.add('airtunes', host, options, mode, txt)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
AirTunes.prototype.addCoreAudio = function (options) {
|
|
82
|
+
return this.devices.add('coreaudio', null, options)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
AirTunes.prototype.stopAll = function (cb) {
|
|
86
|
+
this.devices.stopAll(cb)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
AirTunes.prototype.stop = function (deviceKey) {
|
|
90
|
+
this.devices.stop(deviceKey)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
AirTunes.prototype.setVolume = function (deviceKey, volume, callback) {
|
|
94
|
+
this.devices.setVolume(deviceKey, volume, callback)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
AirTunes.prototype.setProgress = function (deviceKey, progress, duration, callback) {
|
|
98
|
+
this.devices.setProgress(deviceKey, progress, duration, callback)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
AirTunes.prototype.setTrackInfo = function (deviceKey, name, artist, album, callback) {
|
|
102
|
+
this.devices.setTrackInfo(deviceKey, name, artist, album, callback)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
AirTunes.prototype.reset = function () {
|
|
106
|
+
this.circularBuffer.reset()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
AirTunes.prototype.setArtwork = function (deviceKey, art, contentType, callback) {
|
|
110
|
+
this.devices.setArtwork(deviceKey, art, contentType, callback)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
AirTunes.prototype.write = function (data) {
|
|
114
|
+
return this.circularBuffer.write(data)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
AirTunes.prototype.setPasscode = function (deviceKey, passcode) {
|
|
118
|
+
this.devices.setPasscode(deviceKey, passcode)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
AirTunes.prototype.end = function () {
|
|
122
|
+
this.circularBuffer.end()
|
|
123
|
+
// this.remoteBroadcast.stop();
|
|
124
|
+
// this.remoteserver.close();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = AirTunes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const config = require('./config')
|
|
2
|
+
|
|
3
|
+
function NTP() {
|
|
4
|
+
this.timeRef = new Date().getTime() - config.ntp_epoch * 1000
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
NTP.prototype.timestamp = function () {
|
|
8
|
+
const time = new Date().getTime() - this.timeRef
|
|
9
|
+
const sec = Math.floor(time / 1000)
|
|
10
|
+
|
|
11
|
+
const msec = time - sec * 1000
|
|
12
|
+
const ntp_msec = Math.floor(msec * 4294967.296)
|
|
13
|
+
|
|
14
|
+
const ts = new Buffer(8)
|
|
15
|
+
|
|
16
|
+
ts.writeUInt32BE(sec, 0)
|
|
17
|
+
ts.writeUInt32BE(ntp_msec, 4)
|
|
18
|
+
|
|
19
|
+
return ts
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
NTP.prototype.getTime = function (time) {
|
|
23
|
+
var time = new Date().getTime() - this.timeRef
|
|
24
|
+
var sec = Math.floor(time / 1000)
|
|
25
|
+
|
|
26
|
+
var msec = time - sec * 1000
|
|
27
|
+
var ntp_msec = Math.floor(msec * 4294967.296)
|
|
28
|
+
|
|
29
|
+
var ts = new Buffer(8)
|
|
30
|
+
|
|
31
|
+
ts.writeUInt32BE(sec, 0)
|
|
32
|
+
ts.writeUInt32BE(ntp_msec, 4)
|
|
33
|
+
console.log(time, sec)
|
|
34
|
+
return ntp_msec
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = new NTP()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const crypto = require('crypto')
|
|
2
|
+
|
|
3
|
+
exports.randomHex = function (n) {
|
|
4
|
+
return crypto.randomBytes(n).toString('hex')
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
exports.randomBase64 = function (n) {
|
|
8
|
+
return crypto.randomBytes(n).toString('base64').replace('=', '')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
exports.randomInt = function (n) {
|
|
12
|
+
return Math.floor(Math.random() * Math.pow(10, n))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
exports.low16 = function (i) {
|
|
16
|
+
return Math.abs(i) % 65536
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
exports.low32 = function (i) {
|
|
20
|
+
return Math.abs(i) % 4294967296
|
|
21
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const config = require('./config')
|
|
2
|
+
|
|
3
|
+
function PacketPool() {
|
|
4
|
+
this.pool = []
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
PacketPool.prototype.getPacket = function () {
|
|
8
|
+
let packet = this.pool.shift()
|
|
9
|
+
|
|
10
|
+
if (!packet) {
|
|
11
|
+
packet = new Packet(this)
|
|
12
|
+
}
|
|
13
|
+
else
|
|
14
|
+
packet.retain()
|
|
15
|
+
|
|
16
|
+
return packet
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
PacketPool.prototype.release = function (packet) {
|
|
20
|
+
this.pool.push(packet)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function Packet(pool) {
|
|
24
|
+
this.pool = pool
|
|
25
|
+
this.ref = 1
|
|
26
|
+
this.seq = null
|
|
27
|
+
this.pcm = new Buffer(config.packet_size)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
Packet.prototype.retain = function () {
|
|
31
|
+
this.ref++
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
Packet.prototype.release = function () {
|
|
35
|
+
this.ref--
|
|
36
|
+
|
|
37
|
+
if (this.ref === 0) {
|
|
38
|
+
this.seq = null
|
|
39
|
+
this.pool.release(this)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = PacketPool
|