Files
ring-mqtt/lib/streaming/peer-connection.js
tsightler b8338e30de Release 5.1.0 (#537)
* 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
2023-02-02 20:59:09 -05:00

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()
}
}