fix(webcam): add suport for multiple tabs

This commit is contained in:
João Victor 2024-05-07 18:05:36 -03:00
parent 429c9ba63e
commit 41d70b352a
6 changed files with 64 additions and 28 deletions

View File

@ -667,7 +667,7 @@ LEFT JOIN "user_voice" user_talking ON (
CREATE TABLE "user_camera" (
"streamId" varchar(100) PRIMARY KEY,
"streamId" varchar(150) PRIMARY KEY,
"meetingId" varchar(100),
"userId" varchar(50),
FOREIGN KEY ("meetingId", "userId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE

View File

@ -29,7 +29,7 @@ const VideoStreamAdapter: React.FC<AdapterProps> = ({
const streams = data.user_camera.map(({ streamId, user, voice }) => ({
stream: streamId,
deviceId: streamId.split('_')[2],
deviceId: streamId.split('_')[3],
userId: user.userId,
name: user.name,
nameSortable: user.nameSortable,

View File

@ -66,7 +66,7 @@ const FILTER_VIDEO_STATS = [
];
export const useStatus = () => {
const videoState = useVideoState()[0];
const [videoState] = useVideoState();
if (videoState.isConnecting) return 'videoConnecting';
if (videoState.isConnected) return 'connected';
return 'disconnected';
@ -105,7 +105,7 @@ export const useVideoStreamsCount = () => {
export const useLocalVideoStreamsCount = () => {
const { streams } = useStreams();
const localStreams = streams.filter((vs) => vs.userId === Auth.userID);
const localStreams = streams.filter((vs) => videoService.isLocalStream(vs.stream));
return localStreams.length;
};
@ -308,7 +308,7 @@ export const useStreamUsers = (isGridEnabled: boolean) => {
export const useSharedDevices = () => {
const { streams } = useStreams();
const devices = streams
.filter((s) => s.userId === Auth.userID)
.filter((s) => videoService.isLocalStream(s.stream))
.map((vs) => vs.deviceId);
return devices;
@ -359,13 +359,13 @@ export const useVideoStreams = (
if (connectingStream) streams.push(connectingStream);
if (!viewParticipantsWebcams) {
streams = streams.filter((stream) => stream.userId === Auth.userID);
streams = streams.filter((vs) => videoService.isLocalStream(vs.stream));
}
if (isPaginationEnabled) {
const [filtered, others] = partition(
streams,
(vs: StreamItem) => Auth.userID === vs.userId || (vs.type === 'stream' && vs.pinned),
(vs: StreamItem) => videoService.isLocalStream(vs.stream) || (vs.type === 'stream' && vs.pinned),
);
const [pin, mine] = partition(
filtered,
@ -418,12 +418,17 @@ export const useVideoStreams = (
export const useHasVideoStream = () => {
const { streams } = useStreams();
return streams.some((s) => s.userId === Auth.userID);
return streams.some((s) => videoService.isLocalStream(s.stream));
};
const useOwnVideoStreamsQuery = () => useLazyQuery<OwnVideoStreamsResponse>(
OWN_VIDEO_STREAMS_QUERY,
{ variables: { userId: Auth.userID } },
{
variables: {
userId: Auth.userID,
streamIdPrefix: `${videoService.getPrefix()}%`,
},
},
);
export const useExitVideo = (forceExit = false) => {

View File

@ -65,10 +65,11 @@ export const VIDEO_STREAMS_SUBSCRIPTION = gql`
`;
export const OWN_VIDEO_STREAMS_QUERY = gql`
query OwnVideoStreams($userId: String!) {
query OwnVideoStreams($userId: String!, $streamIdPrefix: String!) {
user_camera(
where: {
userId: { _eq: $userId }
userId: { _eq: $userId },
streamId: { _like: $streamIdPrefix }
},
) {
streamId

View File

@ -1,4 +1,5 @@
import { Session } from 'meteor/session';
import { v4 as uuid } from 'uuid';
import Settings from '/imports/ui/services/settings';
import Auth from '/imports/ui/services/auth';
import Meetings from '/imports/api/meetings';
@ -60,6 +61,8 @@ class VideoService {
private deviceId: string | null = null;
private readonly tabId: string;
constructor() {
this.userParameterProfile = null;
this.isMobile = deviceInfo.isMobile;
@ -67,6 +70,7 @@ class VideoService {
this.numberOfDevices = 0;
this.record = null;
this.hackRecordViewer = null;
this.tabId = uuid();
if (navigator.mediaDevices) {
this.updateNumberOfDevices = this.updateNumberOfDevices.bind(this);
@ -119,7 +123,7 @@ class VideoService {
this.deviceId = deviceId;
Storage.setItem('isFirstJoin', false);
if (!VideoService.isUserLocked()) {
const streamName = VideoService.buildStreamName(Auth.userID ?? '', deviceId);
const streamName = this.buildStreamName(deviceId);
const stream = {
stream: streamName,
userId: Auth.userID ?? '',
@ -142,11 +146,13 @@ class VideoService {
}));
}
static storeDeviceIds(streams: Stream[]) {
storeDeviceIds(streams: Stream[]) {
const deviceIds: string[] = [];
streams.filter((s) => s.userId === Auth.userID).forEach((s) => {
deviceIds.push(s.deviceId);
});
streams
.filter((s) => this.isLocalStream(s.stream))
.forEach((s) => {
deviceIds.push(s.deviceId);
});
Session.set('deviceIds', deviceIds.join());
}
@ -220,8 +226,8 @@ class VideoService {
setConnectingStream(null);
}
static buildStreamName(userId: string, deviceId: string) {
return `${userId}${TOKEN}${deviceId}`;
buildStreamName(deviceId: string) {
return `${this.getPrefix()}${TOKEN}${deviceId}`;
}
static getMediaServerAdapter() {
@ -280,13 +286,16 @@ class VideoService {
&& !isBreakout);
}
static getMyStreamId(deviceId: string, streams: Stream[]) {
const videoStream = streams.find((vs) => vs.userId === Auth.userID && vs.deviceId === deviceId);
getMyStreamId(deviceId: string, streams: Stream[]) {
const videoStream = streams.find(
(vs) => this.isLocalStream(vs.stream)
&& vs.deviceId === deviceId,
);
return videoStream ? videoStream.stream : null;
}
static isLocalStream(cameraId: string) {
return !!Auth.userID && cameraId?.startsWith(Auth.userID);
isLocalStream(cameraId = '') {
return cameraId.startsWith(this.getPrefix());
}
static getCameraProfile() {
@ -492,6 +501,14 @@ class VideoService {
});
});
}
getTabId() {
return this.tabId;
}
getPrefix() {
return `${Auth.userID}${TOKEN}${this.tabId}`;
}
}
const videoService = new VideoService();
@ -499,7 +516,7 @@ const videoService = new VideoService();
export default {
addCandidateToPeer: VideoService.addCandidateToPeer,
getInfo: VideoService.getInfo,
getMyStreamId: VideoService.getMyStreamId,
getMyStreamId: videoService.getMyStreamId.bind(videoService),
getAuthenticatedURL: VideoService.getAuthenticatedURL,
getRole: VideoService.getRole,
getMediaServerAdapter: VideoService.getMediaServerAdapter,
@ -509,11 +526,11 @@ export default {
getNextVideoPage: VideoService.getNextVideoPage,
getCurrentVideoPageIndex: VideoService.getCurrentVideoPageIndex,
isVideoPinEnabledForCurrentUser: VideoService.isVideoPinEnabledForCurrentUser,
isLocalStream: VideoService.isLocalStream,
isLocalStream: videoService.isLocalStream.bind(videoService),
isPaginationEnabled: VideoService.isPaginationEnabled,
mirrorOwnWebcam: VideoService.mirrorOwnWebcam,
processInboundIceQueue: VideoService.processInboundIceQueue,
storeDeviceIds: VideoService.storeDeviceIds,
storeDeviceIds: videoService.storeDeviceIds.bind(videoService),
joinedVideo: () => VideoService.joinedVideo(),
exitedVideo: () => videoService.exitedVideo(),
getPreloadedStream: () => videoService.getPreloadedStream(),
@ -536,4 +553,6 @@ export default {
{ leading: false, trailing: true },
),
setTrackEnabled: (value: boolean) => videoService.setTrackEnabled(value),
getTabId: videoService.getTabId.bind(videoService),
getPrefix: videoService.getPrefix.bind(videoService),
};

View File

@ -1,6 +1,7 @@
import { StreamItem } from './types';
import UserListService from '/imports/ui/components/user-list/service';
import Auth from '/imports/ui/services/auth';
import VideoService from './service';
const DEFAULT_SORTING_MODE = 'LOCAL_ALPHABETICAL';
@ -52,15 +53,25 @@ export const sortVoiceActivityLocal = (s1: StreamItem, s2: StreamItem) => {
|| UserListService.sortUsersByName(s1, s2);
};
export const sortByLocal = (s1: StreamItem, s2: StreamItem) => {
if (VideoService.isLocalStream(s1.stream)) {
return -1;
} if (VideoService.isLocalStream(s2.stream)) {
return 1;
}
return 0;
};
// pin -> local -> lastFloorTime (descending) -> alphabetical
export const sortLocalVoiceActivity = (s1: StreamItem, s2: StreamItem) => mandatorySorting(s1, s2)
|| UserListService.sortUsersByCurrent(s1, s2)
|| sortByLocal(s1, s2)
|| sortVoiceActivity(s1, s2)
|| UserListService.sortUsersByName(s1, s2);
// pin -> local -> alphabetic
export const sortLocalAlphabetical = (s1: StreamItem, s2: StreamItem) => mandatorySorting(s1, s2)
|| UserListService.sortUsersByCurrent(s1, s2)
|| sortByLocal(s1, s2)
|| UserListService.sortUsersByName(s1, s2);
export const sortPresenter = (s1: StreamItem, s2: StreamItem) => {
@ -75,7 +86,7 @@ export const sortPresenter = (s1: StreamItem, s2: StreamItem) => {
// pin -> local -> presenter -> alphabetical
export const sortLocalPresenterAlphabetical = (s1: StreamItem, s2: StreamItem) => mandatorySorting(s1, s2)
|| UserListService.sortUsersByCurrent(s1, s2)
|| sortByLocal(s1, s2)
|| sortPresenter(s1, s2)
|| UserListService.sortUsersByName(s1, s2);