@lox-audioserver/node-airplay-sender 0.4.7 → 0.4.8
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 +1 -0
- package/dist/core/deviceAirtunes.d.ts +1 -1
- package/dist/core/deviceAirtunes.js +8 -7
- package/dist/core/devices.js +2 -2
- package/dist/esm/core/deviceAirtunes.js +8 -7
- package/dist/esm/core/devices.js +2 -2
- package/dist/esm/index.js +3 -0
- package/dist/esm/utils/http.js +4 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -0
- package/dist/utils/http.js +4 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,6 +61,7 @@ Creates and starts a sender for one AirPlay device. Returns the instance so you
|
|
|
61
61
|
|
|
62
62
|
**Events** (sent to `onEvent` callback)
|
|
63
63
|
- `device`: `{ event: "device", message: status, detail: { key, desc } }`
|
|
64
|
+
- `session-ended`: `{ event: "session-ended", message: reason, detail: { key, reason } }`
|
|
64
65
|
- `buffer`: `{ event: "buffer", message: status }` where status is `buffering|playing|drain|end`
|
|
65
66
|
- `error`: `{ event: "error", message }`
|
|
66
67
|
- `metrics`: `{ event: "metrics", detail }` sync drift snapshots emitted on each sync tick when enabled.
|
|
@@ -62,7 +62,7 @@ type AirTunesDeviceInstance = EventEmitter & {
|
|
|
62
62
|
logLine?: (...args: any[]) => void;
|
|
63
63
|
doHandshake: () => void;
|
|
64
64
|
relayAudio: () => void;
|
|
65
|
-
cleanup: () => void;
|
|
65
|
+
cleanup: (reason?: string) => void;
|
|
66
66
|
};
|
|
67
67
|
/**
|
|
68
68
|
* Construct a RAOP/AirPlay device handler.
|
|
@@ -247,7 +247,7 @@ AirTunesDevice.prototype.start = function () {
|
|
|
247
247
|
if (err) {
|
|
248
248
|
this.logLine?.(err.code);
|
|
249
249
|
this.status = 'stopped';
|
|
250
|
-
this.emit('status', 'stopped');
|
|
250
|
+
this.emit('status', 'stopped', 'udp_ports');
|
|
251
251
|
this.logLine?.('port issues');
|
|
252
252
|
this.emit('error', 'udp_ports', err.code);
|
|
253
253
|
return;
|
|
@@ -309,10 +309,11 @@ AirTunesDevice.prototype.doHandshake = function () {
|
|
|
309
309
|
this.emit('status', 'pair_success');
|
|
310
310
|
});
|
|
311
311
|
this.rtsp.on('end', (err) => {
|
|
312
|
-
|
|
313
|
-
this.
|
|
314
|
-
|
|
315
|
-
|
|
312
|
+
const reason = err == null ? 'unknown' : String(err);
|
|
313
|
+
this.logLine?.(reason);
|
|
314
|
+
this.cleanup(reason);
|
|
315
|
+
if (reason !== 'stopped')
|
|
316
|
+
this.emit(reason);
|
|
316
317
|
});
|
|
317
318
|
}
|
|
318
319
|
catch (e) {
|
|
@@ -402,11 +403,11 @@ AirTunesDevice.prototype.onSyncNeeded = function (seq) {
|
|
|
402
403
|
this.udpServers.sendControlSync(seq, this);
|
|
403
404
|
//if ( this.airplay2)this.rtsp.sendControlSync(seq, this, this.rtsp);
|
|
404
405
|
};
|
|
405
|
-
AirTunesDevice.prototype.cleanup = function () {
|
|
406
|
+
AirTunesDevice.prototype.cleanup = function (reason = 'stopped') {
|
|
406
407
|
this.audioSocket = null;
|
|
407
408
|
this.audioPacketHistory = null;
|
|
408
409
|
this.status = 'stopped';
|
|
409
|
-
this.emit('status', 'stopped');
|
|
410
|
+
this.emit('status', 'stopped', reason);
|
|
410
411
|
// console.debug('stop');
|
|
411
412
|
if (this.audioCallback) {
|
|
412
413
|
this.audioOut.removeListener('packet', this.audioCallback);
|
package/dist/core/devices.js
CHANGED
|
@@ -56,7 +56,7 @@ class Devices extends node_events_1.EventEmitter {
|
|
|
56
56
|
return previousDev;
|
|
57
57
|
}
|
|
58
58
|
this.devices[dev.key] = dev;
|
|
59
|
-
dev.on('status', (status) => {
|
|
59
|
+
dev.on('status', (status, desc = '') => {
|
|
60
60
|
if (status === 'error' || status === 'stopped') {
|
|
61
61
|
delete this.devices[dev.key];
|
|
62
62
|
this.checkAirTunesDevices();
|
|
@@ -64,7 +64,7 @@ class Devices extends node_events_1.EventEmitter {
|
|
|
64
64
|
if (this.hasAirTunes && status === 'playing') {
|
|
65
65
|
this.emit('need_sync');
|
|
66
66
|
}
|
|
67
|
-
this.emit('status', dev.key, status,
|
|
67
|
+
this.emit('status', dev.key, status, desc);
|
|
68
68
|
});
|
|
69
69
|
dev.start();
|
|
70
70
|
this.checkAirTunesDevices();
|
|
@@ -247,7 +247,7 @@ AirTunesDevice.prototype.start = function () {
|
|
|
247
247
|
if (err) {
|
|
248
248
|
this.logLine?.(err.code);
|
|
249
249
|
this.status = 'stopped';
|
|
250
|
-
this.emit('status', 'stopped');
|
|
250
|
+
this.emit('status', 'stopped', 'udp_ports');
|
|
251
251
|
this.logLine?.('port issues');
|
|
252
252
|
this.emit('error', 'udp_ports', err.code);
|
|
253
253
|
return;
|
|
@@ -309,10 +309,11 @@ AirTunesDevice.prototype.doHandshake = function () {
|
|
|
309
309
|
this.emit('status', 'pair_success');
|
|
310
310
|
});
|
|
311
311
|
this.rtsp.on('end', (err) => {
|
|
312
|
-
|
|
313
|
-
this.
|
|
314
|
-
|
|
315
|
-
|
|
312
|
+
const reason = err == null ? 'unknown' : String(err);
|
|
313
|
+
this.logLine?.(reason);
|
|
314
|
+
this.cleanup(reason);
|
|
315
|
+
if (reason !== 'stopped')
|
|
316
|
+
this.emit(reason);
|
|
316
317
|
});
|
|
317
318
|
}
|
|
318
319
|
catch (e) {
|
|
@@ -402,11 +403,11 @@ AirTunesDevice.prototype.onSyncNeeded = function (seq) {
|
|
|
402
403
|
this.udpServers.sendControlSync(seq, this);
|
|
403
404
|
//if ( this.airplay2)this.rtsp.sendControlSync(seq, this, this.rtsp);
|
|
404
405
|
};
|
|
405
|
-
AirTunesDevice.prototype.cleanup = function () {
|
|
406
|
+
AirTunesDevice.prototype.cleanup = function (reason = 'stopped') {
|
|
406
407
|
this.audioSocket = null;
|
|
407
408
|
this.audioPacketHistory = null;
|
|
408
409
|
this.status = 'stopped';
|
|
409
|
-
this.emit('status', 'stopped');
|
|
410
|
+
this.emit('status', 'stopped', reason);
|
|
410
411
|
// console.debug('stop');
|
|
411
412
|
if (this.audioCallback) {
|
|
412
413
|
this.audioOut.removeListener('packet', this.audioCallback);
|
package/dist/esm/core/devices.js
CHANGED
|
@@ -56,7 +56,7 @@ class Devices extends node_events_1.EventEmitter {
|
|
|
56
56
|
return previousDev;
|
|
57
57
|
}
|
|
58
58
|
this.devices[dev.key] = dev;
|
|
59
|
-
dev.on('status', (status) => {
|
|
59
|
+
dev.on('status', (status, desc = '') => {
|
|
60
60
|
if (status === 'error' || status === 'stopped') {
|
|
61
61
|
delete this.devices[dev.key];
|
|
62
62
|
this.checkAirTunesDevices();
|
|
@@ -64,7 +64,7 @@ class Devices extends node_events_1.EventEmitter {
|
|
|
64
64
|
if (this.hasAirTunes && status === 'playing') {
|
|
65
65
|
this.emit('need_sync');
|
|
66
66
|
}
|
|
67
|
-
this.emit('status', dev.key, status,
|
|
67
|
+
this.emit('status', dev.key, status, desc);
|
|
68
68
|
});
|
|
69
69
|
dev.start();
|
|
70
70
|
this.checkAirTunesDevices();
|
package/dist/esm/index.js
CHANGED
|
@@ -77,6 +77,9 @@ class LoxAirplaySender extends node_events_1.EventEmitter {
|
|
|
77
77
|
});
|
|
78
78
|
this.airtunes.on('device', (key, status, desc) => {
|
|
79
79
|
onEvent?.({ event: 'device', message: status, detail: { key, desc } });
|
|
80
|
+
if (status === 'stopped') {
|
|
81
|
+
onEvent?.({ event: 'session-ended', message: desc || 'stopped', detail: { key, reason: desc || 'stopped' } });
|
|
82
|
+
}
|
|
80
83
|
});
|
|
81
84
|
this.airtunes.on('buffer', (status) => {
|
|
82
85
|
onEvent?.({ event: 'buffer', message: status });
|
package/dist/esm/utils/http.js
CHANGED
|
@@ -98,15 +98,16 @@ class HttpClient {
|
|
|
98
98
|
port
|
|
99
99
|
}, resolve);
|
|
100
100
|
this.socket.on('data', data => {
|
|
101
|
+
const chunk = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
101
102
|
if (!this.pendingResponse) {
|
|
102
103
|
// there is no response pending, parse the data.
|
|
103
|
-
this.parseResponse(
|
|
104
|
+
this.parseResponse(chunk);
|
|
104
105
|
}
|
|
105
106
|
else {
|
|
106
107
|
// incoming data for the pending response.
|
|
107
108
|
const existing = this.pendingResponse.res.body ?? Buffer.alloc(0);
|
|
108
|
-
this.pendingResponse.res.body = Buffer.concat([existing,
|
|
109
|
-
this.pendingResponse.remaining -=
|
|
109
|
+
this.pendingResponse.res.body = Buffer.concat([existing, chunk], chunk.byteLength + existing.byteLength);
|
|
110
|
+
this.pendingResponse.remaining -= chunk.byteLength;
|
|
110
111
|
if (this.pendingResponse.remaining === 0) {
|
|
111
112
|
// all remaining data for the pending response has been read; resolve the promise for the
|
|
112
113
|
// corresponding request.
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -77,6 +77,9 @@ class LoxAirplaySender extends node_events_1.EventEmitter {
|
|
|
77
77
|
});
|
|
78
78
|
this.airtunes.on('device', (key, status, desc) => {
|
|
79
79
|
onEvent?.({ event: 'device', message: status, detail: { key, desc } });
|
|
80
|
+
if (status === 'stopped') {
|
|
81
|
+
onEvent?.({ event: 'session-ended', message: desc || 'stopped', detail: { key, reason: desc || 'stopped' } });
|
|
82
|
+
}
|
|
80
83
|
});
|
|
81
84
|
this.airtunes.on('buffer', (status) => {
|
|
82
85
|
onEvent?.({ event: 'buffer', message: status });
|
package/dist/utils/http.js
CHANGED
|
@@ -98,15 +98,16 @@ class HttpClient {
|
|
|
98
98
|
port
|
|
99
99
|
}, resolve);
|
|
100
100
|
this.socket.on('data', data => {
|
|
101
|
+
const chunk = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
101
102
|
if (!this.pendingResponse) {
|
|
102
103
|
// there is no response pending, parse the data.
|
|
103
|
-
this.parseResponse(
|
|
104
|
+
this.parseResponse(chunk);
|
|
104
105
|
}
|
|
105
106
|
else {
|
|
106
107
|
// incoming data for the pending response.
|
|
107
108
|
const existing = this.pendingResponse.res.body ?? Buffer.alloc(0);
|
|
108
|
-
this.pendingResponse.res.body = Buffer.concat([existing,
|
|
109
|
-
this.pendingResponse.remaining -=
|
|
109
|
+
this.pendingResponse.res.body = Buffer.concat([existing, chunk], chunk.byteLength + existing.byteLength);
|
|
110
|
+
this.pendingResponse.remaining -= chunk.byteLength;
|
|
110
111
|
if (this.pendingResponse.remaining === 0) {
|
|
111
112
|
// all remaining data for the pending response has been read; resolve the promise for the
|
|
112
113
|
// corresponding request.
|
package/package.json
CHANGED