Skip to content

Commit ac66fa7

Browse files
Merge pull request #696 from 100mslive/dev
dev
2 parents 6816955 + 6d639ac commit ac66fa7

25 files changed

+465
-125
lines changed

app/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ dependencies {
6262
implementation "live.100ms:room-kit:$HMS_ROOM_KIT_VERSION"
6363

6464
//100ms noise cancellation dep
65-
def hmsVersion = "2.9.53"
65+
def hmsVersion = "2.9.54"
6666
implementation "live.100ms:hms-noise-cancellation-android:$hmsVersion"
6767

6868
// Navigation

app/src/main/AndroidManifest.xml

-7
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,9 @@
55
<uses-feature android:name="android.hardware.camera" />
66
<uses-feature android:name="android.hardware.camera.autofocus" />
77
<uses-permission android:name="android.permission.VIBRATE"/>
8-
<uses-permission android:name="android.permission.CAMERA" />
9-
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
10-
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
11-
<uses-permission android:name="android.permission.RECORD_AUDIO" />
128
<uses-permission
139
android:name="android.permission.BLUETOOTH"
1410
android:maxSdkVersion="30" />
15-
<uses-permission android:name="android.permission.INTERNET" />
16-
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
17-
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
1811
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
1912
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
2013
<!--Permissions needed for Android 14 -->

app/src/main/java/live/hms/app2/ui/home/HomeFragment.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -127,17 +127,17 @@ class HomeFragment : Fragment() {
127127
return when {
128128
REGEX_MEETING_URL_CODE.matches(url) -> {
129129
val groups = REGEX_MEETING_URL_CODE.findAll(url).toList()[0].groupValues
130-
groups[2]
130+
groups[groups.size - 1]
131131
}
132132
REGEX_STREAMING_MEETING_URL_ROOM_CODE.matches(url) -> {
133133
val groups =
134134
REGEX_STREAMING_MEETING_URL_ROOM_CODE.findAll(url).toList()[0].groupValues
135-
groups[2]
135+
groups[groups.size - 1]
136136

137137
}
138138
REGEX_PREVIEW_URL_CODE.matches(url) -> {
139139
val groups = REGEX_PREVIEW_URL_CODE.findAll(url).toList()[0].groupValues
140-
groups[2]
140+
groups[groups.size - 1]
141141
}
142142
else -> null
143143
}

app/src/main/java/live/hms/app2/util/Constants.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ const val ENV_QA = "qa-init"
55

66
val REGEX_MEETING_URL_CODE = Regex("https?://(.*.100ms.live)/meeting/([a-zA-Z0-9]+-[a-zA-Z0-9]+-[a-zA-Z0-9]+)/?")
77
val REGEX_PREVIEW_URL_CODE = Regex("https?://(.*.100ms.live)/preview/([a-zA-Z0-9]+-[a-zA-Z0-9]+-[a-zA-Z0-9]+)/?")
8-
val REGEX_STREAMING_MEETING_URL_ROOM_CODE = Regex("https?://(.*.100ms.live)/streaming/meeting/([a-zA-Z0-9]+-[a-zA-Z0-9]+-[a-zA-Z0-9]+)/?")
9-
val REGEX_MEETING_URL_ROOM_ID = Regex("https?://(.*.100ms.live)/meeting/([a-zA-Z0-9]+)/([a-zA-Z0-9]+)/?")
8+
val REGEX_STREAMING_MEETING_URL_ROOM_CODE = Regex("https?://(.*.100ms.live)/streaming/((meeting)|(preview))/([a-zA-Z0-9]+-[a-zA-Z0-9]+-[a-zA-Z0-9]+)/?")
9+
val REGEX_MEETING_URL_ROOM_ID = Regex("https?://(.*.100ms.live)/((meeting)|(preview))/([a-zA-Z0-9]+)/([a-zA-Z0-9]+)/?")
1010
val REGEX_TOKEN_ENDPOINT = Regex("https?://.*.100ms.live/hmsapi/([a-zA-Z0-9-.]+.100ms.live)/?")
1111

1212
val REGEX_MEETING_CODE = Regex("^[a-zA-Z0-9]+-[a-zA-Z0-9]+-[a-zA-Z0-9]+$")

app/src/main/java/live/hms/app2/util/MeetingUrlUtils.kt

-14
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,6 @@ fun String.getInitEndpointEnvironment(): String = when {
1616
else -> ENV_QA
1717
}
1818

19-
fun String.toUniqueRoomSpecifier(): String {
20-
require(this.isValidMeetingUrl()) {
21-
"$this is not a valid meeting-url"
22-
}
23-
24-
return if (REGEX_MEETING_URL_CODE.matches(this)) {
25-
val groups = REGEX_MEETING_URL_CODE.findAll(this).toList()[0].groupValues
26-
groups[2]
27-
} else /* if (REGEX_MEETING_URL_ROOM_ID.matches(this)) */ {
28-
val groups = REGEX_MEETING_URL_ROOM_ID.findAll(this).toList()[0].groupValues
29-
groups[2]
30-
}
31-
}
32-
3319
fun getBeamBotJoiningUrl(meetingUrl: String, roomId: String, beamBotUser: String): String {
3420
return "https://${meetingUrl.toSubdomain()}/preview/$roomId/$beamBotUser?token=beam_recording"
3521
}

app/src/test/java/live/hms/app2/ui/meeting/CustomPeerMetadataTest.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package live.hms.app2.ui.meeting
22

3+
import live.hms.roomkit.ui.meeting.CustomPeerMetadata
34
import org.hamcrest.MatcherAssert.assertThat
45
import org.hamcrest.Matchers.equalTo
56
import org.junit.Test
@@ -9,7 +10,7 @@ class CustomPeerMetadataTest{
910
@Test
1011
fun correct_json_values_are_parsed() {
1112
val result = CustomPeerMetadata.fromJson("{\"isHandRaised\":true, \"name\":\"Aniket\"}")
12-
assertThat(result, equalTo(CustomPeerMetadata(true, "Aniket")))
13+
assertThat(result, equalTo(CustomPeerMetadata(true, false, "Aniket")))
1314
}
1415

1516
@Test
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package live.hms.app2.util
2+
3+
import org.hamcrest.CoreMatchers.equalTo
4+
import org.hamcrest.MatcherAssert.assertThat
5+
import org.junit.Test
6+
7+
class MeetingUrlUtilsKtTest {
8+
@Test
9+
fun `meeting streaming works`() {
10+
assertThat("https://masterclass.app.100ms.live/streaming/meeting/amp-pat-cat".isValidMeetingUrl(), equalTo(true))
11+
}
12+
13+
@Test
14+
fun `streaming preview links work`() {
15+
assertThat("https://masterclass.app.100ms.live/streaming/preview/amp-pat-cat".isValidMeetingUrl(), equalTo(true))
16+
}
17+
18+
@Test
19+
fun `regular meeting links work`() {
20+
assertThat("https://aniket.app.100ms.live/meeting/amp-pat-cat".isValidMeetingUrl(), equalTo(true))
21+
}
22+
23+
@Test
24+
fun `regular preview links work`() {
25+
assertThat("https://aniket.app.100ms.live/preview/amp-pat-cat".isValidMeetingUrl(), equalTo(true))
26+
}
27+
28+
@Test
29+
fun `broken links do not work`() {
30+
assertThat("https://aniket.app.100ms.live/preview/milk/amp-pat-cat".isValidMeetingUrl(), equalTo(false))
31+
}
32+
}

gradle.properties

+3-3
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ android.useAndroidX=true
1919
android.enableJetifier=true
2020
# Kotlin code style for this project: "official" or "obsolete":
2121
kotlin.code.style=official
22-
100MS_APP_VERSION_CODE=370
23-
100MS_APP_VERSION_NAME=5.0.4
22+
100MS_APP_VERSION_CODE=371
23+
100MS_APP_VERSION_NAME=5.0.5
2424
hmsRoomKitGroup=live.100ms
25-
HMS_ROOM_KIT_VERSION=1.2.0
25+
HMS_ROOM_KIT_VERSION=1.2.1
2626
android.suppressUnsupportedCompileSdk=33

room-kit/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ dependencies {
7272
implementation 'com.google.android.material:material:1.10.0'
7373
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
7474
implementation 'androidx.percentlayout:percentlayout:1.0.0'
75-
def hmsVersion = "2.9.53"
75+
def hmsVersion = "2.9.54"
7676
implementation "com.otaliastudios:zoomlayout:1.9.0"
7777
// To add dependencies of specific module
7878
implementation "live.100ms:android-sdk:$hmsVersion"

room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingFragment.kt

+110-14
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,30 @@ import android.widget.Toast
2525
import androidx.activity.OnBackPressedCallback
2626
import androidx.activity.result.contract.ActivityResultContracts
2727
import androidx.appcompat.app.AlertDialog
28+
import androidx.compose.foundation.background
29+
import androidx.compose.foundation.layout.Arrangement
30+
import androidx.compose.foundation.layout.Box
31+
import androidx.compose.foundation.layout.Column
32+
import androidx.compose.foundation.layout.fillMaxWidth
33+
import androidx.compose.foundation.layout.padding
34+
import androidx.compose.foundation.layout.requiredHeightIn
35+
import androidx.compose.foundation.rememberScrollState
36+
import androidx.compose.foundation.verticalScroll
37+
import androidx.compose.material3.Text
38+
import androidx.compose.runtime.Composable
39+
import androidx.compose.runtime.LaunchedEffect
40+
import androidx.compose.runtime.getValue
41+
import androidx.compose.runtime.livedata.observeAsState
42+
import androidx.compose.ui.Modifier
43+
import androidx.compose.ui.draw.clip
44+
import androidx.compose.ui.graphics.toArgb
45+
import androidx.compose.ui.platform.ComposeView
46+
import androidx.compose.ui.platform.ViewCompositionStrategy
47+
import androidx.compose.ui.text.AnnotatedString
48+
import androidx.compose.ui.text.TextStyle
49+
import androidx.compose.ui.tooling.preview.Preview
50+
import androidx.compose.ui.unit.dp
51+
import androidx.compose.ui.unit.sp
2852
import androidx.core.os.bundleOf
2953
import androidx.core.view.*
3054
import androidx.fragment.app.Fragment
@@ -64,6 +88,8 @@ import live.hms.roomkit.ui.meeting.chat.combined.OPEN_TO_PARTICIPANTS
6488
import live.hms.roomkit.ui.meeting.chat.combined.PinnedMessageUiUseCase
6589
import live.hms.roomkit.ui.meeting.chat.rbac.RoleBasedChatBottomSheet
6690
import live.hms.roomkit.ui.meeting.commons.VideoGridBaseFragment
91+
import live.hms.roomkit.ui.meeting.compose.Variables
92+
import live.hms.roomkit.ui.meeting.participants.DIRECTLY_OPENED
6793
import live.hms.roomkit.ui.meeting.participants.ParticipantsFragment
6894
import live.hms.roomkit.ui.meeting.pinnedvideo.PinnedVideoFragment
6995
import live.hms.roomkit.ui.meeting.videogrid.VideoGridFragment
@@ -260,6 +286,7 @@ class MeetingFragment : Fragment() {
260286
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
261287
super.onViewCreated(view, savedInstanceState)
262288
binding.applyTheme()
289+
addComposable(binding.composeView)
263290

264291
if (savedInstanceState != null) {
265292
// Recreated Fragment
@@ -303,6 +330,21 @@ class MeetingFragment : Fragment() {
303330
}
304331
}
305332

333+
private fun addComposable(composeView: ComposeView) = composeView.apply {
334+
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
335+
setContent {
336+
val captionsEnabled by meetingViewModel.areCaptionsEnabledByUser.observeAsState(false)
337+
val subtitles by meetingViewModel.captions.observeAsState()
338+
val topBottom by meetingViewModel.transcriptionsPosition.observeAsState()
339+
if( !subtitles.isNullOrEmpty() && captionsEnabled) {
340+
Column(modifier = Modifier.padding(start = 8.dp, top = 8.dp, end = 8.dp, bottom = 16.dp),
341+
verticalArrangement = if(topBottom == MeetingViewModel.TranscriptionsPosition.TOP) Arrangement.Top else Arrangement.Bottom) {
342+
Captions(subtitles)
343+
}
344+
}
345+
}
346+
}
347+
306348
private fun initializeUI() {
307349
initButtons()
308350
initObservers()
@@ -348,10 +390,16 @@ class MeetingFragment : Fragment() {
348390
meetingViewModel.peerLeaveUpdate.observe(viewLifecycleOwner) {
349391
chatViewModel.updatePeerLeave(it)
350392
}
351-
if(meetingViewModel.prebuiltInfoContainer.chatInitialStateOpen()) {
393+
chatButtonEnabled(meetingViewModel.prebuiltInfoContainer.chatInitialStateOpen())
394+
}
395+
396+
private fun chatButtonEnabled(enable : Boolean) {
397+
if(enable) {
352398
binding.buttonOpenChat.setIconDisabled(R.drawable.ic_chat_message)
399+
meetingViewModel.transcriptionsPosition.postValue(MeetingViewModel.TranscriptionsPosition.TOP)
353400
} else {
354401
binding.buttonOpenChat.setIconEnabled(R.drawable.ic_chat_message)
402+
meetingViewModel.transcriptionsPosition.postValue(MeetingViewModel.TranscriptionsPosition.BOTTOM)
355403
}
356404
}
357405

@@ -865,11 +913,7 @@ class MeetingFragment : Fragment() {
865913
} else {
866914
binding.messageMenu.visibility = View.GONE
867915
}
868-
if(meetingViewModel.prebuiltInfoContainer.chatInitialStateOpen()) {
869-
binding.buttonOpenChat.setIconDisabled(R.drawable.ic_chat_message)
870-
} else {
871-
binding.buttonOpenChat.setIconEnabled(R.drawable.ic_chat_message)
872-
}
916+
chatButtonEnabled(meetingViewModel.prebuiltInfoContainer.chatInitialStateOpen())
873917
}
874918
var controlBarsVisible = true
875919
private fun setupConfiguration(mode: MeetingViewMode) {
@@ -981,6 +1025,8 @@ class MeetingFragment : Fragment() {
9811025

9821026
private fun showControlBars(shouldHideAfterDelay : Boolean) {
9831027
controlBarsVisible = true
1028+
binding.bottomControls.maxHeight = Int.MAX_VALUE
1029+
9841030
binding.topMenu.animate()
9851031
?.translationY(0f)?.setDuration(300)?.setListener(object : AnimatorListener {
9861032
override fun onAnimationStart(animation: Animator) {
@@ -1089,7 +1135,8 @@ class MeetingFragment : Fragment() {
10891135
}
10901136

10911137
override fun onAnimationEnd(animation: Animator) {
1092-
bottomMenu.visibility = View.GONE
1138+
bottomMenu.visibility = View.INVISIBLE
1139+
bottomMenu.maxHeight = 0
10931140
controlBarsVisible = false
10941141
}
10951142

@@ -1155,15 +1202,21 @@ class MeetingFragment : Fragment() {
11551202
onScreenShareClicked = { startOrStopScreenShare() },
11561203
onBRBClicked = { meetingViewModel.toggleBRB() },
11571204
onPeerListClicked = {
1205+
meetingViewModel.tempHideCaptions()
11581206
if( meetingViewModel.prebuiltInfoContainer.isChatOverlay() ||
11591207
!meetingViewModel.prebuiltInfoContainer.isChatEnabled()
11601208
) {
11611209
if(isOverlayChatVisible()){
11621210
toggleChatVisibility()
11631211
}
1212+
val args = Bundle()
1213+
.apply {
1214+
putBoolean(DIRECTLY_OPENED, true)
1215+
}
1216+
11641217
childFragmentManager
11651218
.beginTransaction()
1166-
.add(R.id.fragment_container, ParticipantsFragment())
1219+
.add(R.id.fragment_container, ParticipantsFragment().apply { arguments = args })
11671220
.commit()
11681221
} else {
11691222
val args = Bundle()
@@ -1220,6 +1273,7 @@ class MeetingFragment : Fragment() {
12201273
{
12211274
findNavController().navigate(MeetingFragmentDirections.actionMeetingFragmentToPollsCreationFragment())
12221275
})
1276+
meetingViewModel.tempHideCaptions()
12231277
settingsBottomSheet.show(
12241278
requireActivity().supportFragmentManager,
12251279
"settingsBottomSheet"
@@ -1360,12 +1414,7 @@ class MeetingFragment : Fragment() {
13601414
}
13611415
}
13621416

1363-
if(binding.chatView.visibility == View.VISIBLE) {
1364-
1365-
binding.buttonOpenChat.setIconDisabled(R.drawable.ic_chat_message)
1366-
} else {
1367-
binding.buttonOpenChat.setIconEnabled(R.drawable.ic_chat_message)
1368-
}
1417+
chatButtonEnabled(binding.chatView.visibility == View.VISIBLE)
13691418
}
13701419

13711420
private fun startOrStopScreenShare() {
@@ -1439,4 +1488,51 @@ class MeetingFragment : Fragment() {
14391488
LeaveCallBottomSheet().show(parentFragmentManager, null)
14401489
}
14411490
}
1491+
}
1492+
1493+
@Preview
1494+
@Composable
1495+
fun DisplayCaptions() {
1496+
Captions(subtitles = listOf(TranscriptViewHolder("Cat", "Dinner time", peerId = "a"),
1497+
TranscriptViewHolder("Dog","Time for a walk", peerId = "b")))
1498+
}
1499+
1500+
@Composable
1501+
fun Captions(subtitles: List<TranscriptViewHolder>?) {
1502+
val scrollState = rememberScrollState()
1503+
LaunchedEffect(subtitles) {
1504+
scrollState.scrollTo(scrollState.maxValue)
1505+
}
1506+
1507+
Column(
1508+
Modifier
1509+
.background(color = androidx.compose.ui.graphics.Color(Variables.BackgroundDim.toArgb()))
1510+
.fillMaxWidth()
1511+
.requiredHeightIn(
1512+
min = 27.dp,
1513+
max = 110.dp
1514+
)
1515+
.verticalScroll(scrollState)
1516+
.padding(12.dp),
1517+
) {
1518+
subtitles?.forEach {
1519+
Caption(it.getSubtitle())
1520+
}
1521+
}
1522+
1523+
}
1524+
@Composable
1525+
fun Caption(subtitles : AnnotatedString) {
1526+
Box(modifier = Modifier) {
1527+
Text(
1528+
text = subtitles,
1529+
// modifier = Modifier.padding(Variables.Spacing1),
1530+
style = TextStyle(
1531+
fontSize = 14.sp,
1532+
lineHeight = 20.sp,
1533+
color = androidx.compose.ui.graphics.Color.White,
1534+
letterSpacing = 0.25.sp,
1535+
)
1536+
)
1537+
}
14421538
}

0 commit comments

Comments
 (0)