Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/mix.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ struct user {
bool audio;
bool hand;
bool solo;
bool calling;
};

struct chat {
Expand Down
50 changes: 50 additions & 0 deletions src/http.c
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,56 @@ static void http_req_handler(struct http_conn *conn,
return;
}

ROUTE("/api/v1/client/call", "POST")
{
if (sess->user)
sess->user->calling = true;

err = slmix_session_user_updated(sess);
if (err)
goto err;

http_sreply(conn, 204, "OK", "text/html", "", 0, NULL);
return;
}

ROUTE("/api/v1/client/call", "DELETE")
{
char user[512] = {0};
struct pl user_id = PL_INIT;

err = re_regex((char *)mbuf_buf(msg->mb),
mbuf_get_left(msg->mb), "[a-zA-Z0-9@:]+",
&user_id);
if (err)
goto err;

pl_strcpy(&user_id, user, sizeof(user));

struct session *sess_call =
slmix_session_lookup_user_id(&mix->sessl, &user_id);
if (!sess_call) {
warning("http/client/call/delete: user not found %r\n",
&user_id);
goto notfound;
}

if (sess_call != sess) {
/* check host permission */
if (!sess->user || !sess->user->host)
goto auth;
}

sess_call->user->calling = false;

err = slmix_session_user_updated(sess_call);
if (err)
goto err;

http_sreply(conn, 204, "OK", "text/html", "", 0, NULL);
return;
}

ROUTE("/api/v1/client/hangup", "POST")
{
pc_close(sess);
Expand Down
1 change: 1 addition & 0 deletions src/sess.c
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,7 @@ int slmix_session_speaker(struct session *sess, bool enable)
stream_enable(media_get_stream(sess->mvideo), enable);
stream_enable_tx(media_get_stream(sess->mvideo), true);
sess->user->hand = false;
sess->user->calling = false;

/* only allow disable for privacy reasons */
if (!enable)
Expand Down
1 change: 1 addition & 0 deletions src/users.c
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ int user_event_json(char **json, enum user_event event, struct session *sess)
odict_entry_add(o, "hand", ODICT_BOOL, sess->user->hand);
odict_entry_add(o, "solo", ODICT_BOOL, sess->user->solo);
odict_entry_add(o, "webrtc", ODICT_BOOL, sess->pc ? true : false);
odict_entry_add(o, "calling", ODICT_BOOL, sess->user->calling);

err = re_sdprintf(json, "%H", json_encode_odict, o);

Expand Down
37 changes: 37 additions & 0 deletions tests/phpunit/tests/ApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
declare(strict_types=1);

use Tests\Client;
use Tests\ClientAuth;
use Tests\TestCase;
use PHPUnit\Framework\Attributes\TestDox;

Expand Down Expand Up @@ -165,4 +166,40 @@ public function test_chat()

$this->assertEquals("test", $json->chats[0]->msg);
}

#[TestDox('POST/DELETE /api/v1/client/call')]
public function test_client_call()
{
$client = new Client();
$client->login("Alice");

$host= new Client();
$host->login("Bob", ClientAuth::Host);

$client->ws_next(); /* connect websocket */
$host->ws_next(); /* connect websocket */

$r = $client->post("/api/v1/client/call");
$this->assertEquals(204, $r->getStatusCode());

$msg = $host->ws_next("user", "updated");
$this->assertEquals("Alice", $msg->name);
$this->assertEquals(true, $msg->calling);
$user_id = $msg->id;

$msg = $client->ws_next("user", "updated");
$this->assertEquals("Alice", $msg->name);
$this->assertEquals(true, $msg->calling);

$r = $host->delete("/api/v1/client/call", $user_id);
$this->assertEquals(204, $r->getStatusCode());

$msg = $host->ws_next("user", "updated");
$this->assertEquals(false, $msg->calling);
$this->assertEquals($user_id, $msg->id);

$msg = $client->ws_next("user", "updated");
$this->assertEquals(false, $msg->calling);
$this->assertEquals($user_id, $msg->id);
}
}
4 changes: 2 additions & 2 deletions tests/phpunit/tests/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ function get($url)
);
}

function delete($url)
function delete($url, $body = NULL)
{
return $this->client->request(
'DELETE',
$url,
['cookies' => $this->cookies]
['cookies' => $this->cookies, 'body' => $body]
);
}

Expand Down
8 changes: 8 additions & 0 deletions webui/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ export default {
Users.websocket()
},

async call() {
await api_fetch('POST', '/client/call', null)
},

async call_delete(user_id: string) {
await api_fetch('DELETE', '/client/call', user_id)
},

async hangup() {
Webrtc.hangup()
await api_fetch('POST', '/client/hangup', null)
Expand Down
17 changes: 16 additions & 1 deletion webui/src/components/BottomActions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
<!-- Audio Mute -->
<div
v-if="
Webrtc.state.value >= WebrtcState.Listening && (Users.speaker_status.value || !Users.room.value?.show)
Webrtc.state.value >= WebrtcState.Listening && (Users.speaker_status.value)
"
>
<button
Expand Down Expand Up @@ -154,6 +154,16 @@
<PhoneXMarkIcon class="h-10 w-10" />
</button>
</div>
<!-- Hangup -->
<div v-if="Webrtc.state.value >= WebrtcState.Listening && !Users.speaker_status.value">
<button
ref="call"
class="text-green-500 hover:bg-gray-700 group block px-2 py-2 text-base font-medium rounded-md"
@click="call_clicked()"
>
<PhoneIcon class="h-10 w-10" />
</button>
</div>
<!-- Play button -->
<div class="text-gray-600">
<button
Expand Down Expand Up @@ -189,6 +199,7 @@ import api from '../api'
import SettingsModal from '../components/SettingsModal.vue'
import { ref, onMounted } from 'vue'
import {
PhoneIcon,
HandRaisedIcon,
AdjustmentsVerticalIcon,
VideoCameraIcon,
Expand Down Expand Up @@ -244,6 +255,10 @@ function logout_clicked() {
api.logout()
}

function call_clicked() {
api.call()
}

function hangup_clicked() {
api.listener(api.user_id())
}
Expand Down
84 changes: 84 additions & 0 deletions webui/src/components/Calls.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<template>
<div v-if="Users.user.value.calling">
<Dialog class="relative z-20" :open=true>
<div class="fixed inset-0 bg-gray-500/75 transition-opacity" />

<div class="fixed inset-0 z-10 w-screen overflow-y-auto">
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<DialogPanel
class="relative transform overflow-hidden rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
<div class="absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
<button type="button" @click="api.call_delete(Users.user.value.id)"
class="rounded-md bg-white text-gray-400 hover:text-gray-500 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-hidden">
<span class="sr-only">Close</span>
<XMarkIcon class="size-6" aria-hidden="true" />
</button>
</div>
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex size-12 shrink-0 items-center justify-center rounded-full bg-green-100 sm:mx-0 sm:size-10">
<PhoneIcon class="size-6 text-green-600" aria-hidden="true" />
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<DialogTitle as="h3" class="text-base font-semibold text-gray-900">Your call is
waiting...
</DialogTitle>
</div>
</div>
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button type="button"
class="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-xs ring-1 ring-gray-300 ring-inset hover:bg-gray-50 sm:mt-0 sm:w-auto"
@click="api.call_delete(Users.user.value.id)">Cancel</button>
</div>
</DialogPanel>
</div>
</div>
</Dialog>
</div>
<div v-if="Users.host_status.value" v-for="user in Users.calls.value" as="template">
<Dialog class="relative z-20" :open=true>
<div class="fixed inset-0 bg-gray-500/75 transition-opacity" />

<div class="fixed inset-0 z-10 w-screen overflow-y-auto">
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<DialogPanel
class="relative transform overflow-hidden rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
<div class="absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
<button type="button" @click="api.call_delete(user.id)"
class="rounded-md bg-white text-gray-400 hover:text-gray-500 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-hidden">
<span class="sr-only">Close</span>
<XMarkIcon class="size-6" aria-hidden="true" />
</button>
</div>
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex size-12 shrink-0 items-center justify-center rounded-full bg-green-100 sm:mx-0 sm:size-10">
<PhoneIcon class="size-6 text-green-600" aria-hidden="true" />
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<DialogTitle as="h3" class="text-base font-semibold text-gray-900">Call from {{
user.name }}
</DialogTitle>
</div>
</div>
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button type="button"
class="inline-flex w-full justify-center rounded-md bg-green-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-red-500 sm:ml-3 sm:w-auto"
@click="api.speaker(user.id)">Accept</button>
<button type="button"
class="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-xs ring-1 ring-gray-300 ring-inset hover:bg-gray-50 sm:mt-0 sm:w-auto"
@click="api.call_delete(user.id)">Cancel</button>
</div>
</DialogPanel>
</div>
</div>
</Dialog>
</div>
</template>

<script setup lang="ts">
import { Dialog, DialogPanel, DialogTitle } from '@headlessui/vue'
import { PhoneIcon, XMarkIcon } from '@heroicons/vue/24/outline'
import { Users } from '../ws/users'
import api from '../api'
</script>
2 changes: 2 additions & 0 deletions webui/src/views/HomeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<Chat />
</div>
<BottomActions />
<Calls />
</div>
</div>
</template>
Expand All @@ -50,6 +51,7 @@ import Listeners from '../components/Listeners.vue'
import BottomActions from '../components/BottomActions.vue'
import Chat from '../components/Chat.vue'
import RecButton from '../components/RecButton.vue'
import Calls from '../components/Calls.vue'
import api from '../api'
import { onMounted } from 'vue'
//import StudioNav from '../components/StudioNav.vue'
Expand Down
Loading