Skip to content

Commit

Permalink
Merge pull request #703 from 100mslive/dev
Browse files Browse the repository at this point in the history
Release :: v2.9.55
  • Loading branch information
AniketSK authored Apr 26, 2024
2 parents ac66fa7 + c3468a8 commit 6d88939
Show file tree
Hide file tree
Showing 16 changed files with 187 additions and 52 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ dependencies {
implementation "live.100ms:room-kit:$HMS_ROOM_KIT_VERSION"

//100ms noise cancellation dep
def hmsVersion = "2.9.54"
def hmsVersion = "2.9.55"
implementation "live.100ms:hms-noise-cancellation-android:$hmsVersion"

// Navigation
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ kotlin.code.style=official
100MS_APP_VERSION_CODE=371
100MS_APP_VERSION_NAME=5.0.5
hmsRoomKitGroup=live.100ms
HMS_ROOM_KIT_VERSION=1.2.1
HMS_ROOM_KIT_VERSION=1.2.11
android.suppressUnsupportedCompileSdk=33
2 changes: 1 addition & 1 deletion room-kit/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ dependencies {
implementation 'com.google.android.material:material:1.10.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.percentlayout:percentlayout:1.0.0'
def hmsVersion = "2.9.54"
def hmsVersion = "2.9.55"
implementation "com.otaliastudios:zoomlayout:1.9.0"
// To add dependencies of specific module
implementation "live.100ms:android-sdk:$hmsVersion"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
Expand Down Expand Up @@ -337,8 +336,8 @@ class MeetingFragment : Fragment() {
val subtitles by meetingViewModel.captions.observeAsState()
val topBottom by meetingViewModel.transcriptionsPosition.observeAsState()
if( !subtitles.isNullOrEmpty() && captionsEnabled) {
Column(modifier = Modifier.padding(start = 8.dp, top = 8.dp, end = 8.dp, bottom = 16.dp),
verticalArrangement = if(topBottom == MeetingViewModel.TranscriptionsPosition.TOP) Arrangement.Top else Arrangement.Bottom) {
Column(modifier = Modifier.padding(start = 8.dp, top = if(topBottom == MeetingViewModel.TranscriptionsPosition.SCREENSHARE_TOP) 57.dp else 8.dp, end = 8.dp, bottom = 16.dp),
verticalArrangement = if(topBottom == MeetingViewModel.TranscriptionsPosition.BOTTOM) Arrangement.Bottom else Arrangement.Top) {
Captions(subtitles)
}
}
Expand Down Expand Up @@ -394,12 +393,11 @@ class MeetingFragment : Fragment() {
}

private fun chatButtonEnabled(enable : Boolean) {
meetingViewModel.transcriptionsPositionUseCase.chatStateChanged(enable)
if(enable) {
binding.buttonOpenChat.setIconDisabled(R.drawable.ic_chat_message)
meetingViewModel.transcriptionsPosition.postValue(MeetingViewModel.TranscriptionsPosition.TOP)
} else {
binding.buttonOpenChat.setIconEnabled(R.drawable.ic_chat_message)
meetingViewModel.transcriptionsPosition.postValue(MeetingViewModel.TranscriptionsPosition.BOTTOM)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,18 @@ import kotlin.properties.Delegates
class MeetingViewModel(
application: Application
) : AndroidViewModel(application) {
val joined = MutableLiveData(false)
companion object {
private const val TAG = "MeetingViewModel"
}
val transcriptionUseCase = TranscriptionUseCase { hmsSDK.getPeerById(it)?.name }
enum class TranscriptionsPosition {
SCREENSHARE_TOP,
TOP,
BOTTOM
}
val transcriptionsPosition = MutableLiveData(TranscriptionsPosition.BOTTOM)
val transcriptionsPositionUseCase = TranscriptionsPositionUseCase(viewModelScope)
val transcriptionsPosition : LiveData<TranscriptionsPosition> = transcriptionsPositionUseCase.transcriptionsPosition.distinctUntilChanged()
val areCaptionsEnabledByUser : MutableLiveData<Boolean> = MutableLiveData(true)
val captions : LiveData<List<TranscriptViewHolder>> = transcriptionUseCase.captions

Expand Down Expand Up @@ -441,7 +444,7 @@ class MeetingViewModel(
}
}

private val _tracks = Collections.synchronizedList(ArrayList<MeetingTrack>())
val _tracks = Collections.synchronizedList(ArrayList<MeetingTrack>())

// When we get stats, a flow will be updated with the saved stats.
private val statsFlow = MutableSharedFlow<Map<String, Any>>()
Expand Down Expand Up @@ -530,7 +533,8 @@ class MeetingViewModel(
val tracks: LiveData<List<MeetingTrack>> = _liveDataTracks

// Live data containing the current Speaker in the meeting
val speakers = MutableLiveData<Array<HMSSpeaker>>()
val speakersLiveData = MutableLiveData<Array<HMSSpeaker>>()


private val activeSpeakerHandler = ActiveSpeakerHandler(false) { _tracks }

Expand All @@ -545,21 +549,25 @@ class MeetingViewModel(
) { _tracks }

override fun addSpeakerSource() {
addSource(speakers) { speakers : Array<HMSSpeaker> ->
addSource(speakersLiveData) { speakerList : Array<HMSSpeaker> ->

val excludeLocalTrackIfRemotePeerIsPreset : Array<HMSSpeaker> = if (hasInsetEnabled(hmsSDK.getLocalPeer()?.hmsRole)) {
speakers.filter { it.peer?.isLocal == false }.toTypedArray()
} else {
speakers
}
synchronized(speakersLiveData) {

val excludeLocalTrackIfRemotePeerIsPreset: Array<HMSSpeaker> =
if (hasInsetEnabled(hmsSDK.getLocalPeer()?.hmsRole)) {
speakerList.filter { it.peer?.isLocal == false }.toTypedArray()
} else {
speakerList
}

val result = speakerH.speakerUpdate(excludeLocalTrackIfRemotePeerIsPreset)
setValue(result.first)
val result = speakerH.speakerUpdate(excludeLocalTrackIfRemotePeerIsPreset)
setValue(result.first)
}
}
}

override fun removeSpeakerSource() {
removeSource(speakers)
removeSource(speakersLiveData)
}

//TODO can't be null
Expand Down Expand Up @@ -618,7 +626,9 @@ class MeetingViewModel(
}

val activeSpeakers: LiveData<Pair<List<MeetingTrack>, Array<HMSSpeaker>>> =
speakers.map(activeSpeakerHandler::speakerUpdate)
speakersLiveData.map(activeSpeakerHandler::speakerUpdate)


val activeSpeakersUpdatedTracks = _liveDataTracks.map(activeSpeakerHandler::trackUpdateTrigger)

// We need all the active speakers, but the very first time it should be filled.
Expand Down Expand Up @@ -968,6 +978,7 @@ class MeetingViewModel(
updatePolls()
participantPeerUpdate.postValue(Unit)
setupWhiteBoardListener()
joined.postValue(true)
}

override fun onPeerUpdate(type: HMSPeerUpdate, hmsPeer: HMSPeer) {
Expand Down Expand Up @@ -1239,7 +1250,9 @@ class MeetingViewModel(
TAG,
"onAudioLevelUpdate: speakers=${speakers.map { Pair(it.peer?.name, it.level) }}"
)
this@MeetingViewModel.speakers.postValue(speakers)
synchronized(speakersLiveData) {
this@MeetingViewModel.speakersLiveData.postValue(speakers)
}
}
})
}
Expand Down Expand Up @@ -1496,6 +1509,7 @@ class MeetingViewModel(
}
cleanup()
state.postValue(MeetingState.Disconnected(true, details))
joined.postValue(false)
}

private fun addAudioTrack(track: HMSAudioTrack, peer: HMSPeer) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package live.hms.roomkit.ui.meeting

import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

class TranscriptionsPositionUseCase(private val scope : CoroutineScope) {
val TAG = "TranscPositionUseCase"
val transcriptionsPosition = MutableLiveData(MeetingViewModel.TranscriptionsPosition.BOTTOM)


private var isScreenShare : Boolean = false
private var isChatEnabled : Boolean = false
private var lock = Mutex()
fun chatStateChanged(enabled: Boolean) {
scope.launch {
lock.withLock {
// Log.d(TAG, "Chatstate: $enabled")
isChatEnabled = enabled
transcriptionsPosition.postValue(recalculate())
}
}
}

fun setScreenShare(enabled : Boolean) {
scope.launch {
lock.withLock {
// Log.d(TAG, "Screenshare: $enabled")
isScreenShare = enabled

transcriptionsPosition.postValue(recalculate())
}
}
}

private fun recalculate() : MeetingViewModel.TranscriptionsPosition {
return if(!isChatEnabled)
MeetingViewModel.TranscriptionsPosition.BOTTOM
else if(isScreenShare)
MeetingViewModel.TranscriptionsPosition.SCREENSHARE_TOP
else
MeetingViewModel.TranscriptionsPosition.TOP
// Log.d(TAG, "recalculate $r")
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,11 @@ class AudioModeFragment : Fragment() {
adapter.setItems(meetingViewModel.peers)
}

meetingViewModel.speakers.observe(viewLifecycleOwner) {
adapter.applySpeakerUpdates(it)
meetingViewModel.speakersLiveData.observe(viewLifecycleOwner) {
synchronized(meetingViewModel.speakersLiveData) {
adapter.applySpeakerUpdates(it)
}

}

if (meetingViewModel.isLocalVideoEnabled.value != false) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,24 @@ class ChatOnlyAdapter(val fragment: BottomSheetDialogFragment) : FragmentStateAd
override fun createFragment(position: Int): Fragment {
// Return a NEW fragment instance in createFragment(int).

return CombinedChatFragmentTab(fragment::dismissAllowingStateLoss)
return CombinedChatFragmentTab().apply {
this.dismissAllowingStateLoss = fragment::dismissAllowingStateLoss
}
}
}
class ChatParticipantAdapter(val fragment: BottomSheetDialogFragment) : FragmentStateAdapter(fragment) {
val partFragment = ParticipantsTabFragment(fragment::dismiss)
val partFragment = ParticipantsTabFragment().apply {
this.dismissFragment = fragment::dismiss
}
override fun getItemCount(): Int = 2

override fun createFragment(position: Int): Fragment {
// Return a NEW fragment instance in createFragment(int).

return if (position == 0)
CombinedChatFragmentTab(fragment::dismissAllowingStateLoss)
CombinedChatFragmentTab().apply {
this.dismissAllowingStateLoss = fragment::dismissAllowingStateLoss
}
else
partFragment
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,37 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import live.hms.roomkit.databinding.LayoutChatParticipantCombinedTabChatBinding
import live.hms.roomkit.setOnSingleClickListener
import live.hms.roomkit.ui.meeting.ChatViewModelFactory
import live.hms.roomkit.ui.meeting.MeetingViewModel
import live.hms.roomkit.ui.meeting.MeetingViewModelFactory
import live.hms.roomkit.ui.meeting.MessageOptionsBottomSheet
import live.hms.roomkit.ui.meeting.PauseChatUIUseCase
import live.hms.roomkit.ui.meeting.chat.ChatAdapter
import live.hms.roomkit.ui.meeting.chat.ChatUseCase
import live.hms.roomkit.ui.meeting.chat.ChatViewModel
import live.hms.roomkit.ui.meeting.chat.rbac.RoleBasedChatBottomSheet
import live.hms.roomkit.ui.meeting.participants.LoadAfterJoin
import live.hms.roomkit.ui.theme.applyTheme
import live.hms.roomkit.util.viewLifecycle
import kotlin.reflect.KFunction0

class CombinedChatFragmentTab(val dismissAllowingStateLoss: KFunction0<Unit>) : Fragment() {
class CombinedChatFragmentTab : Fragment() {
private var binding by viewLifecycle<LayoutChatParticipantCombinedTabChatBinding>()
val meetingViewModel : MeetingViewModel by activityViewModels()
private val chatViewModel : ChatViewModel by activityViewModels()
val meetingViewModel : MeetingViewModel by activityViewModels {
MeetingViewModelFactory(
requireActivity().application
)
}
private val chatViewModel : ChatViewModel by activityViewModels {
ChatViewModelFactory(meetingViewModel.hmsSDK)
}
private val launchMessageOptionsDialog = LaunchMessageOptionsDialog()
private val chatAdapter by lazy { ChatAdapter({ message ->
launchMessageOptionsDialog.launch(meetingViewModel,
childFragmentManager, message) },{}, { message -> MessageOptionsBottomSheet.showMessageOptions(meetingViewModel, message)})
}
private val pinnedMessageUiUseCase = PinnedMessageUiUseCase()

lateinit var dismissAllowingStateLoss: () -> Unit
override fun onDetach() {
super.onDetach()
meetingViewModel.restoreTempHiddenCaptions()
Expand All @@ -56,8 +65,13 @@ class CombinedChatFragmentTab(val dismissAllowingStateLoss: KFunction0<Unit>) :
return binding.root
}


override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
LoadAfterJoin(meetingViewModel, viewLifecycleOwner) {
afterViewCreatedAndJoined()
}
}

fun afterViewCreatedAndJoined() {

binding.sendToBackground.setOnSingleClickListener {
RoleBasedChatBottomSheet.launch(childFragmentManager, chatViewModel)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ class RoleBasedChatBottomSheet(
private val recipientSelected: (Recipient) -> Unit
) : BottomSheetDialogFragment() {

private var close = false
constructor() : this({null}, {}) {
// Close the fragment if it's recreated by android after a restart
close = true
}

private var initialRecipients : List<Group> = emptyList()
private var allowedParticipants : AllowedToMessageParticipants? = null
private val chatRecipientSearchUseCase : ChatRecipientSearchUseCase = ChatRecipientSearchUseCase(::updateListWithPeers)
Expand All @@ -72,6 +78,8 @@ class RoleBasedChatBottomSheet(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, R.style.AppBottomSheetDialogTheme)
if(close)
dismissAllowingStateLoss()
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package live.hms.roomkit.ui.meeting.participants

import androidx.lifecycle.LifecycleOwner
import live.hms.roomkit.ui.meeting.MeetingViewModel

/**
* Many fragments need to load data out of the MeetingViewModel or the ChatViewModel when the view
* is created. However, if they do this just in onViewCreated, then if the app is destroyed by
* going in the background, they won't wait until the app has joined the room to load this data.
* Which means they will crash at some point.
* Everything covered in this class has been tested by
* 1. Turning on kill `background activities`
* 2. Leave the app, so it's in the background and open it again.
*
* Fragments particularly must have empty constructors only.
* This can be resolved by:
* 1. For methods, make them lateinit and apply them after the fragment is created.
* 2. For data, pass it in an argument.
*/
class LoadAfterJoin(meetingViewModel: MeetingViewModel, viewLifecycleOwner: LifecycleOwner, afterJoin: () -> Unit ) {
private var inited = false
init {
meetingViewModel.joined.observe(viewLifecycleOwner) { joined ->
if(!inited && joined) {
inited = true
afterJoin()
}
}
}
}
Loading

0 comments on commit 6d88939

Please sign in to comment.