mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-28 05:24:44 +00:00
android: ability to hide active call (#3770)
* android: ability to hide active call * enhancements * fixed some problems and adapted to lock screen usage * change * reduce diff * dealing with disable PiP by user * fix back action * fix hidden information on view rotation while info collapsed * better info showing * status bar color and user icon * reorder * experiment * icon placement * enhancements * back button * invitation accepted state handling * awesome background work * better service interaction and UI * disabled call overlay when call ends and ability to accept a new call from the same contact while previous call is not ended * incomming call alert * enhancements * text * text2 * top area * faster ending call * a lot of enhancements * paddings * icon position * move icon --------- Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
co-authored by
Evgeny Poberezkin
parent
09bbaa1c94
commit
5da8aef794
@@ -1,16 +1,19 @@
|
||||
package chat.simplex.common
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.common.views.usersettings.SetDeliveryReceiptsView
|
||||
@@ -20,8 +23,7 @@ import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.CreateFirstProfile
|
||||
import chat.simplex.common.views.helpers.SimpleButton
|
||||
import chat.simplex.common.views.SplashView
|
||||
import chat.simplex.common.views.call.ActiveCallView
|
||||
import chat.simplex.common.views.call.IncomingCallAlertView
|
||||
import chat.simplex.common.views.call.*
|
||||
import chat.simplex.common.views.chat.ChatView
|
||||
import chat.simplex.common.views.chatlist.*
|
||||
import chat.simplex.common.views.database.DatabaseErrorView
|
||||
@@ -169,7 +171,17 @@ fun MainScreen() {
|
||||
}
|
||||
} else {
|
||||
if (chatModel.showCallView.value) {
|
||||
ActiveCallView()
|
||||
if (appPlatform.isAndroid) {
|
||||
LaunchedEffect(Unit) {
|
||||
// This if prevents running the activity in the following condition:
|
||||
// - the activity already started before and was destroyed by collapsing active call (start audio call, press back button, go to a launcher)
|
||||
if (!chatModel.activeCallViewIsCollapsed.value) {
|
||||
platform.androidStartCallActivity(false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ActiveCallView()
|
||||
}
|
||||
} else {
|
||||
// It's needed for privacy settings toggle, so it can be shown even if the app is passcode unlocked
|
||||
ModalManager.fullscreen.showPasscodeInView()
|
||||
@@ -206,9 +218,13 @@ fun MainScreen() {
|
||||
}
|
||||
}
|
||||
|
||||
val ANDROID_CALL_TOP_PADDING = 40.dp
|
||||
|
||||
@Composable
|
||||
fun AndroidScreen(settingsState: SettingsViewState) {
|
||||
BoxWithConstraints {
|
||||
val call = remember { chatModel.activeCall} .value
|
||||
val showCallArea = call != null && call.callState != CallState.WaitCapabilities && call.callState != CallState.InvitationAccepted
|
||||
var currentChatId by rememberSaveable { mutableStateOf(chatModel.chatId.value) }
|
||||
val offset = remember { Animatable(if (chatModel.chatId.value == null) 0f else maxWidth.value) }
|
||||
Box(
|
||||
@@ -216,6 +232,7 @@ fun AndroidScreen(settingsState: SettingsViewState) {
|
||||
.graphicsLayer {
|
||||
translationX = -offset.value.dp.toPx()
|
||||
}
|
||||
.padding(top = if (showCallArea) ANDROID_CALL_TOP_PADDING else 0.dp)
|
||||
) {
|
||||
StartPartOfScreen(settingsState)
|
||||
}
|
||||
@@ -242,11 +259,17 @@ fun AndroidScreen(settingsState: SettingsViewState) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Box(Modifier.graphicsLayer { translationX = maxWidth.toPx() - offset.value.dp.toPx() }) Box2@{
|
||||
Box(Modifier
|
||||
.graphicsLayer { translationX = maxWidth.toPx() - offset.value.dp.toPx() }
|
||||
.padding(top = if (showCallArea) ANDROID_CALL_TOP_PADDING else 0.dp)
|
||||
) Box2@{
|
||||
currentChatId?.let {
|
||||
ChatView(it, chatModel, onComposed)
|
||||
}
|
||||
}
|
||||
if (call != null && showCallArea) {
|
||||
ActiveCallInteractiveArea(call, remember { MutableStateFlow(AnimatedViewState.GONE) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ object ChatModel {
|
||||
val activeCallInvitation = mutableStateOf<RcvCallInvitation?>(null)
|
||||
val activeCall = mutableStateOf<Call?>(null)
|
||||
val activeCallViewIsVisible = mutableStateOf<Boolean>(false)
|
||||
val activeCallViewIsCollapsed = mutableStateOf<Boolean>(false)
|
||||
val callCommand = mutableStateListOf<WCallCommand>()
|
||||
val showCallView = mutableStateOf(false)
|
||||
val switchingCall = mutableStateOf(false)
|
||||
|
||||
+2
-4
@@ -1900,10 +1900,8 @@ object ChatController {
|
||||
if (invitation != null) {
|
||||
chatModel.callManager.reportCallRemoteEnded(invitation = invitation)
|
||||
}
|
||||
withCall(r, r.contact) { _ ->
|
||||
chatModel.callCommand.add(WCallCommand.End)
|
||||
chatModel.activeCall.value = null
|
||||
chatModel.showCallView.value = false
|
||||
withCall(r, r.contact) { call ->
|
||||
withBGApi { chatModel.callManager.endCall(call) }
|
||||
}
|
||||
}
|
||||
is CR.ContactSwitch ->
|
||||
|
||||
+5
@@ -1,16 +1,21 @@
|
||||
package chat.simplex.common.platform
|
||||
|
||||
import chat.simplex.common.model.ChatId
|
||||
import chat.simplex.common.model.NotificationsMode
|
||||
|
||||
interface PlatformInterface {
|
||||
suspend fun androidServiceStart() {}
|
||||
fun androidServiceSafeStop() {}
|
||||
fun androidCallServiceSafeStop() {}
|
||||
fun androidNotificationsModeChanged(mode: NotificationsMode) {}
|
||||
fun androidChatStartedAfterBeingOff() {}
|
||||
fun androidChatStopped() {}
|
||||
fun androidChatInitializedAndStarted() {}
|
||||
fun androidIsBackgroundCallAllowed(): Boolean = true
|
||||
fun androidSetNightModeIfSupported() {}
|
||||
fun androidStartCallActivity(acceptCall: Boolean, remoteHostId: Long? = null, chatId: ChatId? = null) {}
|
||||
fun androidPictureInPictureAllowed(): Boolean = true
|
||||
fun androidCallEnded() {}
|
||||
suspend fun androidAskToAllowBackgroundCalls(): Boolean = true
|
||||
}
|
||||
/**
|
||||
|
||||
+27
-19
@@ -1,6 +1,6 @@
|
||||
package chat.simplex.common.views.call
|
||||
|
||||
import chat.simplex.common.model.ChatModel
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.views.helpers.withBGApi
|
||||
import kotlinx.datetime.Clock
|
||||
@@ -23,27 +23,29 @@ class CallManager(val chatModel: ChatModel) {
|
||||
}
|
||||
}
|
||||
|
||||
fun acceptIncomingCall(invitation: RcvCallInvitation) {
|
||||
fun acceptIncomingCall(invitation: RcvCallInvitation) = withBGApi {
|
||||
val call = chatModel.activeCall.value
|
||||
if (call == null) {
|
||||
justAcceptIncomingCall(invitation = invitation)
|
||||
val contactInfo = chatModel.controller.apiContactInfo(invitation.remoteHostId, invitation.contact.contactId)
|
||||
val profile = contactInfo?.second ?: invitation.user.profile.toProfile()
|
||||
// In case the same contact calling while previous call didn't end yet (abnormal ending of call from the other side)
|
||||
if (call == null || (call.remoteHostId == invitation.remoteHostId && call.contact.id == invitation.contact.id)) {
|
||||
justAcceptIncomingCall(invitation = invitation, profile)
|
||||
} else {
|
||||
withBGApi {
|
||||
chatModel.switchingCall.value = true
|
||||
try {
|
||||
endCall(call = call)
|
||||
justAcceptIncomingCall(invitation = invitation)
|
||||
} finally {
|
||||
chatModel.switchingCall.value = false
|
||||
}
|
||||
chatModel.switchingCall.value = true
|
||||
try {
|
||||
endCall(call = call)
|
||||
justAcceptIncomingCall(invitation = invitation, profile)
|
||||
} finally {
|
||||
chatModel.switchingCall.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun justAcceptIncomingCall(invitation: RcvCallInvitation) {
|
||||
private fun justAcceptIncomingCall(invitation: RcvCallInvitation, userProfile: Profile) {
|
||||
with (chatModel) {
|
||||
activeCall.value = Call(
|
||||
remoteHostId = invitation.remoteHostId,
|
||||
userProfile = userProfile,
|
||||
contact = invitation.contact,
|
||||
callState = CallState.InvitationAccepted,
|
||||
localMedia = invitation.callType.media,
|
||||
@@ -68,17 +70,23 @@ class CallManager(val chatModel: ChatModel) {
|
||||
}
|
||||
|
||||
suspend fun endCall(call: Call) {
|
||||
with (chatModel) {
|
||||
with(chatModel) {
|
||||
// If there is active call currently and it's with other contact, don't interrupt it
|
||||
if (activeCall.value != null && !(activeCall.value?.remoteHostId == call.remoteHostId && activeCall.value?.contact?.id == call.contact.id)) return
|
||||
|
||||
// Don't destroy WebView if you plan to accept next call right after this one
|
||||
if (!switchingCall.value) {
|
||||
showCallView.value = false
|
||||
activeCall.value = null
|
||||
activeCallViewIsCollapsed.value = false
|
||||
platform.androidCallEnded()
|
||||
}
|
||||
if (call.callState == CallState.Ended) {
|
||||
Log.d(TAG, "CallManager.endCall: call ended")
|
||||
activeCall.value = null
|
||||
showCallView.value = false
|
||||
} else {
|
||||
Log.d(TAG, "CallManager.endCall: ending call...")
|
||||
callCommand.add(WCallCommand.End)
|
||||
showCallView.value = false
|
||||
//callCommand.add(WCallCommand.End)
|
||||
controller.apiEndCall(call.remoteHostId, call.contact)
|
||||
activeCall.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-2
@@ -7,11 +7,11 @@ import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.net.URI
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
data class Call(
|
||||
val remoteHostId: Long?,
|
||||
val userProfile: Profile,
|
||||
val contact: Contact,
|
||||
val callState: CallState,
|
||||
val localMedia: CallMediaType,
|
||||
@@ -23,7 +23,7 @@ data class Call(
|
||||
val soundSpeaker: Boolean = localMedia == CallMediaType.Video,
|
||||
var localCamera: VideoCamera = VideoCamera.User,
|
||||
val connectionInfo: ConnectionInfo? = null,
|
||||
var connectedAt: Instant? = null
|
||||
var connectedAt: Instant? = null,
|
||||
) {
|
||||
val encrypted: Boolean get() = localEncrypted && sharedKey != null
|
||||
val localEncrypted: Boolean get() = localCapabilities?.encryption ?: false
|
||||
@@ -36,6 +36,9 @@ data class Call(
|
||||
}
|
||||
|
||||
val hasMedia: Boolean get() = callState == CallState.OfferSent || callState == CallState.Negotiated || callState == CallState.Connected
|
||||
|
||||
fun supportsVideo(): Boolean = peerMedia == CallMediaType.Video || localMedia == CallMediaType.Video
|
||||
|
||||
}
|
||||
|
||||
enum class CallState {
|
||||
@@ -75,6 +78,7 @@ sealed class WCallCommand {
|
||||
@Serializable @SerialName("media") data class Media(val media: CallMediaType, val enable: Boolean): WCallCommand()
|
||||
@Serializable @SerialName("camera") data class Camera(val camera: VideoCamera): WCallCommand()
|
||||
@Serializable @SerialName("description") data class Description(val state: String, val description: String): WCallCommand()
|
||||
@Serializable @SerialName("layout") data class Layout(val layout: LayoutType): WCallCommand()
|
||||
@Serializable @SerialName("end") object End: WCallCommand()
|
||||
}
|
||||
|
||||
@@ -167,6 +171,13 @@ enum class VideoCamera {
|
||||
val flipped: VideoCamera get() = if (this == User) Environment else User
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class LayoutType {
|
||||
@SerialName("default") Default,
|
||||
@SerialName("localVideo") LocalVideo,
|
||||
@SerialName("remoteVideo") RemoteVideo
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ConnectionState(
|
||||
val connectionState: String,
|
||||
|
||||
+4
-2
@@ -301,7 +301,9 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
||||
withBGApi {
|
||||
val cInfo = chat.chatInfo
|
||||
if (cInfo is ChatInfo.Direct) {
|
||||
chatModel.activeCall.value = Call(remoteHostId = chatRh, contact = cInfo.contact, callState = CallState.WaitCapabilities, localMedia = media)
|
||||
val contactInfo = chatModel.controller.apiContactInfo(chat.remoteHostId, cInfo.contact.contactId)
|
||||
val profile = contactInfo?.second ?: chatModel.currentUser.value?.profile?.toProfile() ?: return@withBGApi
|
||||
chatModel.activeCall.value = Call(remoteHostId = chatRh, contact = cInfo.contact, callState = CallState.WaitCapabilities, localMedia = media, userProfile = profile)
|
||||
chatModel.showCallView.value = true
|
||||
chatModel.callCommand.add(WCallCommand.Capabilities(media))
|
||||
}
|
||||
@@ -673,7 +675,7 @@ fun ChatInfoToolbar(
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (activeCall?.contact?.id == chat.id) {
|
||||
} else if (activeCall?.contact?.id == chat.id && appPlatform.isDesktop) {
|
||||
barButtons.add {
|
||||
val call = remember { chatModel.activeCall }.value
|
||||
val connectedAt = call?.connectedAt
|
||||
|
||||
+8
-2
@@ -29,6 +29,7 @@ import chat.simplex.common.views.onboarding.WhatsNewView
|
||||
import chat.simplex.common.views.onboarding.shouldShowWhatsNew
|
||||
import chat.simplex.common.views.usersettings.SettingsView
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.views.call.Call
|
||||
import chat.simplex.common.views.newchat.*
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.*
|
||||
@@ -121,7 +122,12 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf
|
||||
}
|
||||
}
|
||||
if (searchText.value.text.isEmpty()) {
|
||||
DesktopActiveCallOverlayLayout(newChatSheetState)
|
||||
if (appPlatform.isDesktop) {
|
||||
val call = remember { chatModel.activeCall }.value
|
||||
if (call != null) {
|
||||
ActiveCallInteractiveArea(call, newChatSheetState)
|
||||
}
|
||||
}
|
||||
// TODO disable this button and sheet for the duration of the switch
|
||||
tryOrShowError("NewChatSheet", error = {}) {
|
||||
NewChatSheet(chatModel, newChatSheetState, stopped, hideNewChatSheet)
|
||||
@@ -314,7 +320,7 @@ private fun ToggleFilterDisabledButton() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
expect fun DesktopActiveCallOverlayLayout(newChatSheetState: MutableStateFlow<AnimatedViewState>)
|
||||
expect fun ActiveCallInteractiveArea(call: Call, newChatSheetState: MutableStateFlow<AnimatedViewState>)
|
||||
|
||||
fun connectIfOpenedViaUri(rhId: Long?, uri: URI, chatModel: ChatModel) {
|
||||
Log.d(TAG, "connectIfOpenedViaUri: opened via link")
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ private fun ShareListToolbar(chatModel: ChatModel, userPickerState: MutableState
|
||||
userPickerState.value = AnimatedViewState.VISIBLE
|
||||
}
|
||||
}
|
||||
else -> NavigationButtonBack { chatModel.sharedContent.value = null }
|
||||
else -> NavigationButtonBack(onButtonClicked = { chatModel.sharedContent.value = null })
|
||||
}
|
||||
}
|
||||
if (chatModel.chats.size >= 8) {
|
||||
|
||||
+2
-2
@@ -18,7 +18,7 @@ import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
|
||||
@Composable
|
||||
fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}) {
|
||||
fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, tintColor: Color = if (close != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, endButtons: @Composable RowScope.() -> Unit = {}) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -35,7 +35,7 @@ fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, endButtons: @
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
if (showClose) {
|
||||
NavigationButtonBack(onButtonClicked = close)
|
||||
NavigationButtonBack(tintColor = tintColor, onButtonClicked = close)
|
||||
} else {
|
||||
Spacer(Modifier)
|
||||
}
|
||||
|
||||
+2
-2
@@ -44,10 +44,10 @@ fun DefaultTopAppBar(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NavigationButtonBack(onButtonClicked: (() -> Unit)?) {
|
||||
fun NavigationButtonBack(onButtonClicked: (() -> Unit)?, tintColor: Color = if (onButtonClicked != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary) {
|
||||
IconButton(onButtonClicked ?: {}, enabled = onButtonClicked != null) {
|
||||
Icon(
|
||||
painterResource(MR.images.ic_arrow_back_ios_new), stringResource(MR.strings.back), tint = if (onButtonClicked != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
|
||||
painterResource(MR.images.ic_arrow_back_ios_new), stringResource(MR.strings.back), tint = tintColor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ fun ModalView(
|
||||
}
|
||||
Surface(Modifier.fillMaxSize(), contentColor = LocalContentColor.current) {
|
||||
Column(if (background != MaterialTheme.colors.background) Modifier.background(background) else Modifier.themedBackground()) {
|
||||
CloseSheetBar(close, showClose, endButtons)
|
||||
CloseSheetBar(close, showClose, endButtons = endButtons)
|
||||
Box(modifier) { content() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +179,9 @@
|
||||
<!-- SimpleX Chat foreground Service -->
|
||||
<string name="simplex_service_notification_title">SimpleX Chat service</string>
|
||||
<string name="simplex_service_notification_text">Receiving messages…</string>
|
||||
<string name="call_service_notification_audio_call">Audio call</string>
|
||||
<string name="call_service_notification_video_call">Video call</string>
|
||||
<string name="call_service_notification_end_call">End call</string>
|
||||
<string name="hide_notification">Hide</string>
|
||||
|
||||
<!-- Notification channels -->
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<body>
|
||||
<video
|
||||
id="remote-video-stream"
|
||||
class="inline"
|
||||
autoplay
|
||||
playsinline
|
||||
poster="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAEUlEQVR42mNk+M+AARiHsiAAcCIKAYwFoQ8AAAAASUVORK5CYII="
|
||||
@@ -15,6 +16,7 @@
|
||||
></video>
|
||||
<video
|
||||
id="local-video-stream"
|
||||
class="inline"
|
||||
muted
|
||||
autoplay
|
||||
playsinline
|
||||
|
||||
@@ -5,14 +5,14 @@ body {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
#remote-video-stream {
|
||||
#remote-video-stream.inline {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
#local-video-stream {
|
||||
#local-video-stream.inline {
|
||||
position: absolute;
|
||||
width: 30%;
|
||||
max-width: 30%;
|
||||
@@ -23,6 +23,20 @@ body {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#remote-video-stream.fullscreen {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
#local-video-stream.fullscreen {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
*::-webkit-media-controls {
|
||||
display: none !important;
|
||||
-webkit-appearance: none !important;
|
||||
|
||||
@@ -11,6 +11,12 @@ var VideoCamera;
|
||||
VideoCamera["User"] = "user";
|
||||
VideoCamera["Environment"] = "environment";
|
||||
})(VideoCamera || (VideoCamera = {}));
|
||||
var LayoutType;
|
||||
(function (LayoutType) {
|
||||
LayoutType["Default"] = "default";
|
||||
LayoutType["LocalVideo"] = "localVideo";
|
||||
LayoutType["RemoteVideo"] = "remoteVideo";
|
||||
})(LayoutType || (LayoutType = {}));
|
||||
// for debugging
|
||||
// var sendMessageToNative = ({resp}: WVApiMessage) => console.log(JSON.stringify({command: resp}))
|
||||
var sendMessageToNative = (msg) => console.log(JSON.stringify(msg));
|
||||
@@ -319,6 +325,10 @@ const processCommand = (function () {
|
||||
localizedDescription = command.description;
|
||||
resp = { type: "ok" };
|
||||
break;
|
||||
case "layout":
|
||||
changeLayout(command.layout);
|
||||
resp = { type: "ok" };
|
||||
break;
|
||||
case "end":
|
||||
endCall();
|
||||
resp = { type: "ok" };
|
||||
@@ -607,6 +617,28 @@ function toggleMedia(s, media) {
|
||||
}
|
||||
return res;
|
||||
}
|
||||
function changeLayout(layout) {
|
||||
const local = document.getElementById("local-video-stream");
|
||||
const remote = document.getElementById("remote-video-stream");
|
||||
switch (layout) {
|
||||
case LayoutType.Default:
|
||||
local.className = "inline";
|
||||
remote.className = "inline";
|
||||
local.style.visibility = "visible";
|
||||
remote.style.visibility = "visible";
|
||||
break;
|
||||
case LayoutType.LocalVideo:
|
||||
local.className = "fullscreen";
|
||||
local.style.visibility = "visible";
|
||||
remote.style.visibility = "hidden";
|
||||
break;
|
||||
case LayoutType.RemoteVideo:
|
||||
remote.className = "fullscreen";
|
||||
local.style.visibility = "hidden";
|
||||
remote.style.visibility = "visible";
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Cryptography function - it is loaded both in the main window and in worker context (if the worker is used)
|
||||
function callCryptoFunction() {
|
||||
const initialPlainTextRequired = {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<body>
|
||||
<video
|
||||
id="remote-video-stream"
|
||||
class="inline"
|
||||
autoplay
|
||||
playsinline
|
||||
poster="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAEUlEQVR42mNk+M+AARiHsiAAcCIKAYwFoQ8AAAAASUVORK5CYII="
|
||||
@@ -16,6 +17,7 @@
|
||||
></video>
|
||||
<video
|
||||
id="local-video-stream"
|
||||
class="inline"
|
||||
muted
|
||||
autoplay
|
||||
playsinline
|
||||
|
||||
@@ -5,14 +5,14 @@ body {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
#remote-video-stream {
|
||||
#remote-video-stream.inline {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
#local-video-stream {
|
||||
#local-video-stream.inline {
|
||||
position: absolute;
|
||||
width: 20%;
|
||||
max-width: 20%;
|
||||
@@ -23,6 +23,20 @@ body {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#remote-video-stream.fullscreen {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
#local-video-stream.fullscreen {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
*::-webkit-media-controls {
|
||||
display: none !important;
|
||||
-webkit-appearance: none !important;
|
||||
|
||||
Reference in New Issue
Block a user