mirror of
https://github.com/tsightler/ring-mqtt.git
synced 2025-09-26 21:01:12 +08:00

* Use MQTT for start-stream debug messages * Fix ANSI colors * Refactor event URL management * Fix subscription detection * Improve event URL expiry handling by parsing Amazon S3 expire time * Convert to ESM/replace colors with chalk * Force colors for chalk * Migrate to ESM * Fix stop of keepalive stream * Add transcoded event selections * Update event URL on raw/trancoded toggle * Switch to per-camera livecall threads * Customized WebRTC functions Mostly copied from ring-client-api with port to pure Javascript, removal of unneeded features and additional debugging modified for use as worker thread with ring-mqtt. Allows easier testing with updated Werift versions. * Add nightlight enable/disable * Include nightlight state as attribute * Only pro versions have nightlight * Tweak battery level reporting for dual battery cameras * Release 5.1.0
143 lines
4.8 KiB
JavaScript
143 lines
4.8 KiB
JavaScript
// This code is largely copied from ring-client-api, but converted from Typescript
|
|
// to straight Javascript and some code not required for ring-mqtt removed.
|
|
// Much thanks to @dgreif for the original code which is the basis for this work.
|
|
|
|
import { RTCPeerConnection, RTCRtpCodecParameters } from 'werift'
|
|
import { interval, merge, ReplaySubject, Subject } from 'rxjs'
|
|
import { Subscribed } from './subscribed.js'
|
|
|
|
const ringIceServers = [
|
|
'stun:stun.kinesisvideo.us-east-1.amazonaws.com:443',
|
|
'stun:stun.kinesisvideo.us-east-2.amazonaws.com:443',
|
|
'stun:stun.kinesisvideo.us-west-2.amazonaws.com:443',
|
|
'stun:stun.l.google.com:19302',
|
|
'stun:stun1.l.google.com:19302',
|
|
'stun:stun2.l.google.com:19302',
|
|
'stun:stun3.l.google.com:19302',
|
|
'stun:stun4.l.google.com:19302',
|
|
]
|
|
|
|
export class WeriftPeerConnection extends Subscribed {
|
|
constructor() {
|
|
super()
|
|
this.onAudioRtp = new Subject()
|
|
this.onVideoRtp = new Subject()
|
|
this.onIceCandidate = new Subject()
|
|
this.onConnectionState = new ReplaySubject(1)
|
|
this.onRequestKeyFrame = new Subject()
|
|
const pc = (this.pc = new RTCPeerConnection({
|
|
codecs: {
|
|
audio: [
|
|
new RTCRtpCodecParameters({
|
|
mimeType: 'audio/opus',
|
|
clockRate: 48000,
|
|
channels: 2,
|
|
}),
|
|
new RTCRtpCodecParameters({
|
|
mimeType: 'audio/PCMU',
|
|
clockRate: 8000,
|
|
channels: 1,
|
|
payloadType: 0,
|
|
}),
|
|
],
|
|
video: [
|
|
new RTCRtpCodecParameters({
|
|
mimeType: 'video/H264',
|
|
clockRate: 90000,
|
|
rtcpFeedback: [
|
|
{ type: 'transport-cc' },
|
|
{ type: 'ccm', parameter: 'fir' },
|
|
{ type: 'nack' },
|
|
{ type: 'nack', parameter: 'pli' },
|
|
{ type: 'goog-remb' },
|
|
],
|
|
parameters: 'packetization-mode=1;profile-level-id=640029;level-asymmetry-allowed=1',
|
|
}),
|
|
],
|
|
},
|
|
iceServers: ringIceServers.map((server) => ({ urls: server })),
|
|
iceTransportPolicy: 'all',
|
|
bundlePolicy: 'balanced'
|
|
}))
|
|
|
|
const audioTransceiver = pc.addTransceiver('audio', {
|
|
direction: 'recvonly',
|
|
})
|
|
|
|
const videoTransceiver = pc.addTransceiver('video', {
|
|
direction: 'recvonly',
|
|
})
|
|
|
|
audioTransceiver.onTrack.subscribe((track) => {
|
|
track.onReceiveRtp.subscribe((rtp) => {
|
|
this.onAudioRtp.next(rtp)
|
|
})
|
|
})
|
|
|
|
videoTransceiver.onTrack.subscribe((track) => {
|
|
track.onReceiveRtp.subscribe((rtp) => {
|
|
this.onVideoRtp.next(rtp)
|
|
})
|
|
track.onReceiveRtp.once(() => {
|
|
// debug('received first video packet')
|
|
this.addSubscriptions(merge(this.onRequestKeyFrame, interval(4000)).subscribe(() => {
|
|
videoTransceiver.receiver
|
|
.sendRtcpPLI(track.ssrc)
|
|
.catch((e) => {
|
|
// debug(e)
|
|
})
|
|
}))
|
|
this.requestKeyFrame()
|
|
})
|
|
})
|
|
|
|
this.pc.onIceCandidate.subscribe((iceCandidate) => {
|
|
this.onIceCandidate.next(iceCandidate)
|
|
})
|
|
|
|
pc.iceConnectionStateChange.subscribe(() => {
|
|
// debug(`iceConnectionStateChange: ${pc.iceConnectionState}`)
|
|
if (pc.iceConnectionState === 'closed') {
|
|
this.onConnectionState.next('closed')
|
|
}
|
|
})
|
|
|
|
pc.connectionStateChange.subscribe(() => {
|
|
// debug(`connectionStateChange: ${pc.connectionState}`)
|
|
this.onConnectionState.next(pc.connectionState)
|
|
})
|
|
}
|
|
|
|
async createOffer() {
|
|
const offer = await this.pc.createOffer()
|
|
await this.pc.setLocalDescription(offer)
|
|
return offer
|
|
}
|
|
|
|
async createAnswer(offer) {
|
|
await this.pc.setRemoteDescription(offer)
|
|
const answer = await this.pc.createAnswer()
|
|
await this.pc.setLocalDescription(answer)
|
|
return answer
|
|
}
|
|
|
|
async acceptAnswer(answer) {
|
|
await this.pc.setRemoteDescription(answer)
|
|
}
|
|
|
|
addIceCandidate(candidate) {
|
|
return this.pc.addIceCandidate(candidate)
|
|
}
|
|
|
|
requestKeyFrame() {
|
|
this.onRequestKeyFrame.next()
|
|
}
|
|
|
|
close() {
|
|
this.pc.close().catch((e) => {
|
|
//debug
|
|
})
|
|
this.unsubscribe()
|
|
}
|
|
}
|