Hi all,
I’m using GStreamer to publish 2 camera streams to a room.
When I join the room on a desktop I see both streams working perfectly without issues.
On my Android device it also works when I join the room whether I use the Chrome browser or when I build it into an APK with Capacitor.
But on budget devices like;
- Honor X9 tablet
- Samsung A52 phone
We retrieve the streams but it only shows one.
When I refresh I randomly only see camera1 or camera2. While my other devices are watching simultaneously they always show both streams.
I’m using the following GStreamer code to stream both videos:
"v4l2src device=/dev/video0 ! video/x-raw,width=960,height=720,framerate=30/1 ! videoconvert ! queue ! x264enc bitrate=1000 byte-stream=false key-int-max=60 threads=0 tune=zerolatency speed-preset=ultrafast ! rtph264pay config-interval=-1 ! queue ! application/x-rtp,media=video,encoding-name=H264"
"v4l2src device=/dev/video1 ! video/x-raw,width=960,height=720,framerate=30/1 ! videoconvert ! queue ! x264enc bitrate=1000 byte-stream=false key-int-max=60 threads=0 tune=zerolatency speed-preset=ultrafast ! rtph264pay config-interval=-1 ! queue ! application/x-rtp,media=video,encoding-name=H264"
We are using the janus-gateway NPM package to spectate a room:
this.janus.attach({
plugin: 'janus.plugin.videoroom',
opaqueId: this.opaqueId,
success: (pluginHandle: JanusJS.PluginHandle) => {
Janus.log('Attached for subscriber', pluginHandle.getId(), roomId);
feedPluginHandle = pluginHandle;
if (type === 'spectator' && userMedia) {
if (this.subscribersRoomHandlesMap.has(roomId)) {
this.subscribersRoomHandlesMap.get(roomId).push(feedPluginHandle);
} else {
this.subscribersRoomHandlesMap.set(roomId, [feedPluginHandle]);
}
this.subscribersHandleMap.set(feedId, feedPluginHandle);
} else if (type === 'external' && this.ownUserMedia) {
this.ownSubscribersRoomHandles.set(feedId, feedPluginHandle);
}
Janus.log(
'Plugin attached! (' + feedPluginHandle.getPlugin() + ', id=' + feedPluginHandle.getId() + ')'
);
Janus.log(' -- This is a subscriber');
feedPluginHandle.send({
message: subscribe,
success: () => {
Janus.log('Subscriber handle success', feedId);
feedPluginHandle.send({
message: {
request: 'subscribe',
streams: [{ feed: feedId }],
},
success: () => {
Janus.log('Subscribed to feed:', feedId);
},
error: (error: any) => {
console.error('Error joining room as subscriber:', error);
},
});
},
error: (error: any) => {
console.error('Error joining room as subscriber:', error);
},
});
},
error: (error: string) => {
console.error(`Error attaching to the room ${roomId}`, error);
},
iceState: (state) => {
Janus.log('ICE state (feed #' + feedId + ') changed to ' + state);
},
webrtcState: (on) => {
Janus.log(
'Janus says this WebRTC PeerConnection (feed #' + feedId + ') is ' + (on ? 'up' : 'down') + ' now'
);
},
slowLink: (uplink, lost, mid) => {
Janus.warn(
'Janus reports problems ' +
(uplink ? 'sending' : 'receiving') +
' packets on mid ' +
mid +
' (' +
lost +
' lost packets)'
);
},
onmessage: (msg, jsep: JanusJS.JSEP) => {
Janus.log('ONMESSAGE-s', msg, jsep);
if (jsep) {
Janus.log('HANDLING JSEP');
this.handleSubscriberJsep(feedPluginHandle, jsep, roomId);
}
},
onremotetrack: (track: DCStreamTrack, mid, on, event: { reason: string }) => {
Janus.log(
'Handle 2: Remote track handler: ' + feedPluginHandle.getId(),
feedPluginHandle.detached,
on,
userMedia
);
Janus.log('Handle 2: Received remote track', track.kind, track, 'from feed', feedId, mid, on, event);
if (!feedPluginHandle.detached) {
if (on) {
track.feedId = feedId;
if (type === 'spectator' && userMedia) {
Janus.log('Binding users userMedia', displayMetaData.facingMode, mid, track.id);
if (track.kind === 'video') {
let forceTrack = true;
if (
isSpectating &&
displayMetaData.facingMode === FacingModes.PLAYER &&
user?.room?.disablePlayerCamForSpectators === true
) {
forceTrack = false;
}
if (forceTrack) {
userMedia.addVideoTrack(track, mid, displayMetaData);
if (record) {
try {
userMedia.startMediaRecorder(displayMetaData.facingMode);
} catch (err) {
console.error(err);
}
}
if (userMedia.remoteEvents$) {
userMedia.remoteEvents$.next(mid);
}
}
} else if (track.kind === 'audio') {
userMedia.addAudioTrack(track, displayMetaData);
}
} else if (type === 'external' && this.ownUserMedia) {
Janus.log('Binding own userMedia', displayMetaData.facingMode, mid, track.id);
if (track.kind === 'video' && !this.isPublishingVideo) {
this.ownUserMedia.addVideoTrack(track, mid, displayMetaData);
if (this.ownUserMedia.remoteEvents$) {
this.ownUserMedia.remoteEvents$.next(mid);
}
} else if (track.kind === 'audio' && !this.isPublishingAudio) {
this.ownUserMedia.addAudioTrack(track, displayMetaData);
}
if (this._janusEventService && this.janus?.isConnected()) {
this._janusEventService.emitEvent({
type: JanusEventType.OwnExternalDeviceConnected,
user: user,
});
}
}
} else {
if (type === 'spectator' && userMedia) {
userMedia.removeTrack(track, displayMetaData);
} else if (type === 'external' && this.ownUserMedia) {
this.ownUserMedia.removeTrack(track, displayMetaData);
}
}
}
if (track.kind === 'video') {
if (type === 'spectator' && userMedia) {
if (
displayMetaData.facingMode === FacingModes.BOARD &&
userMedia.videoStreams?.board?.feedId === feedId
) {
if (displayMetaData.scale) {
userMedia.videoStreams.board.scale = displayMetaData.scale;
}
}
let activeStreams = null;
if (activeStreams !== false) {
activeStreams = userMedia.videoStreams.board?.isActive;
}
if (activeStreams !== false) {
activeStreams = userMedia.videoStreams.player?.isActive;
}
userMedia.videoStreams.activeStreams = activeStreams;
} else if (type === 'external' && this.ownUserMedia) {
if (
displayMetaData.facingMode === FacingModes.BOARD &&
this.ownUserMedia.videoStreams?.board?.feedId === feedId
) {
if (displayMetaData.scale) {
this.ownUserMedia.videoStreams.board.scale = displayMetaData.scale;
}
}
let activeStreams = null;
if (activeStreams !== false) {
activeStreams = this.ownUserMedia.videoStreams.board?.isActive;
}
if (activeStreams !== false) {
activeStreams = this.ownUserMedia.videoStreams.player?.isActive;
}
this.ownUserMedia.videoStreams.activeStreams = activeStreams;
}
if (on) {
if (type === 'spectator' && userMedia) {
userMedia.videoStreams.kind = displayMetaData.kind;
} else if (type === 'external' && this.ownUserMedia) {
this.ownUserMedia.videoStreams.kind = displayMetaData.kind;
}
}
}
},
oncleanup: () => {
Janus.log(' ::: Got a cleanup notification (remote feed ' + feedId + ') :::');
},
});
The addVideoTrack() fuction adds a track to the existing MediaStream.
Again, the streams are working fine on iPhone’s and newer Android phones, so it seems like there is an issue somewhere in our janus-gateaway code.