2022-12-07 19:03:22 +00:00
|
|
|
<script>
|
2022-12-08 08:56:29 +00:00
|
|
|
import axios from "axios";
|
2022-12-10 03:16:43 +00:00
|
|
|
import { pushNotFound, webfinger } from "../assets/utils";
|
2022-12-08 08:56:29 +00:00
|
|
|
import { useMastodonStore } from "../stores/mastodon";
|
2022-12-16 03:36:51 +00:00
|
|
|
import { map, some, omit, filter, trim, clone } from "lodash-es";
|
2022-12-08 08:56:29 +00:00
|
|
|
import Participant from "../components/Participant.vue";
|
|
|
|
import {
|
|
|
|
mdiMicrophone,
|
|
|
|
mdiMicrophoneOff,
|
|
|
|
mdiMicrophoneQuestion,
|
2022-12-09 00:21:17 +00:00
|
|
|
mdiDoorClosed,
|
2022-12-09 14:59:37 +00:00
|
|
|
mdiVolumeOff,
|
2022-12-10 03:16:43 +00:00
|
|
|
mdiClose,
|
|
|
|
mdiCheck,
|
|
|
|
mdiAccountVoice,
|
2022-12-15 21:43:50 +00:00
|
|
|
mdiLogout,
|
2022-12-16 03:36:51 +00:00
|
|
|
mdiDotsVertical,
|
|
|
|
mdiPencil,
|
2022-12-08 08:56:29 +00:00
|
|
|
} from "@mdi/js";
|
2022-12-10 03:16:43 +00:00
|
|
|
import {
|
|
|
|
Room,
|
|
|
|
RoomEvent,
|
|
|
|
Track,
|
|
|
|
DisconnectReason,
|
|
|
|
DataPacket_Kind,
|
2022-12-10 13:06:08 +00:00
|
|
|
AudioPresets,
|
2022-12-10 03:16:43 +00:00
|
|
|
} from "livekit-client";
|
2022-12-08 08:56:29 +00:00
|
|
|
import { login } from "masto";
|
2022-12-16 03:36:51 +00:00
|
|
|
import { useVuelidate } from "@vuelidate/core";
|
|
|
|
import { helpers, maxLength, required } from "@vuelidate/validators";
|
2022-12-18 11:12:59 +00:00
|
|
|
import NoSleep from "@uriopass/nosleep.js";
|
2022-12-26 17:01:41 +00:00
|
|
|
import { DateTime } from "luxon";
|
2022-12-08 08:56:29 +00:00
|
|
|
|
2022-12-10 13:06:08 +00:00
|
|
|
const publishOpts = {
|
2022-12-10 13:07:42 +00:00
|
|
|
audioBitrate: AudioPresets.music,
|
2022-12-10 13:06:08 +00:00
|
|
|
// forceStereo: true,
|
|
|
|
};
|
|
|
|
|
|
|
|
const captureOpts = {
|
2022-12-18 11:12:59 +00:00
|
|
|
// autoGainControl: false,
|
|
|
|
// echoCancellation: false,
|
2022-12-10 13:06:08 +00:00
|
|
|
// sampleRate: 48000,
|
|
|
|
// sampleSize: 16,
|
|
|
|
// channelCount: 2
|
|
|
|
};
|
|
|
|
|
2022-12-08 08:56:29 +00:00
|
|
|
export default {
|
|
|
|
setup() {
|
2022-12-18 11:12:59 +00:00
|
|
|
const noSleep = new NoSleep();
|
|
|
|
document.addEventListener(
|
|
|
|
"click",
|
|
|
|
function enableNoSleep() {
|
|
|
|
document.removeEventListener("click", enableNoSleep, false);
|
|
|
|
noSleep.enable();
|
|
|
|
},
|
|
|
|
false
|
|
|
|
);
|
2022-12-08 08:56:29 +00:00
|
|
|
return {
|
2023-01-03 03:58:41 +00:00
|
|
|
webfinger,
|
|
|
|
clone,
|
|
|
|
mdiLogout,
|
|
|
|
mdiAccountVoice,
|
|
|
|
mdiMicrophone,
|
|
|
|
mdiMicrophoneOff,
|
|
|
|
mdiMicrophoneQuestion,
|
|
|
|
mdiVolumeOff,
|
|
|
|
mdiClose,
|
|
|
|
mdiCheck,
|
|
|
|
mdiDotsVertical,
|
|
|
|
mdiPencil,
|
2022-12-16 03:36:51 +00:00
|
|
|
v$: useVuelidate(),
|
2022-12-08 08:56:29 +00:00
|
|
|
donStore: useMastodonStore(),
|
2022-12-10 03:16:43 +00:00
|
|
|
decoder: new TextDecoder(),
|
|
|
|
encoder: new TextEncoder(),
|
2022-12-08 08:56:29 +00:00
|
|
|
};
|
|
|
|
},
|
|
|
|
components: {
|
|
|
|
Participant,
|
|
|
|
},
|
2022-12-16 03:36:51 +00:00
|
|
|
validations() {
|
|
|
|
return {
|
|
|
|
editingRoomInfo: {
|
|
|
|
title: {
|
|
|
|
required: helpers.withMessage(
|
2022-12-17 02:30:46 +00:00
|
|
|
this.$t("form.titleRequired"),
|
2022-12-16 03:36:51 +00:00
|
|
|
required
|
|
|
|
),
|
|
|
|
maxLength: maxLength(100),
|
|
|
|
},
|
|
|
|
description: {
|
|
|
|
maxLength: maxLength(500),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
},
|
2022-12-08 08:56:29 +00:00
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
roomID: this.$route.params.id,
|
2022-12-29 14:53:42 +00:00
|
|
|
loading: true,
|
2023-01-03 03:58:41 +00:00
|
|
|
mainHeight: 700,
|
2022-12-09 00:21:17 +00:00
|
|
|
roomClient: new Room(),
|
2022-12-08 08:56:29 +00:00
|
|
|
roomInfo: {
|
2023-01-11 05:54:56 +00:00
|
|
|
title: this.$t("connecting"),
|
2022-12-08 08:56:29 +00:00
|
|
|
description: "",
|
2022-12-16 03:36:51 +00:00
|
|
|
restriction: "",
|
2022-12-08 08:56:29 +00:00
|
|
|
host: null,
|
|
|
|
cohosts: [],
|
2022-12-10 03:16:43 +00:00
|
|
|
speakers: [],
|
2022-12-26 17:01:41 +00:00
|
|
|
created_at: null,
|
2022-12-08 08:56:29 +00:00
|
|
|
},
|
2022-12-16 03:36:51 +00:00
|
|
|
editingRoomInfo: {
|
|
|
|
title: "",
|
|
|
|
description: "",
|
|
|
|
restriction: "",
|
|
|
|
},
|
|
|
|
relOptions: [
|
2022-12-17 02:30:46 +00:00
|
|
|
{ title: this.$t("form.relationships.everyone"), value: "everyone" },
|
|
|
|
{ title: this.$t("form.relationships.following"), value: "following" },
|
|
|
|
{ title: this.$t("form.relationships.follower"), value: "follower" },
|
|
|
|
{ title: this.$t("form.relationships.knowing"), value: "knowing" },
|
|
|
|
{ title: this.$t("form.relationships.mutual"), value: "mutual" },
|
|
|
|
{ title: this.$t("form.relationships.private"), value: "private" },
|
2022-12-16 03:36:51 +00:00
|
|
|
],
|
2022-12-08 08:56:29 +00:00
|
|
|
participants: {},
|
|
|
|
cachedMastoData: {},
|
|
|
|
activeSpeakerIDs: new Set(),
|
2022-12-09 00:21:17 +00:00
|
|
|
mutedSpeakerIDs: new Set(),
|
|
|
|
micGranted: false,
|
|
|
|
autoplayDisabled: false,
|
2022-12-10 03:16:43 +00:00
|
|
|
speakRequests: new Set(),
|
|
|
|
showRequestNotification: false,
|
|
|
|
showRequestDialog: false,
|
|
|
|
showRequestedNotification: false,
|
2022-12-16 03:36:51 +00:00
|
|
|
isEditLoading: false,
|
|
|
|
showEditDialog: false,
|
2022-12-26 17:01:41 +00:00
|
|
|
timeElapsed: "",
|
2022-12-29 14:53:42 +00:00
|
|
|
preview: false,
|
2022-12-08 08:56:29 +00:00
|
|
|
};
|
|
|
|
},
|
2022-12-29 14:53:42 +00:00
|
|
|
async created() {
|
2022-12-08 08:56:29 +00:00
|
|
|
this.onResize();
|
2022-12-29 14:53:42 +00:00
|
|
|
// fetch mastodon token
|
|
|
|
if (!this.donStore.client || !this.donStore.authorized) {
|
|
|
|
try {
|
|
|
|
await this.donStore.fetchToken();
|
|
|
|
} catch {
|
|
|
|
this.preview = true;
|
|
|
|
try {
|
|
|
|
const resp = await axios.get(`/app/preview/${this.roomID}`);
|
|
|
|
this.roomInfo = resp.data.roomInfo;
|
|
|
|
this.participants = resp.data.participants;
|
|
|
|
this.mutedSpeakerIDs = new Set(Object.keys(this.participants));
|
|
|
|
for (const [key, value] of Object.entries(this.participants)) {
|
|
|
|
if (value !== null) {
|
|
|
|
this.fetchMastoData(key, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (error) {
|
2022-12-29 15:23:51 +00:00
|
|
|
switch (error.response?.status) {
|
|
|
|
case 404:
|
|
|
|
pushNotFound(this.$route);
|
2022-12-29 15:28:21 +00:00
|
|
|
break;
|
|
|
|
case 403:
|
|
|
|
alert(this.$t("loginRequired"));
|
2023-01-11 20:53:15 +00:00
|
|
|
break;
|
2023-01-11 05:31:57 +00:00
|
|
|
case 410:
|
|
|
|
alert(this.$t("errors.alreadyClosed"));
|
2023-01-11 20:53:15 +00:00
|
|
|
break;
|
2022-12-29 14:53:42 +00:00
|
|
|
}
|
2023-01-11 20:53:15 +00:00
|
|
|
this.$router.push({
|
|
|
|
name: "login",
|
|
|
|
query: { l: `/r/${this.roomID}` },
|
|
|
|
});
|
2022-12-29 14:53:42 +00:00
|
|
|
} finally {
|
|
|
|
this.loading = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!this.preview) {
|
|
|
|
try {
|
|
|
|
await this.joinRoom();
|
|
|
|
} finally {
|
|
|
|
this.loading = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setInterval(this.refreshRemoteMuteStatus, 100);
|
|
|
|
setInterval(this.refreshTimeElapsed, 1000);
|
2022-12-08 08:56:29 +00:00
|
|
|
},
|
2022-12-09 00:21:17 +00:00
|
|
|
computed: {
|
|
|
|
iamMuted() {
|
|
|
|
const myAudonID = this.donStore.oauth.audon_id;
|
|
|
|
return (
|
2022-12-10 03:16:43 +00:00
|
|
|
(this.iamHost || this.iamCohost || this.iamSpeaker) &&
|
2022-12-09 00:21:17 +00:00
|
|
|
this.micGranted &&
|
|
|
|
this.mutedSpeakerIDs.has(myAudonID)
|
|
|
|
);
|
|
|
|
},
|
|
|
|
iamHost() {
|
|
|
|
const myAudonID = this.donStore.oauth.audon_id;
|
|
|
|
if (!myAudonID) return false;
|
|
|
|
|
|
|
|
return this.isHost(myAudonID);
|
|
|
|
},
|
|
|
|
iamCohost() {
|
|
|
|
const myInfo = this.donStore.userinfo;
|
|
|
|
if (!myInfo) return false;
|
|
|
|
|
2022-12-09 14:59:37 +00:00
|
|
|
return this.isCohost({ remote_id: myInfo.id, remote_url: myInfo.url });
|
2022-12-09 00:21:17 +00:00
|
|
|
},
|
2022-12-10 03:16:43 +00:00
|
|
|
iamSpeaker() {
|
|
|
|
const myAudonID = this.donStore.oauth.audon_id;
|
|
|
|
if (!myAudonID) return false;
|
|
|
|
|
|
|
|
return this.isSpeaker(myAudonID);
|
|
|
|
},
|
2022-12-09 00:21:17 +00:00
|
|
|
micStatusIcon() {
|
|
|
|
if (!this.micGranted) {
|
|
|
|
return mdiMicrophoneQuestion;
|
|
|
|
}
|
|
|
|
if (this.iamMuted) {
|
|
|
|
return mdiMicrophoneOff;
|
|
|
|
}
|
|
|
|
return mdiMicrophone;
|
|
|
|
},
|
2022-12-16 03:36:51 +00:00
|
|
|
titleErrors() {
|
|
|
|
const errors = this.v$.editingRoomInfo.title.$errors;
|
|
|
|
const messages = map(errors, (e) => e.$message);
|
|
|
|
return messages;
|
|
|
|
},
|
2022-12-09 00:21:17 +00:00
|
|
|
},
|
2022-12-08 08:56:29 +00:00
|
|
|
methods: {
|
2022-12-26 17:01:41 +00:00
|
|
|
refreshTimeElapsed() {
|
|
|
|
if (!this.roomInfo.created_at) return;
|
|
|
|
const now = DateTime.utc();
|
|
|
|
const createdAt = DateTime.fromISO(this.roomInfo.created_at);
|
|
|
|
const delta = now.diff(createdAt);
|
|
|
|
this.timeElapsed = delta.toFormat("hh:mm:ss");
|
|
|
|
},
|
2022-12-08 08:56:29 +00:00
|
|
|
async joinRoom() {
|
2022-12-09 00:21:17 +00:00
|
|
|
if (!this.donStore.authorized) return;
|
2022-12-08 08:56:29 +00:00
|
|
|
try {
|
|
|
|
const resp = await axios.get(`/api/room/${this.roomID}`);
|
|
|
|
const room = new Room({
|
2022-12-18 11:12:59 +00:00
|
|
|
// adaptiveStream: false,
|
|
|
|
// dynacast: false,
|
|
|
|
// publishDefaults: {
|
|
|
|
// stopMicTrackOnMute: false,
|
|
|
|
// simulcast: false,
|
|
|
|
// },
|
2022-12-08 08:56:29 +00:00
|
|
|
});
|
|
|
|
const self = this;
|
|
|
|
room
|
|
|
|
.on(RoomEvent.TrackSubscribed, (track, publication, participant) => {
|
|
|
|
if (track.kind === Track.Kind.Audio) {
|
|
|
|
const element = track.attach();
|
|
|
|
self.$refs.audioDOM.appendChild(element);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.on(
|
|
|
|
RoomEvent.TrackUnsubscribed,
|
|
|
|
(track, publication, participant) => {
|
|
|
|
track.detach();
|
|
|
|
}
|
|
|
|
)
|
2022-12-09 00:21:17 +00:00
|
|
|
.on(RoomEvent.LocalTrackPublished, (publication, participant) => {
|
|
|
|
self.micGranted = true;
|
|
|
|
})
|
2022-12-08 08:56:29 +00:00
|
|
|
.on(RoomEvent.LocalTrackUnpublished, (publication, participant) => {
|
|
|
|
publication.track?.detach();
|
|
|
|
})
|
|
|
|
.on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
|
|
|
|
self.activeSpeakerIDs = new Set(map(speakers, (p) => p.identity));
|
|
|
|
})
|
|
|
|
.on(RoomEvent.ParticipantConnected, (participant) => {
|
|
|
|
const metadata = self.addParticipant(participant);
|
|
|
|
if (metadata !== null) {
|
|
|
|
self.fetchMastoData(participant.identity, metadata);
|
|
|
|
}
|
2022-12-09 00:21:17 +00:00
|
|
|
})
|
2022-12-25 02:45:03 +00:00
|
|
|
.on(RoomEvent.TrackMuted, (publication, participant) => {})
|
|
|
|
.on(RoomEvent.TrackUnmuted, (publication, participant) => {})
|
2022-12-08 08:56:29 +00:00
|
|
|
.on(RoomEvent.ParticipantDisconnected, (participant) => {
|
|
|
|
self.participants = omit(self.participants, participant.identity);
|
|
|
|
})
|
|
|
|
.on(RoomEvent.AudioPlaybackStatusChanged, () => {
|
|
|
|
if (!room.canPlaybackAudio) {
|
2022-12-09 14:59:37 +00:00
|
|
|
self.autoplayDisabled = true;
|
2022-12-08 08:56:29 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.on(RoomEvent.Disconnected, (reason) => {
|
2022-12-09 00:21:17 +00:00
|
|
|
// TODO: change this from alert to a vuetify thing
|
|
|
|
let message = "";
|
|
|
|
switch (reason) {
|
|
|
|
case DisconnectReason.ROOM_DELETED:
|
2022-12-17 02:30:46 +00:00
|
|
|
message = self.$t("roomEvent.closedByHost");
|
2022-12-09 00:21:17 +00:00
|
|
|
break;
|
|
|
|
case DisconnectReason.PARTICIPANT_REMOVED:
|
2022-12-17 02:30:46 +00:00
|
|
|
message = self.$t("roomEvent.removed");
|
2022-12-09 00:21:17 +00:00
|
|
|
break;
|
|
|
|
case DisconnectReason.CLIENT_INITIATED:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
message = "Disconnected due to unknown reasons";
|
|
|
|
}
|
|
|
|
if (message !== "") {
|
|
|
|
alert(message);
|
|
|
|
}
|
|
|
|
self.$router.push({ name: "home" });
|
2022-12-10 03:16:43 +00:00
|
|
|
})
|
|
|
|
.on(RoomEvent.DataReceived, (payload, participant, kind) => {
|
|
|
|
try {
|
|
|
|
/* data should be like
|
|
|
|
{ "kind": "speak_request" }
|
|
|
|
{ "kind": "chat", "data": "..." }
|
|
|
|
{ "kind": "request_declined", "audon_id": "..."}
|
|
|
|
*/
|
|
|
|
const strData = self.decoder.decode(payload);
|
|
|
|
const jsonData = JSON.parse(strData);
|
|
|
|
const metadata = JSON.parse(participant.metadata);
|
|
|
|
switch (jsonData?.kind) {
|
|
|
|
case "speak_request": // someone is wanting to be a speaker
|
|
|
|
self.onSpeakRequestReceived(participant);
|
|
|
|
break;
|
|
|
|
case "request_declined":
|
|
|
|
if (
|
|
|
|
self.isHost(participant.identity) ||
|
|
|
|
self.isCohost(metadata)
|
|
|
|
) {
|
|
|
|
self.speakRequests.delete(jsonData.audon_id);
|
2023-01-03 03:58:41 +00:00
|
|
|
if (self.speakRequests.size < 1)
|
|
|
|
self.showRequestNotification = false;
|
2022-12-10 03:16:43 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.log(
|
|
|
|
"invalida data received from: ",
|
|
|
|
participant.identity
|
|
|
|
);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.on(RoomEvent.RoomMetadataChanged, (metadata) => {
|
|
|
|
self.roomInfo = JSON.parse(metadata);
|
2022-12-16 03:36:51 +00:00
|
|
|
self.editingRoomInfo = clone(self.roomInfo);
|
|
|
|
if (!self.roomInfo.speakers) return;
|
2022-12-10 03:16:43 +00:00
|
|
|
for (const speakers of self.roomInfo.speakers) {
|
|
|
|
self.speakRequests.delete(speakers.audon_id);
|
2023-01-03 03:58:41 +00:00
|
|
|
if (self.speakRequests.size < 1)
|
|
|
|
self.showRequestNotification = false;
|
2022-12-10 03:16:43 +00:00
|
|
|
}
|
2022-12-25 02:45:03 +00:00
|
|
|
if (self.iamSpeaker && !self.micGranted) {
|
2022-12-10 13:06:08 +00:00
|
|
|
self.roomClient.localParticipant
|
|
|
|
.setMicrophoneEnabled(true, captureOpts, publishOpts)
|
|
|
|
.then((v) => {
|
|
|
|
self.micGranted = true;
|
2022-12-18 09:16:41 +00:00
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
self.roomClient.localParticipant.setMicrophoneEnabled(false);
|
2022-12-10 13:06:08 +00:00
|
|
|
});
|
2022-12-10 03:16:43 +00:00
|
|
|
}
|
2022-12-08 08:56:29 +00:00
|
|
|
});
|
|
|
|
await room.connect(resp.data.url, resp.data.token);
|
|
|
|
this.roomClient = room;
|
|
|
|
this.roomInfo = JSON.parse(room.metadata);
|
2022-12-16 03:36:51 +00:00
|
|
|
this.editingRoomInfo = clone(this.roomInfo);
|
2022-12-08 08:56:29 +00:00
|
|
|
this.addParticipant(room.localParticipant);
|
|
|
|
for (const part of room.participants.values()) {
|
|
|
|
this.addParticipant(part);
|
|
|
|
}
|
2022-12-18 09:16:41 +00:00
|
|
|
this.mutedSpeakerIDs.add(this.donStore.oauth.audon_id);
|
2022-12-08 08:56:29 +00:00
|
|
|
this.activeSpeakerIDs = new Set(
|
|
|
|
map(room.activeSpeakers, (p) => p.identity)
|
|
|
|
);
|
|
|
|
// cache mastodon data of current participants
|
|
|
|
for (const [key, value] of Object.entries(this.participants)) {
|
|
|
|
if (value !== null) {
|
|
|
|
this.fetchMastoData(key, value);
|
|
|
|
}
|
|
|
|
}
|
2022-12-10 03:16:43 +00:00
|
|
|
if (this.iamHost || this.iamCohost || this.iamSpeaker) {
|
2022-12-09 00:21:17 +00:00
|
|
|
try {
|
2022-12-10 13:06:08 +00:00
|
|
|
await room.localParticipant.setMicrophoneEnabled(
|
|
|
|
true,
|
|
|
|
captureOpts,
|
|
|
|
publishOpts
|
|
|
|
);
|
2022-12-09 00:21:17 +00:00
|
|
|
} catch {
|
2022-12-17 02:30:46 +00:00
|
|
|
alert(this.$t("microphoneBlocked"));
|
2022-12-18 09:16:41 +00:00
|
|
|
} finally {
|
|
|
|
await room.localParticipant.setMicrophoneEnabled(false);
|
2022-12-09 00:21:17 +00:00
|
|
|
}
|
|
|
|
}
|
2022-12-08 08:56:29 +00:00
|
|
|
} catch (error) {
|
2022-12-09 14:59:37 +00:00
|
|
|
switch (error.response?.status) {
|
2022-12-15 21:43:50 +00:00
|
|
|
case 403:
|
|
|
|
let message = "";
|
|
|
|
switch (error.response?.data) {
|
|
|
|
case "following":
|
2022-12-17 02:30:46 +00:00
|
|
|
message = this.$t("errors.restriction.following");
|
2022-12-15 21:43:50 +00:00
|
|
|
break;
|
|
|
|
case "follower":
|
2022-12-17 02:30:46 +00:00
|
|
|
message = this.$t("errors.restriction.follower");
|
2022-12-15 21:43:50 +00:00
|
|
|
break;
|
|
|
|
case "knowing":
|
2022-12-17 02:30:46 +00:00
|
|
|
message = this.$t("errors.restriction.knowing");
|
2022-12-15 21:43:50 +00:00
|
|
|
break;
|
|
|
|
case "mutual":
|
2022-12-17 02:30:46 +00:00
|
|
|
message = this.$t("errors.restriction.mutual");
|
2022-12-15 21:43:50 +00:00
|
|
|
break;
|
|
|
|
case "private":
|
2022-12-17 02:30:46 +00:00
|
|
|
message = this.$t("errors.restriction.private");
|
2022-12-15 21:43:50 +00:00
|
|
|
break;
|
|
|
|
default:
|
2022-12-17 02:30:46 +00:00
|
|
|
message = this.$t("errors.restriction.default");
|
2022-12-15 21:43:50 +00:00
|
|
|
}
|
|
|
|
alert(message);
|
|
|
|
break;
|
2022-12-09 14:59:37 +00:00
|
|
|
case 404:
|
|
|
|
pushNotFound(this.$route);
|
|
|
|
break;
|
|
|
|
case 406:
|
2022-12-17 02:30:46 +00:00
|
|
|
alert(this.$t("errors.alreadyConnected"));
|
2022-12-09 14:59:37 +00:00
|
|
|
break;
|
|
|
|
case 410:
|
2022-12-17 02:30:46 +00:00
|
|
|
alert(this.$t("errors.alreadyClosed"));
|
2022-12-09 14:59:37 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
alert(error);
|
2022-12-08 08:56:29 +00:00
|
|
|
}
|
2022-12-15 21:43:50 +00:00
|
|
|
this.$router.push({ name: "home" });
|
2022-12-08 08:56:29 +00:00
|
|
|
}
|
|
|
|
},
|
2022-12-25 02:45:03 +00:00
|
|
|
refreshRemoteMuteStatus() {
|
2022-12-18 09:16:41 +00:00
|
|
|
for (const part of this.roomClient.participants.values()) {
|
2022-12-25 02:45:03 +00:00
|
|
|
const track = part.getTrack(Track.Source.Microphone);
|
|
|
|
if (track?.isMuted === false) {
|
|
|
|
this.mutedSpeakerIDs.delete(part.identity);
|
|
|
|
} else {
|
2022-12-18 09:16:41 +00:00
|
|
|
this.mutedSpeakerIDs.add(part.identity);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2022-12-08 08:56:29 +00:00
|
|
|
onResize() {
|
|
|
|
const mainArea = document.getElementById("mainArea");
|
|
|
|
const height = mainArea.clientHeight;
|
2022-12-17 02:30:46 +00:00
|
|
|
this.mainHeight = height > 700 ? 700 : window.innerHeight - 95;
|
2022-12-08 08:56:29 +00:00
|
|
|
},
|
2022-12-09 00:21:17 +00:00
|
|
|
isHost(identity) {
|
|
|
|
return identity === this.roomInfo.host?.audon_id;
|
|
|
|
},
|
2022-12-25 02:45:03 +00:00
|
|
|
isCohost(metadata) {
|
2022-12-08 08:56:29 +00:00
|
|
|
return (
|
2022-12-25 02:45:03 +00:00
|
|
|
metadata &&
|
2022-12-08 08:56:29 +00:00
|
|
|
some(this.roomInfo.cohosts, {
|
2022-12-25 02:45:03 +00:00
|
|
|
remote_id: metadata.remote_id,
|
|
|
|
remote_url: metadata.remote_url,
|
2022-12-08 08:56:29 +00:00
|
|
|
})
|
|
|
|
);
|
|
|
|
},
|
2022-12-10 03:16:43 +00:00
|
|
|
isSpeaker(identity) {
|
|
|
|
return identity && some(this.roomInfo.speakers, { audon_id: identity });
|
|
|
|
},
|
|
|
|
isTalking(identity) {
|
|
|
|
return (
|
|
|
|
this.activeSpeakerIDs.has(identity) &&
|
|
|
|
!this.mutedSpeakerIDs.has(identity)
|
|
|
|
);
|
|
|
|
},
|
|
|
|
onSpeakRequestReceived(participant) {
|
|
|
|
if (this.iamHost || this.iamCohost) {
|
|
|
|
if (this.speakRequests.has(participant.identity)) return;
|
|
|
|
this.speakRequests.add(participant.identity);
|
|
|
|
this.showRequestNotification = true;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async onAcceptRequest(identity) {
|
|
|
|
// promote user to a speaker
|
|
|
|
// the livekit server will update room metadata
|
|
|
|
try {
|
|
|
|
await axios.put(`/api/room/${this.roomID}/${identity}`);
|
|
|
|
} catch (reqError) {
|
|
|
|
console.log("permission update request error: ", reqError);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async onDeclineRequest(identity) {
|
|
|
|
// share declined identity with host and other cohosts
|
|
|
|
if (!this.speakRequests.delete(identity)) return;
|
|
|
|
const data = { kind: "request_declined", audon_id: identity };
|
|
|
|
await this.publishDataToHostAndCohosts(data);
|
|
|
|
},
|
|
|
|
async requestSpeak() {
|
2022-12-17 02:30:46 +00:00
|
|
|
if (confirm(this.$t("speakRequest.dialog"))) {
|
2022-12-10 03:16:43 +00:00
|
|
|
await this.publishDataToHostAndCohosts({ kind: "speak_request" });
|
|
|
|
this.showRequestedNotification = true;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async publishDataToHostAndCohosts(data) {
|
|
|
|
const payload = this.encoder.encode(JSON.stringify(data));
|
|
|
|
// participants - speakers
|
|
|
|
const hostandcohosts = filter(
|
|
|
|
Array.from(this.roomClient.participants.values()),
|
|
|
|
(p) => {
|
|
|
|
const metadata = JSON.parse(p.metadata);
|
|
|
|
return this.isHost(p.identity) || this.isCohost(metadata);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
await this.roomClient.localParticipant.publishData(
|
|
|
|
payload,
|
|
|
|
DataPacket_Kind.RELIABLE,
|
|
|
|
hostandcohosts
|
|
|
|
);
|
|
|
|
},
|
2022-12-08 08:56:29 +00:00
|
|
|
addParticipant(participant) {
|
|
|
|
const metadata = participant.metadata
|
|
|
|
? JSON.parse(participant.metadata)
|
|
|
|
: null;
|
2022-12-09 00:21:17 +00:00
|
|
|
if (metadata) {
|
|
|
|
this.participants[participant.identity] = metadata;
|
|
|
|
}
|
2022-12-08 08:56:29 +00:00
|
|
|
return metadata;
|
|
|
|
},
|
|
|
|
async fetchMastoData(identity, { remote_id, remote_url }) {
|
|
|
|
if (this.cachedMastoData[identity] !== undefined) return;
|
|
|
|
try {
|
|
|
|
const url = new URL(remote_url);
|
|
|
|
const mastoClient = await login({
|
|
|
|
url: url.origin,
|
|
|
|
disableVersionCheck: true,
|
|
|
|
});
|
|
|
|
const info = await mastoClient.accounts.fetch(remote_id);
|
|
|
|
this.cachedMastoData[identity] = info;
|
|
|
|
} catch (error) {
|
|
|
|
// FIXME: display error snackbar
|
|
|
|
console.log(error);
|
|
|
|
}
|
|
|
|
},
|
2022-12-09 00:21:17 +00:00
|
|
|
async onToggleMute() {
|
|
|
|
const myTrack = this.roomClient.localParticipant.getTrack(
|
|
|
|
Track.Source.Microphone
|
|
|
|
);
|
2022-12-25 02:45:03 +00:00
|
|
|
const myIdentity = this.roomClient.localParticipant.identity;
|
2022-12-10 03:16:43 +00:00
|
|
|
if (this.iamHost || this.iamCohost || this.iamSpeaker) {
|
2022-12-09 00:21:17 +00:00
|
|
|
try {
|
2022-12-25 02:45:03 +00:00
|
|
|
let newMicStatus = false;
|
2022-12-09 00:21:17 +00:00
|
|
|
if (!this.micGranted) {
|
2022-12-25 02:45:03 +00:00
|
|
|
newMicStatus = true;
|
2022-12-10 13:06:08 +00:00
|
|
|
await this.roomClient.localParticipant.setMicrophoneEnabled(
|
2022-12-25 02:45:03 +00:00
|
|
|
newMicStatus,
|
2022-12-10 13:06:08 +00:00
|
|
|
captureOpts,
|
|
|
|
publishOpts
|
|
|
|
);
|
2022-12-09 00:21:17 +00:00
|
|
|
} else if (myTrack) {
|
2022-12-25 02:45:03 +00:00
|
|
|
newMicStatus = myTrack.isMuted;
|
2022-12-09 00:21:17 +00:00
|
|
|
await this.roomClient.localParticipant.setMicrophoneEnabled(
|
2022-12-25 02:45:03 +00:00
|
|
|
newMicStatus,
|
2022-12-10 13:06:08 +00:00
|
|
|
captureOpts,
|
|
|
|
publishOpts
|
2022-12-09 00:21:17 +00:00
|
|
|
);
|
|
|
|
}
|
2022-12-25 02:45:03 +00:00
|
|
|
if (newMicStatus) {
|
|
|
|
this.mutedSpeakerIDs.delete(myIdentity);
|
|
|
|
} else {
|
|
|
|
this.mutedSpeakerIDs.add(myIdentity);
|
|
|
|
}
|
2022-12-09 00:21:17 +00:00
|
|
|
} catch {
|
2022-12-17 02:30:46 +00:00
|
|
|
alert(this.$t("microphoneBlocked"));
|
2022-12-09 00:21:17 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-12-10 03:16:43 +00:00
|
|
|
this.requestSpeak();
|
2022-12-09 00:21:17 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
async onRoomClose() {
|
|
|
|
// TODO: change this from confirm to a vuetify thing
|
2022-12-17 02:30:46 +00:00
|
|
|
if (confirm(this.$t("closeRoomConfirm"))) {
|
2022-12-09 00:21:17 +00:00
|
|
|
try {
|
|
|
|
await axios.delete(`/api/room/${this.roomID}`);
|
|
|
|
} catch (error) {
|
|
|
|
alert(error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async onStartListening() {
|
|
|
|
try {
|
|
|
|
await this.roomClient.startAudio();
|
|
|
|
this.autoplayDisabled = false;
|
|
|
|
} catch {
|
2022-12-17 02:30:46 +00:00
|
|
|
alert(this.$t("errors.connectionFailed"));
|
2022-12-09 00:21:17 +00:00
|
|
|
await this.roomClient.disconnect();
|
|
|
|
}
|
2022-12-09 14:59:37 +00:00
|
|
|
},
|
2022-12-16 03:36:51 +00:00
|
|
|
async onEditSubmit() {
|
|
|
|
this.editingRoomInfo.title = trim(this.editingRoomInfo.title);
|
|
|
|
this.editingRoomInfo.description = trim(this.editingRoomInfo.description);
|
|
|
|
const isFormCorrect = await this.v$.$validate();
|
|
|
|
if (!isFormCorrect) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.isEditLoading = true;
|
|
|
|
|
|
|
|
try {
|
|
|
|
const payload = {
|
|
|
|
title: this.editingRoomInfo.title,
|
|
|
|
description: this.editingRoomInfo.description,
|
|
|
|
restriction: this.editingRoomInfo.restriction,
|
|
|
|
};
|
|
|
|
await axios.patch(`/api/room/${this.roomID}`, payload);
|
|
|
|
} catch (error) {
|
|
|
|
alert(error);
|
|
|
|
} finally {
|
|
|
|
this.isEditLoading = false;
|
|
|
|
this.showEditDialog = false;
|
|
|
|
}
|
|
|
|
},
|
2022-12-08 08:56:29 +00:00
|
|
|
},
|
|
|
|
};
|
2022-12-07 19:03:22 +00:00
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
2022-12-16 03:36:51 +00:00
|
|
|
<v-dialog v-model="showEditDialog" max-width="500" persistent>
|
2022-12-29 14:53:42 +00:00
|
|
|
<v-card :loading="isEditLoading">
|
2022-12-17 02:30:46 +00:00
|
|
|
<v-card-title>{{ $t("editRoom") }}</v-card-title>
|
2022-12-16 03:36:51 +00:00
|
|
|
<v-card-text>
|
|
|
|
<v-text-field
|
|
|
|
v-model="editingRoomInfo.title"
|
2022-12-17 02:30:46 +00:00
|
|
|
:label="$t('form.title')"
|
2022-12-16 03:36:51 +00:00
|
|
|
:error-messages="titleErrors"
|
|
|
|
:counter="100"
|
|
|
|
required
|
|
|
|
@input="v$.editingRoomInfo.title.$touch()"
|
|
|
|
@blur="v$.editingRoomInfo.title.$touch()"
|
|
|
|
></v-text-field>
|
|
|
|
<v-textarea
|
|
|
|
auto-grow
|
|
|
|
v-model="editingRoomInfo.description"
|
|
|
|
rows="2"
|
2022-12-17 02:30:46 +00:00
|
|
|
:label="$t('form.description')"
|
2022-12-16 03:36:51 +00:00
|
|
|
:counter="500"
|
|
|
|
></v-textarea>
|
|
|
|
<v-select
|
|
|
|
:items="relOptions"
|
2022-12-17 02:30:46 +00:00
|
|
|
:label="$t('form.restriction')"
|
2022-12-16 03:36:51 +00:00
|
|
|
v-model="editingRoomInfo.restriction"
|
2022-12-17 02:30:46 +00:00
|
|
|
:messages="[$t('form.cohostCanAlwaysJoin')]"
|
2022-12-16 03:36:51 +00:00
|
|
|
></v-select>
|
|
|
|
</v-card-text>
|
|
|
|
<v-divider></v-divider>
|
|
|
|
<v-card-actions class="justify-end">
|
|
|
|
<v-btn
|
|
|
|
@click="
|
|
|
|
showEditDialog = false;
|
|
|
|
editingRoomInfo = clone(roomInfo);
|
|
|
|
"
|
2022-12-17 02:30:46 +00:00
|
|
|
>{{ $t("cancel") }}</v-btn
|
2022-12-16 03:36:51 +00:00
|
|
|
>
|
2022-12-29 14:53:42 +00:00
|
|
|
<v-btn :disabled="isEditLoading" @click="onEditSubmit">{{
|
|
|
|
$t("save")
|
|
|
|
}}</v-btn>
|
2022-12-16 03:36:51 +00:00
|
|
|
</v-card-actions>
|
|
|
|
</v-card>
|
|
|
|
</v-dialog>
|
2022-12-09 00:21:17 +00:00
|
|
|
<v-dialog v-model="autoplayDisabled" max-width="500" persistent>
|
|
|
|
<v-alert color="indigo">
|
2022-12-09 14:59:37 +00:00
|
|
|
<div class="mb-5">
|
2022-12-17 02:30:46 +00:00
|
|
|
{{ $t("browserMuted") }}
|
2022-12-09 14:59:37 +00:00
|
|
|
</div>
|
2022-12-09 00:21:17 +00:00
|
|
|
<div class="text-center mb-3">
|
2022-12-17 02:30:46 +00:00
|
|
|
<v-btn color="gray" @click="onStartListening">{{
|
|
|
|
$t("startListening")
|
|
|
|
}}</v-btn>
|
2022-12-09 00:21:17 +00:00
|
|
|
</div>
|
|
|
|
<div class="text-center">
|
2022-12-17 02:30:46 +00:00
|
|
|
<v-btn variant="text" @click="roomClient.disconnect()">{{
|
|
|
|
$t("leaveRoom")
|
|
|
|
}}</v-btn>
|
2022-12-09 00:21:17 +00:00
|
|
|
</div>
|
|
|
|
</v-alert>
|
|
|
|
</v-dialog>
|
2022-12-10 04:02:42 +00:00
|
|
|
<v-dialog v-model="showRequestDialog" max-width="500">
|
2022-12-10 03:16:43 +00:00
|
|
|
<v-card max-height="600" class="d-flex flex-column">
|
2022-12-17 02:30:46 +00:00
|
|
|
<v-card-title>{{ $t("speakRequest.label") }}</v-card-title>
|
2022-12-10 03:16:43 +00:00
|
|
|
<v-card-text class="flex-grow-1 overflow-auto py-0">
|
|
|
|
<v-list v-if="speakRequests.size > 0" lines="two" variant="tonal">
|
|
|
|
<v-list-item
|
|
|
|
v-for="id of Array.from(speakRequests)"
|
|
|
|
:key="id"
|
|
|
|
:title="cachedMastoData[id]?.displayName"
|
|
|
|
class="my-1"
|
|
|
|
rounded
|
|
|
|
>
|
|
|
|
<template v-slot:prepend>
|
|
|
|
<v-avatar class="rounded">
|
|
|
|
<v-img :src="cachedMastoData[id]?.avatar"></v-img>
|
|
|
|
</v-avatar>
|
|
|
|
</template>
|
|
|
|
<template v-slot:append>
|
|
|
|
<v-btn
|
|
|
|
class="mr-2"
|
|
|
|
size="small"
|
|
|
|
variant="text"
|
|
|
|
:icon="mdiCheck"
|
|
|
|
@click="onAcceptRequest(id)"
|
|
|
|
></v-btn>
|
|
|
|
<v-btn
|
|
|
|
size="small"
|
|
|
|
variant="text"
|
|
|
|
:icon="mdiClose"
|
|
|
|
@click="onDeclineRequest(id)"
|
|
|
|
></v-btn>
|
|
|
|
</template>
|
|
|
|
<v-list-item-subtitle>
|
|
|
|
<a
|
|
|
|
:href="cachedMastoData[id]?.url"
|
2022-12-17 22:00:12 +00:00
|
|
|
class="text-body plain"
|
2022-12-10 03:16:43 +00:00
|
|
|
target="_blank"
|
|
|
|
>{{ webfinger(cachedMastoData[id]) }}</a
|
|
|
|
>
|
|
|
|
</v-list-item-subtitle>
|
|
|
|
</v-list-item>
|
|
|
|
</v-list>
|
2022-12-17 02:30:46 +00:00
|
|
|
<p class="text-center py-3" v-else>
|
|
|
|
{{ $t("speakRequest.norequest") }}
|
|
|
|
</p>
|
2022-12-10 03:16:43 +00:00
|
|
|
</v-card-text>
|
|
|
|
<v-divider></v-divider>
|
|
|
|
<v-card-actions class="justify-end">
|
2022-12-17 02:30:46 +00:00
|
|
|
<v-btn @click="showRequestDialog = false">{{ $t("close") }}</v-btn>
|
2022-12-10 03:16:43 +00:00
|
|
|
</v-card-actions>
|
|
|
|
</v-card>
|
|
|
|
</v-dialog>
|
|
|
|
<v-snackbar
|
|
|
|
location="top"
|
|
|
|
:timeout="5000"
|
|
|
|
v-model="showRequestedNotification"
|
|
|
|
color="info"
|
|
|
|
>
|
2022-12-17 02:30:46 +00:00
|
|
|
<strong>{{ $t("speakRequest.sent") }}</strong>
|
2022-12-10 03:16:43 +00:00
|
|
|
<template v-slot:actions>
|
|
|
|
<v-btn
|
|
|
|
variant="text"
|
|
|
|
@click="showRequestedNotification = false"
|
|
|
|
:icon="mdiClose"
|
|
|
|
size="small"
|
|
|
|
></v-btn>
|
|
|
|
</template>
|
|
|
|
</v-snackbar>
|
|
|
|
<v-snackbar
|
|
|
|
location="top"
|
|
|
|
:timeout="-1"
|
|
|
|
v-model="showRequestNotification"
|
|
|
|
color="info"
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
style="cursor: pointer"
|
|
|
|
@click="
|
|
|
|
showRequestDialog = true;
|
|
|
|
showRequestNotification = false;
|
|
|
|
"
|
|
|
|
>
|
2022-12-17 02:30:46 +00:00
|
|
|
<strong>{{ $t("speakRequest.receive") }}</strong>
|
2022-12-10 03:16:43 +00:00
|
|
|
</div>
|
|
|
|
<template v-slot:actions>
|
|
|
|
<v-btn
|
|
|
|
variant="text"
|
|
|
|
@click="showRequestNotification = false"
|
|
|
|
:icon="mdiClose"
|
|
|
|
size="small"
|
|
|
|
></v-btn>
|
|
|
|
</template>
|
|
|
|
</v-snackbar>
|
2022-12-08 08:56:29 +00:00
|
|
|
<div class="d-none" ref="audioDOM"></div>
|
|
|
|
<main class="fill-height" v-resize="onResize">
|
|
|
|
<v-card :height="mainHeight" :loading="loading" class="d-flex flex-column">
|
2022-12-26 17:01:41 +00:00
|
|
|
<v-card-title class="d-flex align-center">
|
|
|
|
<div class="mr-auto overflow-y-auto">{{ roomInfo.title }}</div>
|
2023-01-03 03:58:41 +00:00
|
|
|
<v-chip v-if="timeElapsed" class="mx-1 flex-shrink-0">
|
|
|
|
<code>{{ timeElapsed }}</code>
|
|
|
|
</v-chip>
|
2022-12-26 17:01:41 +00:00
|
|
|
<div v-if="iamHost" class="flex-shrink-0">
|
2023-01-11 20:53:15 +00:00
|
|
|
<v-btn
|
|
|
|
size="small"
|
|
|
|
variant="text"
|
|
|
|
color="white"
|
|
|
|
:icon="mdiPencil"
|
|
|
|
@click="showEditDialog = true"
|
|
|
|
></v-btn>
|
2022-12-09 00:21:17 +00:00
|
|
|
</div>
|
|
|
|
</v-card-title>
|
2022-12-08 08:56:29 +00:00
|
|
|
<div
|
|
|
|
class="overflow-auto flex-shrink-0 pb-2"
|
|
|
|
v-if="roomInfo.description"
|
|
|
|
style="height: 100px"
|
|
|
|
>
|
|
|
|
<v-container class="py-0">
|
2022-12-15 21:43:50 +00:00
|
|
|
<p style="white-space: pre-wrap">{{ roomInfo.description }}</p>
|
2022-12-08 08:56:29 +00:00
|
|
|
</v-container>
|
|
|
|
</div>
|
|
|
|
<v-divider></v-divider>
|
|
|
|
<v-card-text class="flex-grow-1 overflow-auto">
|
|
|
|
<v-row justify="start">
|
|
|
|
<template v-for="(value, key) of participants" :key="key">
|
|
|
|
<Participant
|
2022-12-09 00:21:17 +00:00
|
|
|
v-if="isHost(key)"
|
2022-12-10 03:16:43 +00:00
|
|
|
:talking="isTalking(key)"
|
2022-12-08 08:56:29 +00:00
|
|
|
type="host"
|
|
|
|
:data="cachedMastoData[key]"
|
2022-12-09 00:21:17 +00:00
|
|
|
:muted="mutedSpeakerIDs.has(key)"
|
2022-12-08 08:56:29 +00:00
|
|
|
></Participant>
|
|
|
|
<Participant
|
|
|
|
v-if="isCohost(value)"
|
2022-12-10 03:16:43 +00:00
|
|
|
:talking="isTalking(key)"
|
2022-12-08 08:56:29 +00:00
|
|
|
type="cohost"
|
|
|
|
:data="cachedMastoData[key]"
|
2022-12-09 00:21:17 +00:00
|
|
|
:muted="mutedSpeakerIDs.has(key)"
|
2022-12-08 08:56:29 +00:00
|
|
|
></Participant>
|
2022-12-10 03:16:43 +00:00
|
|
|
<Participant
|
|
|
|
v-if="isSpeaker(key)"
|
|
|
|
:talking="isTalking(key)"
|
|
|
|
type="speaker"
|
|
|
|
:data="cachedMastoData[key]"
|
|
|
|
:muted="mutedSpeakerIDs.has(key)"
|
|
|
|
>
|
|
|
|
</Participant>
|
2022-12-08 08:56:29 +00:00
|
|
|
</template>
|
|
|
|
</v-row>
|
2022-12-10 03:16:43 +00:00
|
|
|
<v-row justify="start">
|
2022-12-08 08:56:29 +00:00
|
|
|
<template v-for="(value, key) of participants" :key="key">
|
|
|
|
<Participant
|
2022-12-10 03:16:43 +00:00
|
|
|
v-if="!isHost(key) && !isCohost(value) && !isSpeaker(key)"
|
2022-12-08 08:56:29 +00:00
|
|
|
:data="cachedMastoData[key]"
|
2022-12-10 03:16:43 +00:00
|
|
|
type="listener"
|
2022-12-08 08:56:29 +00:00
|
|
|
></Participant>
|
|
|
|
</template>
|
|
|
|
</v-row>
|
|
|
|
</v-card-text>
|
|
|
|
<v-divider></v-divider>
|
2022-12-29 14:53:42 +00:00
|
|
|
<v-card-actions v-if="preview" class="justify-center">
|
|
|
|
<v-btn
|
|
|
|
variant="flat"
|
|
|
|
color="indigo"
|
|
|
|
block
|
|
|
|
:to="{ name: 'login', query: { l: `/r/${roomID}` } }"
|
|
|
|
>{{ $t("enterRoom") }}</v-btn
|
|
|
|
>
|
|
|
|
</v-card-actions>
|
|
|
|
<v-card-actions v-else class="justify-center" style="gap: 50px">
|
2022-12-08 08:56:29 +00:00
|
|
|
<v-btn
|
2022-12-09 00:21:17 +00:00
|
|
|
:icon="micStatusIcon"
|
2022-12-08 08:56:29 +00:00
|
|
|
color="white"
|
|
|
|
variant="flat"
|
2022-12-09 00:21:17 +00:00
|
|
|
@click="onToggleMute"
|
|
|
|
></v-btn>
|
2023-01-11 20:53:15 +00:00
|
|
|
<v-btn
|
|
|
|
v-if="iamHost"
|
|
|
|
:icon="mdiLogout"
|
2023-01-11 12:14:11 +00:00
|
|
|
color="red"
|
|
|
|
@click="onRoomClose"
|
|
|
|
variant="flat"
|
|
|
|
></v-btn>
|
2023-01-11 20:53:15 +00:00
|
|
|
<v-btn
|
|
|
|
v-else
|
2022-12-11 05:15:03 +00:00
|
|
|
:icon="mdiLogout"
|
2022-12-09 00:21:17 +00:00
|
|
|
color="red"
|
|
|
|
@click="roomClient.disconnect()"
|
|
|
|
variant="flat"
|
2022-12-08 08:56:29 +00:00
|
|
|
></v-btn>
|
2022-12-10 03:16:43 +00:00
|
|
|
<v-badge
|
|
|
|
v-if="iamHost || iamCohost"
|
|
|
|
color="info"
|
|
|
|
:model-value="speakRequests.size > 0"
|
|
|
|
:content="speakRequests.size"
|
|
|
|
>
|
|
|
|
<v-btn
|
|
|
|
:icon="mdiAccountVoice"
|
|
|
|
variant="flat"
|
|
|
|
color="white"
|
|
|
|
@click="
|
|
|
|
showRequestDialog = true;
|
|
|
|
showRequestNotification = false;
|
|
|
|
"
|
|
|
|
>
|
|
|
|
</v-btn>
|
|
|
|
</v-badge>
|
2022-12-08 08:56:29 +00:00
|
|
|
</v-card-actions>
|
|
|
|
</v-card>
|
|
|
|
</main>
|
2022-12-07 19:03:22 +00:00
|
|
|
</template>
|
2023-01-03 03:58:41 +00:00
|
|
|
|
2023-01-11 20:53:15 +00:00
|
|
|
<style scoped></style>
|