Skip to content

Commit

Permalink
Join spaces, mute/unmute, global chatEvents. Before cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
slkzgm committed Jan 2, 2025
1 parent 89cb1f4 commit 048fc65
Show file tree
Hide file tree
Showing 7 changed files with 982 additions and 49 deletions.
10 changes: 10 additions & 0 deletions src/spaces/core/ChatClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,16 @@ export class ChatClient extends EventEmitter {
muted: false,
});
}

if (body.guestBroadcastingEvent === 12) {
this.emit('newSpeakerAccepted', {
userId: body.guestRemoteID,
username: body.guestUsername,
sessionUUID: body.sessionUUID,
});
}

// TODO: 9 Became speaker?
// Example of guest reaction
if (body?.type === 2) {
this.logger.info('[ChatClient] Emitting guest reaction event =>', body);
Expand Down
108 changes: 90 additions & 18 deletions src/spaces/core/JanusClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,34 +75,105 @@ export class JanusClient extends EventEmitter {
this.logger.info('[JanusClient] Initialization complete');
}

public async subscribeSpeaker(userId: string): Promise<void> {
this.logger.debug('[JanusClient] subscribeSpeaker => userId=', userId);
public async initializeGuestSpeaker() {
this.logger.debug('[JanusClient] initializeGuestSpeaker() called');

const subscriberHandleId = await this.attachPlugin();
this.logger.debug('[JanusClient] subscriber handle =>', subscriberHandleId);
// 1) createSession()
this.sessionId = await this.createSession();
this.handleId = await this.attachPlugin(); // publisher handle
this.pollActive = true;
this.startPolling();

const publishersEvt = await this.waitForJanusEvent(
// 2) "joinRoom" but as "publisher" with request=join
// Note: we do NOT "createRoom()" because the room already exists.
const evtPromise = this.waitForJanusEvent(
(e) =>
e.janus === 'event' &&
e.plugindata?.plugin === 'janus.plugin.videoroom' &&
e.plugindata?.data?.videoroom === 'event' &&
Array.isArray(e.plugindata?.data?.publishers) &&
e.plugindata?.data?.publishers.length > 0,
8000,
'discover feed_id from "publishers"',
e.plugindata?.data?.videoroom === 'joined',
10000,
'Guest Speaker joined event',
);

const list = publishersEvt.plugindata.data.publishers as any[];
const pub = list.find(
(p) => p.display === userId || p.periscope_user_id === userId,
const body = {
request: 'join',
room: this.config.roomId,
ptype: 'publisher',
display: this.config.userId,
periscope_user_id: this.config.userId,
};

await this.sendJanusMessage(this.handleId, body);
const evt = await evtPromise;

const data = evt.plugindata?.data;
this.publisherId = data.id; // Our own publisherId
this.logger.debug(
'[JanusClient] guest joined => publisherId=',
this.publisherId,
);
if (!pub) {
throw new Error(
`[JanusClient] subscribeSpeaker => No publisher found for userId=${userId}`,

const publishers = data.publishers || [];
this.logger.debug('[JanusClient] existing publishers =>', publishers);

// 3) create PeerConnection (with turn servers)
this.pc = new RTCPeerConnection({
iceServers: [
{
urls: this.config.turnServers.uris,
username: this.config.turnServers.username,
credential: this.config.turnServers.password,
},
],
});
this.setupPeerEvents();

// 4) enableLocalAudio() or whatever you want
this.enableLocalAudio();

// 5) configurePublisher => sends an Offer, waits for Answer
await this.configurePublisher();

await Promise.all(
publishers.map((pub: any) => this.subscribeSpeaker(pub.display, pub.id)),
);

this.logger.info('[JanusClient] Guest speaker negotiation complete');
}

public async subscribeSpeaker(
userId: string,
feedId: number = 0,
): Promise<void> {
this.logger.debug('[JanusClient] subscribeSpeaker => userId=', userId);

const subscriberHandleId = await this.attachPlugin();
this.logger.debug('[JanusClient] subscriber handle =>', subscriberHandleId);

if (feedId === 0) {
const publishersEvt = await this.waitForJanusEvent(
(e) =>
e.janus === 'event' &&
e.plugindata?.plugin === 'janus.plugin.videoroom' &&
e.plugindata?.data?.videoroom === 'event' &&
Array.isArray(e.plugindata?.data?.publishers) &&
e.plugindata?.data?.publishers.length > 0,
8000,
'discover feed_id from "publishers"',
);

const list = publishersEvt.plugindata.data.publishers as any[];
const pub = list.find(
(p) => p.display === userId || p.periscope_user_id === userId,
);
if (!pub) {
throw new Error(
`[JanusClient] subscribeSpeaker => No publisher found for userId=${userId}`,
);
}
feedId = pub.id;
this.logger.debug('[JanusClient] found feedId =>', feedId);
}
const feedId = pub.id;
this.logger.debug('[JanusClient] found feedId =>', feedId);
this.emit('subscribedSpeaker', { userId, feedId });

const joinBody = {
Expand All @@ -118,6 +189,7 @@ export class JanusClient extends EventEmitter {
},
],
};

await this.sendJanusMessage(subscriberHandleId, joinBody);

const attachedEvt = await this.waitForJanusEvent(
Expand Down
112 changes: 89 additions & 23 deletions src/spaces/core/Space.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import {
publishBroadcast,
authorizeToken,
getRegion,
muteSpeaker,
unmuteSpeaker,
setupCommonChatEvents,
} from '../utils';
import type {
BroadcastCreated,
SpeakerRequest,
OccupancyUpdate,
GuestReaction,
Plugin,
AudioDataWithUser,
PluginRegistration,
Expand Down Expand Up @@ -180,26 +180,7 @@ export class Space extends EventEmitter {

private setupChatEvents() {
if (!this.chatClient) return;

this.chatClient.on('speakerRequest', (req: SpeakerRequest) => {
this.logger.info('[Space] Speaker request =>', req);
this.emit('speakerRequest', req);
});

this.chatClient.on('occupancyUpdate', (update: OccupancyUpdate) => {
this.logger.debug('[Space] occupancyUpdate =>', update);
this.emit('occupancyUpdate', update);
});

this.chatClient.on('muteStateChanged', (evt) => {
this.logger.debug('[Space] muteStateChanged =>', evt);
this.emit('muteStateChanged', evt);
});

this.chatClient.on('guestReaction', (reaction: GuestReaction) => {
this.logger.info('[Space] Guest reaction =>', reaction);
this.emit('guestReaction', reaction);
});
setupCommonChatEvents(this.chatClient, this.logger, this);
}

/**
Expand Down Expand Up @@ -468,6 +449,91 @@ export class Space extends EventEmitter {
return Array.from(this.speakers.values());
}

/**
* Mute the host himself (i.e. you).
* session_uuid = "" for the host calls.
*/
public async muteHost() {
if (!this.authToken) {
throw new Error('[Space] No auth token available');
}
if (!this.broadcastInfo) {
throw new Error('[Space] No broadcastInfo');
}

await muteSpeaker({
broadcastId: this.broadcastInfo.room_id,
sessionUUID: '', // host => empty
chatToken: this.broadcastInfo.access_token,
authToken: this.authToken,
});
this.logger.info('[Space] Host muted successfully.');
}

/**
* Unmute the host
*/
public async unmuteHost() {
if (!this.authToken) throw new Error('[Space] No auth token');
if (!this.broadcastInfo) throw new Error('[Space] No broadcastInfo');

await unmuteSpeaker({
broadcastId: this.broadcastInfo.room_id,
sessionUUID: '',
chatToken: this.broadcastInfo.access_token,
authToken: this.authToken,
});
this.logger.info('[Space] Host unmuted successfully.');
}

/**
* Mute a specific speaker (by userId).
* We'll look up his sessionUUID in our 'speakers' map.
*/
public async muteSpeaker(userId: string) {
if (!this.authToken) {
throw new Error('[Space] No auth token available');
}
if (!this.broadcastInfo) {
throw new Error('[Space] No broadcastInfo');
}

const speaker = this.speakers.get(userId);
if (!speaker) {
throw new Error(`[Space] Speaker not found for userId=${userId}`);
}

await muteSpeaker({
broadcastId: this.broadcastInfo.room_id,
sessionUUID: speaker.sessionUUID,
chatToken: this.broadcastInfo.access_token,
authToken: this.authToken,
});
this.logger.info(`[Space] Muted speaker => userId=${userId}`);
}

public async unmuteSpeaker(userId: string) {
if (!this.authToken) {
throw new Error('[Space] No auth token available');
}
if (!this.broadcastInfo) {
throw new Error('[Space] No broadcastInfo');
}

const speaker = this.speakers.get(userId);
if (!speaker) {
throw new Error(`[Space] Speaker not found for userId=${userId}`);
}

await unmuteSpeaker({
broadcastId: this.broadcastInfo.room_id,
sessionUUID: speaker.sessionUUID,
chatToken: this.broadcastInfo.access_token,
authToken: this.authToken,
});
this.logger.info(`[Space] Unmuted speaker => userId=${userId}`);
}

public async stop() {
this.logger.info('[Space] Stopping...');

Expand Down
Loading

0 comments on commit 048fc65

Please sign in to comment.