Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New WebRTC #151

Merged
merged 3 commits into from
Feb 15, 2023
Merged

New WebRTC #151

merged 3 commits into from
Feb 15, 2023

Conversation

joaoantoniocardoso
Copy link
Member

@joaoantoniocardoso joaoantoniocardoso commented Dec 22, 2022

image

image


// Regularly asks for available streams, which will trigger the consumer "on_available_streams" callback
let handler_id: number | undefined = undefined
handler_id = window.setInterval(() => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this interval be ended if the component is removed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great one, interval's life is independent, right? So it will never be collected if we don't clear it... Any suggestion on how to do it properly?

Copy link
Member Author

@joaoantoniocardoso joaoantoniocardoso Dec 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, probably this setInterval will prevent its parent object (this) from being collected, right? 🤦

Copy link
Member Author

@joaoantoniocardoso joaoantoniocardoso Dec 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, maybe all those callbacks (e.g. on_track_added in Session) might need to be set to undefined too for the Session to be collected..? ..Or are they something like weak references and they don't count for the garbage collection thing? @patrickelectric

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I remember correctly, the parent will be collected, but the interval will keep running (the former I'm sure).

Copy link
Member Author

@joaoantoniocardoso joaoantoniocardoso Dec 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After some research and reasoning, my understanding is the following:

  1. this will only be collectible if this arrow function is collectible.
  2. the interval arrow function will only be collectible if id is nullified.
  3. id is not being nullified, even when this ends

To this point, id is never nullified, thus, neither the interval arrow function nor this will be ever collected.

To enable the interval arrow function to be collectible, we might need to:

  1. set id = undefined after every clearInterval(id)
  2. create another condition to make sure that:
    • if this ends, the interval will be cleared and the id will be nullified.

The only ways I see to make it possible is to:

  1. make the id a private property of this and nullify it inside its end() method.
  2. create a private boolean property, like ended, set to false in its end(), and use it inside the arrow function to nullify the id and clear the interval, which seems better

*/
private onTrackAdded(event: RTCTrackEvent): void {
let id: number | undefined = undefined
id = window.setInterval(() => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this interval be ended if the component is removed?

Copy link
Member

@patrickelectric patrickelectric left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not a full review, it's just a tribute.

const addAvailablePeer = (peerId: string, displayName: string): void => {
removeAvailablePeer(peerId)
availablePeers.value.push({ id: peerId, displayName })
const consumer_id: Ref<string | undefined> = ref()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We avoid snake_case when possible.

const rtc_configuration: RTCConfiguration = { iceServers: [] }
const status: Ref<string> = ref('waiting...')

const updateStatus = (new_status: string): void => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We avoid snake_case when possible.


const updateStatus = (new_status: string): void => {
status.value = new_status
console.log(`Status updated: ${new_status}`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably want to add a prefix for the message like webRTC: or something.
That would be more a console.debug than console.log

clearInterval(handler_id)
}

signaller.requestStreams()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this need to be inside the timer inside of a websocket connection ?
Can't we put it totally outside ? That should fix the clearInterval issue.

},
{ once: true }
)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making this a singleton component that will be accessible by video widgets would fix the unmount logic

console.warn(`Socket for session '${sessionId.value}' already undefined.`)
return
const startSession = (producer_id: string): void => {
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check consumer_id before the playing message ?

if (sessionWebSocket.value === undefined) {
resetSocket()
const stream = available_streams.value.find((s) => s.id === producer_id)
console.debug(`stream: ${stream}`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This debug message should be after the if check, the if check message will already provide the necessary information if the value is undefined.

src/libs/webrtc/session.ts Show resolved Hide resolved
@joaoantoniocardoso joaoantoniocardoso marked this pull request as ready for review January 31, 2023 02:27
@joaoantoniocardoso
Copy link
Member Author

joaoantoniocardoso commented Jan 31, 2023

@rafaellehmkuhl @patrickelectric there are two (similar) cases where it's not behaving as expected:

  1. When the cockpit is opened before the mavlink-camera-manager, the signalling connects as soon as the mavlink-camera-manager goes up, but the stream is never negotiated.
  2. When the cockpit is opened and the mavlink-camera-manager restarts, the signalling connects as soon as the mavlink-camera-manager goes up, but the stream is never re-negotiated.

}

if (selectedStream.value === undefined) {
console.debug!('[WebRTC] trying to set stream...')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to not return if the selectedStream is undefined ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, regardless of selectedStream being undefined, when we get a defined savedStream different from selectedStream, we want selectedStream to receive the value of the found savedStream.

This should be enough to guarantee that the saved stream is synched with the available streams and that the selected stream is only be updated to a valid stream. We could, however, want the selectedStream to go back to 'undefined' when we don't find the saved stream in the list of available streams. This change would make the combobox be shown as empty when the saved stream is not available.

if (oldStream !== undefined) {
this.stopSession(msg)
}
if (newStream !== undefined && newStream !== oldStream) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (newStream !== undefined && newStream !== oldStream) {
if (newStream !== undefined) {

The first if of the logic takes care of it

* @param {RTCTrackEvent} event
* @returns {boolean}
*/
private onTrackAdded(event: RTCTrackEvent): boolean {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to return a value at all ? If its always true

this.signaller.requestSessionId(
this.consumerId,
producerId,
(receivedSessionId: string): void => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't better to break it on own its function over a huge lambda ?

src/composables/webRTC.ts Show resolved Hide resolved
* Defines the behavior for the RTCPeerConnection's 'onconnectionstatechange' event
*/
private onConnectionStateChange(): void {
switch (this.peerConnection.connectionState) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should save the state as a variable inside this function to be used for msgs when necessary, avoiding the long property path

const msg = `RTCPeerConnection state changed to ${this.peerConnection.connectionState}`
console.debug('[WebRTC] [Session] ' + msg)

if (this.peerConnection.connectionState == 'failed') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should never happen since state will be disconnected

src/libs/webrtc/session.ts Outdated Show resolved Hide resolved
src/libs/webrtc/session.ts Outdated Show resolved Hide resolved
Comment on lines 272 to 278
// Cleanup event listeners
this.peerConnection.removeEventListener('track', this.onTrackAddedCallback.bind(this))
this.peerConnection.removeEventListener('icecandidate', this.onIceCandidate.bind(this))
this.peerConnection.removeEventListener('iceconnectionstatechange', this.onIceConnectionStateChange.bind(this))
this.peerConnection.removeEventListener('connectionstatechange', this.onConnectionStateChange.bind(this))
this.peerConnection.removeEventListener('signalingstatechange', this.onSignalingStateChange.bind(this))
this.peerConnection.removeEventListener('icegatheringstatechange', this.onIceGatheringStateChange.bind(this))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should encapsulate this logic under a function and use it in end and reconnected

@rafaellehmkuhl
Copy link
Member

rafaellehmkuhl commented Jan 31, 2023

Joao, I'm not able to list streams. Is this because I don't have streams available? Should I create an UDP stream so a webRTC one is created?

image

PS: I have created the following stream and yet I don't have webRTC streams available.

image

@joaoantoniocardoso
Copy link
Member Author

joaoantoniocardoso commented Jan 31, 2023

Joao, I'm not able to list streams. Is this because I don't have streams available? Should I create an UDP stream so a webRTC one is created?

image

PS: I have created the following stream and yet I don't have webRTC streams available.

image

Yes, you need to create a stream, but it needs to be an H264 encoded one. I advise RTSP over UDP 'cause RTSP only uses resources when has a client connected to it, as opposed to UDP, which is always sending packages.

@patrickelectric
Copy link
Member

patrickelectric commented Jan 31, 2023 via email

@rafaellehmkuhl
Copy link
Member

Joao, I'm not able to list streams. Is this because I don't have streams available? Should I create an UDP stream so a webRTC one is created?
image
PS: I have created the following stream and yet I don't have webRTC streams available.
image

Yes, you need to create a stream, but it needs to be an H264 encoded one. I advise RTSP over UDP 'cause RTSP only uses resources when has a client connected to it, as opposed to UDP, which is always sending packages.

Ok. Don't know if a problem with mavlink-camera-manager or this PR, but the stream (UDP) appears, but once connected, give a blank screen. Also, on BlueOS it stays forever on "fetching thumbnail...".

image

image

@patrickelectric
Copy link
Member

Is the thumbnail normal ? I'm having the same problem on BlueOS when using my mac

@rafaellehmkuhl
Copy link
Member

Joao, I'm not able to list streams. Is this because I don't have streams available? Should I create an UDP stream so a webRTC one is created?
image
PS: I have created the following stream and yet I don't have webRTC streams available.
image

Yes, you need to create a stream, but it needs to be an H264 encoded one. I advise RTSP over UDP 'cause RTSP only uses resources when has a client connected to it, as opposed to UDP, which is always sending packages.

Ok. Don't know if a problem with mavlink-camera-manager or this PR, but the stream (UDP) appears, but once connected, give a blank screen. Also, on BlueOS it stays forever on "fetching thumbnail...".

image image

RTSP worked flawlessly.

@rafaellehmkuhl
Copy link
Member

Is the thumbnail normal ? I'm having the same problem on BlueOS when using my mac

No. H264 + UDP gives me no thumbnail and blank stream.

@joaoantoniocardoso
Copy link
Member Author

joaoantoniocardoso commented Feb 7, 2023

I didn't know, but:
image

Is it better to remove the 'autoplay' from the video object?

@rafaellehmkuhl
Copy link
Member

rafaellehmkuhl commented Feb 7, 2023

I didn't know, but: image

Is it better to remove the 'autoplay' from the video object?

Also new to me.

Apparently if the source has no audio, we can autoplay.

So just add a muted attribute to the video widget and it will work.

@rafaellehmkuhl
Copy link
Member

I didn't know, but: image
Is it better to remove the 'autoplay' from the video object?

Also new to me.

Apparently if the source has no audio, we can autoplay.

So just add a muted attribute to the video widget and it will work.

Strange, there is one already. You shouldn't be receiving this warn...

@rafaellehmkuhl
Copy link
Member

I didn't know, but: image
Is it better to remove the 'autoplay' from the video object?

Also new to me.
Apparently if the source has no audio, we can autoplay.
So just add a muted attribute to the video widget and it will work.

Strange, there is one already. You shouldn't be receiving this warn...

Apparently this changes from browser to browser.

@joaoantoniocardoso
Copy link
Member Author

joaoantoniocardoso commented Feb 8, 2023

I didn't know, but: image
Is it better to remove the 'autoplay' from the video object?

Also new to me.
Apparently if the source has no audio, we can autoplay.
So just add a muted attribute to the video widget and it will work.

Strange, there is one already. You shouldn't be receiving this warn...

Apparently this changes from browser to browser.

Yeah, I didn't get why Firefox is complaining, maybe there's a hidden related to WebRTC. We can check if it can autoplay, and create a popup asking the user to click a button that will call the play. Probably in another PR hehe

@joaoantoniocardoso
Copy link
Member Author

joaoantoniocardoso commented Feb 10, 2023

@rafaellehmkuhl @patrickelectric there are two (similar) cases where it's not behaving as expected:

1. When the cockpit is opened before the mavlink-camera-manager, the signalling connects as soon as the mavlink-camera-manager goes up, but the stream is never negotiated.

2. When the cockpit is opened and the mavlink-camera-manager restarts, the signalling connects as soon as the mavlink-camera-manager goes up, but the stream is never re-negotiated.

After some investigation, the (1) is on the signalling server side, and (2) was finally solved here :)

export type Message =
| {
/**
*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This empty comments are a sad thing to see

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, one day, maybe..: Aleph-Alpha/ts-rs#52

Copy link
Member

@patrickelectric patrickelectric left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add the necessary permission request later

bundlePolicy: 'max-bundle',
iceServers: [
{
urls: `turn:blueos.local:3478`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use the same address as the network configuration of cockpit

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or at least allow configuring it somehow

Copy link
Member Author

@joaoantoniocardoso joaoantoniocardoso Feb 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not have addresses in default.ts, the mavlink one should also be contemplated by the solution. Do you think this should be handled in this PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OH, it already has a configuration. I'll do that for these :D

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So just to be sure, you will take the same address from the mainVehicle store and parse it, right?

Copy link
Member Author

@joaoantoniocardoso joaoantoniocardoso Feb 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So just to be sure, you will take the same address from the mainVehicle store and parse it, right?

Nope, I'm modifying the configuration page to allow a global address (defaulted to blueos.local) to build both mainVehicle and webRTC addresses, and also making both customizable:

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect. Was going to suggest that haha

@Williangalvani
Copy link
Member

fixes #209

@joaoantoniocardoso
Copy link
Member Author

Well, it's merged, but we still need to make those custom ips/addresses permanent (saved in the store) and reactive. @rafaellehmkuhl is helping me with it

@patrickelectric
Copy link
Member

Well, it's merged, but we still need to make those custom ips/addresses permanent (saved in the store) and reactive. @rafaellehmkuhl is helping me with it

Yes, I saw that, but merged to get a release since this fixes should not hold the test release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants