bland-baker-98506
09/19/2025, 3:46 PMtall-airline-16756
09/19/2025, 3:46 PMtall-airline-16756
09/19/2025, 3:47 PMlet room = await room.connect(url, token, { autoSubscribe: false });
2. Set Track Subscription Permissions: Next, you can use the setTrackSubscriptionPermissions method on the LocalParticipant to specify exactly who can subscribe:
localParticipant.setTrackSubscriptionPermissions(
false,
[new ParticipantTrackPermission({ participantSid: "allowed-sid", allTracksAllowed: true })]
);
If you're thinking about controlling this based on roles, a little tip is to map roles to participant identities or SIDs within your app's logic. Then, you can set permissions accordingly since LiveKit doesn’t directly link roles with subscription permissions. I hope this helps clarify things—feel free to reach out if you want to dive deeper or need more guidance!
Sources: Subscribing to tracks | LiveKit Docs | Rooms, participants, and tracks | LiveKit Docs | Camera & microphone | LiveKit Docs | livekit-android-sdk/src/main/java/io/livekit/android/room/participant/Local... | localparticipant.gobland-baker-98506
09/19/2025, 3:49 PMtall-airline-16756
09/19/2025, 3:49 PMallParticipantsAllowed to false. This stops automatic subscriptions for all participants.
2. Specify Allowed Participants: Next, use the setTrackSubscriptionPermissions method to explicitly allow only certain participants to subscribe. For example:
localParticipant.setTrackSubscriptionPermissions(
false,
[new ParticipantTrackPermission({ participantSid: "allowed-sid", allTracksAllowed: true })]
);
Since LiveKit doesn’t directly link roles with subscription permissions, you’ll want to map roles like "team" and "broadcaster" to participant identities or SIDs within your application logic. Then, you can use those identities to set up the subscription permissions just the way you need them.
Sources: Camera & microphone | LiveKit Docs | localparticipant.go | livekit-android-sdk/src/main/java/io/livekit/android/room/participant/Local...bland-baker-98506
09/19/2025, 3:50 PMDevice change error for ${kind}:, err);
}
};
const handleDisconnect = () => {
setShouldConnect(false);
setIsDisconnected(true);
setShowDisconnectedModal(true);
// Perform redirect based on role
if (isViewer) {
window.location.href = "/watch/thank-you"; // Redirect viewers/guests to the homepage
} else if (isTeam) {
window.location.href = `/score-submission/${matchData.matchId}/${matchData.teamId}`; // Redirect teams to their score submission link
}
};
const handleViewerJoin = async (name) => {
try {
const response = await fetch(/api/livekit/viewer-token?name=${name}, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
match_id: matchData.matchId,
participant_name: name, // Pass the name entered by the viewer
role,
}),
});
const data = await response.json();
if (!data.token) throw new Error(data.error || "Missing token");
setToken(data.token);
setShouldConnect(true);
} catch (err) {
console.error("Viewer token error:", err);
setError(err.message);
}
};
const handleBroadcasterJoin = async (defaults) => {
const name = defaults?.username?.trim() || "Broadcaster";
try {
const res = await fetch("/api/livekit/broadcast-token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
match_id: matchData.matchId,
participant_name: name,
role: "broadcaster",
}),
});
const data = await res.json();
if (!data.token) throw new Error(data.error || "Missing token");
setRole("broadcaster");
setToken(data.token);
setShouldConnect(true);
} catch (err) {
console.error("Broadcaster token error:", err);
setError(err.message);
}
};
const handleTeamJoin = async (defaults) => {
const name = defaults.username?.trim();
if (!name) {
setError("Missing display name");
return;
}
try {
const response = await fetch("/api/livekit/team-token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
match_id: matchData.matchId,
team_slug: matchData.team?.shortName ?? matchData.teamSlug ?? "unknown-team",
participant_name: name,
access_key: matchData.accessKey,
}),
});
const data = await response.json();
if (!data.token) throw new Error(data.error || "Missing token");
setToken(data.token);
setShouldConnect(true);
} catch (err) {
console.error("Team token error:", err);
setError(err.message);
}
};
const defaults = {
username:
Number(matchData.teamId) === matchData.homeTeam?.id
? matchData.homeTeam?.shortName ?? "Team"
: Number(matchData.teamId) === matchData.awayTeam?.id
? matchData.awayTeam?.shortName ?? "Team"
: "Team",
videoEnabled: true,
audioEnabled: true,
};
if (error) {
toast.error(error, { autoClose: 5000 });
return (
<div className="error-message" style={{ color: "#B71C1C", textAlign: "center", marginTop: "2rem" }}>
<strong>Error:</strong> {error}
</div>
);
}
if (!shouldConnect) {
if (isDisconnected) return null;
if (role === "broadcaster") {
return (
<PreJoin
defaults={{
username: "Broadcaster",
videoEnabled: false, // mic only
audioEnabled: true,
}}
persistUserChoices={false}
onSubmit={handleBroadcasterJoin}
onActiveDeviceChange={applyDeviceChange}
data-lk-theme="default"
/>
);
}
if (isViewer) return <ViewerPreJoin onJoin={handleViewerJoin} />;
if (isTeam)
return (
<PreJoin
defaults={defaults}
persistUserChoices={false}
onSubmit={handleTeamJoin}
onActiveDeviceChange={applyDeviceChange}
data-lk-theme="default"
/>
);
}
return (
<div className="livekit-container--inner full-height full-width">
<ToastContainer position="top-center" autoClose={3000} />
<LiveKitRoom
video={role === "team" && videoEnabled} // Only enable video if available for team
audio={isTeam || isBroadcaster}
publishDefaults={{
video: role === "team" && videoEnabled, // Only publish video for team and if available
audio: isTeam || isBroadcaster, // Only publish audio for team
}}
token={token}
serverUrl={matchData.serverUrl}
connect={shouldConnect}
onConnected={async (room) => {
if (isBroadcaster) {
await room.localParticipant.setCameraEnabled(false);
await room.localParticipant.setMicrophoneEnabled(true);
}
}}
onDisconnected={async () => {
setShouldConnect(false);
await new Promise((r) => setTimeout(r, 300));
handleDisconnect();
}}
onError={(err) => {
console.warn("LiveKitRoom error:", err);
setError(err.message);
}}
data-lk-theme="default"
style={{ flex: 1, display: "flex", flexDirection: "column" }}
>
<ViewerCountBadge />
<RoomContent
role={role}
matchData={matchData}
onLeave={handleDisconnect}
videoEnabled={videoEnabled}
/>
</LiveKitRoom>
</div>
);
}
export default CompetitionApp;tall-airline-16756
09/19/2025, 3:51 PMsetTrackSubscriptionPermissions method to control who can access them. For example:
room.localParticipant.setTrackSubscriptionPermissions(
false,
[
new ParticipantTrackPermission({
participantSid: "allowed-team-sid", // Replace with the specific team participant SID
allTracksAllowed: false, // Or true if you want to allow all tracks
}),
]
);
2. Map Roles to Participant SIDs: Since LiveKit doesn’t automatically link roles with subscription permissions, you’ll want to create a mapping between roles like "team" and "broadcaster" to participant SIDs in your backend. This way, you can dynamically set the permissions when tracks are published.
3. Update Your App Logic: Make sure that "broadcaster" participants call the setTrackSubscriptionPermissions method right after connecting to the room and before publishing their tracks.
If you’d like, I’m here to help you walk through implementing these changes or answer any other questions you might have. Just let me know!bland-baker-98506
09/19/2025, 3:52 PMtall-airline-16756
09/19/2025, 3:52 PMbland-baker-98506
09/23/2025, 5:13 PMtall-airline-16756
09/23/2025, 5:14 PMonConnected callback for broadcasters:
onConnected={async (room) => {
if (isBroadcaster) {
await room.localParticipant.setCameraEnabled(false);
await room.localParticipant.setMicrophoneEnabled(true);
// Restrict subscription permissions
room.localParticipant.setTrackSubscriptionPermissions(
false,
[
new ParticipantTrackPermission({
participantSid: "allowed-team-sid", // Replace with the specific team participant SID
allTracksAllowed: false, // Or true if you want to allow all tracks
}),
]
);
}
}}
2. Map Roles to Participant SIDs: It’s helpful to update your backend to map roles like "team" and "broadcaster" to participant SIDs. Then, pass this mapping to the client so you can dynamically control permissions based on roles.
3. Test the Changes: After implementing, double-check that only allowed participants can access the "broadcaster" tracks, and that "team" participants are prevented from subscribing.
If you’d like, I’m here to support you with implementing these changes or answer any questions you might have about your code. Just let me know!