Skip to content

Commit

Permalink
WIP: Implement a simple exclusive filter by IP address
Browse files Browse the repository at this point in the history
  • Loading branch information
joaoantoniocardoso committed Nov 16, 2023
1 parent db88ade commit 731f5bf
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/components/mini-widgets/MiniVideoRecorder.vue
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ watch(externalStreams, () => {
// Retrieve stream from the saved stream name, otherwise choose the first available stream as a fallback
const savedStream = savedStreamName ? availableStreams.value.find((s) => s.name === savedStreamName) : undefined
if (savedStream !== undefined && savedStream !== selectedStream.value && selectedStream.value === undefined) {
if (savedStream !== undefined && savedStream.id !== selectedStream.value?.id && selectedStream.value === undefined) {
console.debug!('[WebRTC] trying to set stream...')
updateCurrentStream(savedStream)
}
Expand Down
53 changes: 51 additions & 2 deletions src/components/widgets/VideoPlayer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@
hide-details
return-object
/>
<v-text-field
v-model="selectedICEIPs"
label="Selected IP Address"
class="uri-input my-3"
validate-on="lazy input"
density="compact"
variant="outlined"
type="input"
hint="IP Address of the Vehicle to be used for the WebRTC ICE Routing, usually, the IP of the tether/cabled interface. Blank means any route. E.g: 192.168.2.2"
:rules="[isValidHostAddress]"
/>
<v-banner-text>Saved stream name: "{{ widget.options.streamName }}"</v-banner-text>
<v-banner-text>Signaller Status: {{ signallerStatus }}</v-banner-text>
<v-banner-text>Stream Status: {{ streamStatus }}</v-banner-text>
Expand Down Expand Up @@ -66,6 +77,30 @@ import type { Stream } from '@/libs/webrtc/signalling_protocol'
import { useMainVehicleStore } from '@/stores/mainVehicle'
import type { Widget } from '@/types/widgets'
// FIXME: Why can't I just import a property from another component?
// import isValidHostAddress from '@/views/ConfigurationGeneralView.vue'
const isValidHostAddress = (value: string): boolean | string => {
if (value.length >= 255) {
return 'Address is too long'
}
// Regexes from https://stackoverflow.com/a/106223/3850957
const ipRegex = new RegExp(
'^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$'
)
const hostnameRegex = new RegExp(
'^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$'
)
if (ipRegex.test(value) || hostnameRegex.test(value)) {
return true
}
return 'Invalid host address. Should be an IP address or a hostname'
}
const mainVehicleStore = useMainVehicleStore()
const { rtcConfiguration, webRTCSignallingURI } = useMainVehicleStore()
console.debug('[WebRTC] Using webrtc-adapter for', adapter.browserDetails)
Expand All @@ -79,10 +114,15 @@ const props = defineProps<{
const widget = toRefs(props).widget
const selectedICEIPsField = ref<string | undefined>()
const selectedICEIPs = ref<string | undefined>()
const selectedStream = ref<Stream | undefined>()
const videoElement = ref<HTMLVideoElement | undefined>()
const webRTCManager = new WebRTCManager(webRTCSignallingURI.val, rtcConfiguration)
const { availableStreams, mediaStream, signallerStatus, streamStatus } = webRTCManager.startStream(selectedStream)
const { availableStreams, mediaStream, signallerStatus, streamStatus } = webRTCManager.startStream(
selectedStream,
selectedICEIPs
)
onBeforeMount(() => {
// Set initial widget options if they don't exist
Expand Down Expand Up @@ -119,6 +159,15 @@ watch(mediaStream, async (newStream, oldStream) => {
})
})
watch(selectedICEIPsField, async (oldAddr, newAddr) => {
if (!newAddr || isValidHostAddress(newAddr)) {
return
}
selectedICEIPs.value = selectedICEIPsField.value
})
watch(selectedStream, () => (widget.value.options.streamName = selectedStream.value?.name))
watch(availableStreams, () => {
Expand All @@ -133,7 +182,7 @@ watch(availableStreams, () => {
? availableStreams.value.find((s) => s.name === savedStreamName)
: availableStreams.value.first()
if (savedStream !== undefined && savedStream !== selectedStream.value) {
if (savedStream !== undefined && savedStream.id !== selectedStream.value?.id) {
console.debug!('[WebRTC] trying to set stream...')
selectedStream.value = savedStream
}
Expand Down
36 changes: 35 additions & 1 deletion src/composables/webRTC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class WebRTCManager {
private streamName: string | undefined
private session: Session | undefined
private rtcConfiguration: RTCConfiguration
private selectedICEIPs: string | undefined

private hasEnded = false
private signaller: Signaller
Expand All @@ -51,6 +52,7 @@ export class WebRTCManager {
*
* @param {Connection.URI} webRTCSignallingURI
* @param {RTCConfiguration} rtcConfiguration
* @param {string[]} selectedICEIPs
*/
constructor(webRTCSignallingURI: Connection.URI, rtcConfiguration: RTCConfiguration) {
console.debug('[WebRTC] Trying to connect to signalling server.')
Expand Down Expand Up @@ -78,9 +80,13 @@ export class WebRTCManager {
/**
*
* @param { Ref<Stream | undefined> } selectedStream - Stream to receive stream from
* @param { Ref<string> } selectedICEIPs
* @returns { startStreamReturn }
*/
public startStream(selectedStream: Ref<Stream | undefined>): startStreamReturn {
public startStream(
selectedStream: Ref<Stream | undefined>,
selectedICEIPs: Ref<string | undefined>
): startStreamReturn {
watch(selectedStream, (newStream, oldStream) => {
if (newStream?.id === oldStream?.id) {
return
Expand All @@ -97,6 +103,28 @@ export class WebRTCManager {
}
})

watch(selectedICEIPs, (newIp, oldIp) => {
if (newIp === oldIp) {
return
}

const msg = `Selected IP changed from "${oldIp}" to "${newIp}".`
console.debug('[WebRTC] ' + msg)

this.selectedICEIPs = newIp

if (this.streamName !== undefined) {
this.stopSession(msg)
}

if (this.streamName !== undefined) {
this.startSession()
}
})

// FIXME: I want to assign this.selectedICEIPs when startStream is first called
// this.selectedICEIPs = selectedICEIPs.value

return {
availableStreams: this.availableStreams,
mediaStream: this.mediaStream,
Expand Down Expand Up @@ -294,13 +322,19 @@ export class WebRTCManager {
* @param {string} receivedSessionId
*/
private onSessionIdReceived(stream: Stream, producerId: string, receivedSessionId: string): void {
const selectedICEIPs = []
if (this.selectedICEIPs) {
selectedICEIPs.push(this.selectedICEIPs)
}

// Create a new Session with the received Session ID
this.session = new Session(
receivedSessionId,
this.consumerId!,
stream,
this.signaller,
this.rtcConfiguration,
selectedICEIPs,
(event: RTCTrackEvent): void => this.onTrackAdded(event),
(_sessionId, reason) => this.onSessionClosed(reason)
)
Expand Down
10 changes: 10 additions & 0 deletions src/libs/webrtc/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class Session {
private ended: boolean
private signaller: Signaller
private peerConnection: RTCPeerConnection
private selectedICEIPs: string[]
public rtcConfiguration: RTCConfiguration
public onTrackAdded?: OnTrackAddedCallback
public onClose?: OnCloseCallback
Expand All @@ -27,6 +28,7 @@ export class Session {
* @param {Stream} stream - The Stream instance for which this Session will be created with, given by the signalling server
* @param {Signaller} signaller - The Signaller instance for this Session to use
* @param {RTCConfiguration} rtcConfiguration - Configuration for the RTC connection, such as Turn and Stun servers
* @param {string[]} selectedICEIPs - A whitelist for ICE IP addresses, ignored if empty
* @param {OnTrackAddedCallback} onTrackAdded - An optional callback for when a track is added to this session
* @param {OnCloseCallback} onClose - An optional callback for when this session closes
*/
Expand All @@ -36,6 +38,7 @@ export class Session {
stream: Stream,
signaller: Signaller,
rtcConfiguration: RTCConfiguration,
selectedICEIPs: string[] = [],
onTrackAdded?: OnTrackAddedCallback,
onClose?: OnCloseCallback
) {
Expand All @@ -48,6 +51,7 @@ export class Session {
this.signaller = signaller
this.rtcConfiguration = rtcConfiguration
this.ended = false
this.selectedICEIPs = selectedICEIPs

this.peerConnection = this.createRTCPeerConnection(rtcConfiguration)

Expand Down Expand Up @@ -165,6 +169,12 @@ export class Session {
* @param {RTCIceCandidateInit} candidate - The ICE candidate received from the signalling server
*/
public onIncomingICE(candidate: RTCIceCandidateInit): void {
// Ignores unwanted routes, useful, for example, to prevent WebRTC to chose the wrong route, like when the OS default is WiFi but you want to receive the video via tether because of reliability
if (candidate.candidate && !this.selectedICEIPs.some((address) => candidate.candidate!.includes(address))) {
console.debug(`[WebRTC] [Session] ICE candidate ignored: ${JSON.stringify(candidate, null, 4)}`)
return
}

this.peerConnection
.addIceCandidate(candidate)
.then(() => console.debug(`[WebRTC] [Session] ICE candidate added: ${JSON.stringify(candidate, null, 4)}`))
Expand Down

0 comments on commit 731f5bf

Please sign in to comment.