-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathring.js
More file actions
145 lines (121 loc) · 3.84 KB
/
ring.js
File metadata and controls
145 lines (121 loc) · 3.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import { RingApi } from 'ring-client-api'
import { EventEmitter } from 'events'
import { getRefreshTokenFromEnv, updateRefreshTokenInEnv } from './util.js'
import RtpSequencer from './rtp-sequencer.js'
const {
CAMERA_NAME
} = process.env
class Ring extends EventEmitter {
constructor() {
super()
this.ringApi = null
this.currentCall = null
this.camera = null
this.sip = null
this.initiatingCall = false
this.receivingAudio = false
this.rtpSequencer = new RtpSequencer()
this.battery = null
this.connected = false
}
// 1) Initialize the Ring API
initialize() {
const refreshToken = getRefreshTokenFromEnv()
this.ringApi = new RingApi({ refreshToken, cameraStatusPollingSeconds: 600, debug: true })
this.ringApi.onRefreshTokenUpdated.subscribe(async ({ newRefreshToken }) => {
console.log('RING - Refresh token updated')
updateRefreshTokenInEnv(newRefreshToken)
})
return this.ringApi.getCameras().then((cameras) => {
if (!cameras.length) {
return reject(new Error('No cameras found in the location.'))
}
for (let camera of cameras) {
if (camera.isDoorbot && camera.name == CAMERA_NAME) {
this.camera = camera
console.log(`Attaching button listener to ${camera.name}`)
}
}
})
}
// 2) Initiate a live call
initiateCall() {
if (this.initiatingCall) return
this.initiatingCall = true
return new Promise(async (resolve, reject) => {
if (!this.ringApi) {
return reject(new Error('Ring not initialized. Call initialize() first!'))
}
try {
console.log(`RING - Starting live call on camera: ${this.camera.name}`)
const call = await this.camera.startLiveCall()
this.currentCall = call
// Listen for call ended
call.onCallEnded.subscribe(() => {
console.log('RING - Call ended')
this.emit('callEnded')
})
// Listen for call answered
call.connection.onCallAnswered.subscribe((sdp) => {
console.log('RING - Call answered, SDP received')
this.emit('callEstablished')
})
// Start playing ringback for demonstration
call.activateCameraSpeaker()
// Listen for audio RTP
call.connection.onAudioRtp.subscribe((rtpPacket) => {
if (!this.receivingAudio) {
this.receivingAudio = true
this.emit('receivingAudio')
}
if (this.sip) {
this.sip.sendAudioPacket(rtpPacket, false)
}
})
// We’ve initiated the call
resolve()
} catch (err) {
console.error('RING - Error initiating call:', err)
reject(err)
}
})
}
listen() {
this.camera.onDoorbellPressed.subscribe(() => {
console.log(`RING - Button pressed`)
this.emit('buttonPressed', this.camera)
});
this.camera.onData.subscribe((data) => {
this.battery = data.health.battery_percentage
this.connected = data.health.connected
console.log('RING - Battery', this.battery)
})
}
sendAudioPacket(rtp, isTone = false) {
// If we haven't configured a destination, do nothing
if (!this.currentCall) return
// Use the utility to decide if we drop or forward
const shouldForward = this.rtpSequencer.process(rtp, isTone)
if (!shouldForward) return
this.currentCall.sendAudioPacket(rtp)
}
pipeAudio(sip) {
this.sip = sip
}
getBattery() {
return this.battery
}
isConnected() {
return this.connected
}
// 4) End the Ring call
cleanup() {
if (this.currentCall) {
console.log('RING - Stopping the live call...')
this.currentCall.stop()
this.currentCall = null
}
}
}
// Export a singleton instance
export const ring = new Ring()