rendering call video on webapp

This commit is contained in:
balzack 2025-02-06 16:44:09 -08:00
parent cda9f6078a
commit 35f2c7d58d
4 changed files with 103 additions and 59 deletions

View File

@ -2,65 +2,64 @@
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 10;
}
.inactive {
display: none;
}
.titleView {
position: absolute;
width: 100%;
height: 100%;
padding-top: 32px;
display: flex;
flex-direction: column;
align-items: center;
.titleName {
font-size: 32px;
padding-bottom: 32px;
}
.image {
height: 50%;
width: 50%;
display: flex;
justify-content: center;
.frame {
width: fit-content;
height: 100%;
}
.logo {
width: 100%;
height: 100%;
}
}
.titleStatus {
font-size: 24;
padding-top: 16px;
}
}
.buttons {
background-color: var(--mantine-color-surface-2);
position: absolute;
bottom: 92px;
opacity: 0.8;
padding: 16px;
border-radius: 8px;
gap: 16px;
display: flex;
}
.call {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.controls {
display: flex;
flex-direction: row;
align-items: center;
position: absolute;
bottom: 10%;
padding: 8;
gap: 12;
border-radius: 8;
opacity: 0.7;
}
.full {
width: 100%;
height: 100%;
}
.box {
position: absolute;
top: 10%;
right: 10%;
width: 20%;
height: 20%;
}
.titleView {
position: absolute;
top: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
paddingTop: 92;
}
.titleName {
font-size: 24;
padding-bottom: 32;
}
.titleImage {
width: 80%;
height: auto;
aspect-ratio: 1;
border-radius: 8;
}
.duration {
padding-top: 16;
font-size: 20;
}
.logoView {
height: 100%;
width: auto;
aspect-ratio: 1;
background-color: var(--mantine-color-surface-4);
}

View File

@ -4,6 +4,8 @@ import classes from './Call.module.css'
import { Card as Contact } from '../card/Card';
import { Colors } from '../constants/Colors';
import { modals } from '@mantine/modals'
import { Image, Text, ActionIcon } from '@mantine/core'
import { IconPhone, IconMicrophone, IconMicrophoneOff, IconVideo, IconVideoOff } from '@tabler/icons-react'
export function Call() {
const { state, actions } = useCall();
@ -76,7 +78,47 @@ export function Call() {
}
return (
<div />
<div className={(state.calling && state.fullscreen) ? classes.active : classes.inactive}>
{ state.calling && (
<div className={classes.call}>
<div className={classes.titleView}>
{ state.calling.name && (
<Text className={classes.titleName}>{ state.calling.name }</Text>
)}
{ !state.calling.name && (
<Text className={classes.titleName}>{ `${state.calling.handle}/${state.calling.node}` }</Text>
)}
<div className={classes.image}>
<div className={classes.frame}>
<Image radius="lg" fit="contain" className={classes.logo} src={state.calling.imageUrl} />
</div>
</div>
<Text className={classes.titleStatus}>{ `${Math.floor(state.duration/60)}:${(state.duration % 60).toString().padStart(2, '0')}` }</Text>
</div>
<div className={classes.buttons}>
<ActionIcon onClick={toggleAudio} disabled={!state.connected} loading={applyingAudio} color={Colors.primary} size="xl">
{ state.audioEnabled && (
<IconMicrophone />
)}
{ !state.audioEnabled && (
<IconMicrophoneOff />
)}
</ActionIcon>
<ActionIcon onClick={toggleVideo} disabled={!state.connected} loading={applyingVideo} color={Colors.primary} size="xl">
{ state.videoEnabled && (
<IconVideo />
)}
{ !state.videoEnabled && (
<IconVideoOff />
)}
</ActionIcon>
<ActionIcon onClick={end} color={Colors.offsync} size="xl"><IconPhone className={classes.off} /></ActionIcon>
</div>
</div>
)}
</div>
);
}

View File

@ -86,7 +86,7 @@ export function useRingContext() {
console.log(err);
}
} else if (status === 'closed') {
await cleanup;
await cleanup();
}
}
}
@ -190,6 +190,7 @@ export function useRingContext() {
}
const cleanup = async () => {
console.log("CLEANUP!");
closing.current = true;
while (updatingPeer.current || connecting.current) {
await new Promise((r) => setTimeout(r, CLOSE_POLL_MS));
@ -213,6 +214,7 @@ export function useRingContext() {
peerUpdate.current = [];
updateState({ calling: null, connected: false, connectedTime: 0, failed: false, localStream: null, remoteStream: null, localVideo: false, remoteVideo: false });
closing.current = false;
console.log("!!");
}
const transmit = (ice: { urls: string; username: string; credential: string }[]) => {

View File

@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import { useRing } from './useRing.hook';
import classes from './Ring.module.css';
import { Card as Contact } from '../card/Card';
import { Card } from 'databag-client-sdk';
import { Colors } from '../constants/Colors';
import { modals } from '@mantine/modals'
import { Loader, Image, Text, ActionIcon } from '@mantine/core'
@ -59,7 +60,7 @@ export function Ring() {
}
}
const accept = async (callId, card) => {
const accept = async (callId: string, card: Card) => {
if (!accepting) {
setAccepting(callId);
try {
@ -72,7 +73,7 @@ export function Ring() {
}
}
const ignore = async (callId, card) => {
const ignore = async (callId: string, card: Card) => {
if (!ignoring) {
setIgnoring(callId);
try {
@ -85,7 +86,7 @@ export function Ring() {
}
}
const decline = async (callId, card) => {
const decline = async (callId: string, card: Card) => {
if (!declining) {
setDeclining(callId);
try {
@ -124,7 +125,7 @@ export function Ring() {
)}
{ state.calling && (
<div className={classes.ring}>
<ActionIcon variant="subtle" loading={applyingAudio} disabled={!state.connected} className={classes.circleIcon} color={Colors.primary}>
<ActionIcon variant="subtle" loading={applyingAudio} disabled={!state.connected} className={classes.circleIcon} color={Colors.primary} onClick={toggleAudio}>
{ state.audioEnabled && (
<IconMicrophone />
)}
@ -132,7 +133,7 @@ export function Ring() {
<IconMicrophoneOff />
)}
</ActionIcon>
<ActionIcon variant="subtle" disabled={!state.connected} className={classes.circleIcon} color={Colors.confirmed}>
<ActionIcon variant="subtle" disabled={!state.connected} className={classes.circleIcon} color={Colors.confirmed} onClick={()=>actions.setFullscreen(true)}>
<IconArrowsMaximize />
</ActionIcon>
<div className={classes.name}>
@ -140,7 +141,7 @@ export function Ring() {
<Text className={classes.nameSet}>{ state.calling.name }</Text>
)}
{ !state.calling.name && (
<Text className={classs.nameUnset}>{ state.strings.name }</Text>
<Text className={classes.nameUnset}>{ state.strings.name }</Text>
)}
</div>
<div className={classes.status}>