Merge pull request #7478 from vector-im/feature/fre/voice_broadcast_player_interface
Voice Broadcast - Some internal improvements related to the player
This commit is contained in:
commit
01ab39ec5f
1
changelog.d/7478.wip
Normal file
1
changelog.d/7478.wip
Normal file
@ -0,0 +1 @@
|
|||||||
|
[Voice Broadcast] Improve playlist fetching and player codebase
|
@ -18,24 +18,33 @@ package im.vector.app.core.di
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
|
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorderQ
|
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayerImpl
|
||||||
|
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||||
|
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorderQ
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
object VoiceModule {
|
@Module
|
||||||
@Provides
|
abstract class VoiceModule {
|
||||||
@Singleton
|
|
||||||
fun providesVoiceBroadcastRecorder(context: Context): VoiceBroadcastRecorder? {
|
companion object {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
@Provides
|
||||||
VoiceBroadcastRecorderQ(context)
|
@Singleton
|
||||||
} else {
|
fun providesVoiceBroadcastRecorder(context: Context): VoiceBroadcastRecorder? {
|
||||||
null
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
VoiceBroadcastRecorderQ(context)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindVoiceBroadcastPlayer(player: VoiceBroadcastPlayerImpl): VoiceBroadcastPlayer
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ import im.vector.app.features.raw.wellknown.isSecureBackupRequired
|
|||||||
import im.vector.app.features.raw.wellknown.withElementWellKnown
|
import im.vector.app.features.raw.wellknown.withElementWellKnown
|
||||||
import im.vector.app.features.session.coroutineScope
|
import im.vector.app.features.session.coroutineScope
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import im.vector.app.features.voicebroadcast.usecase.StopOngoingVoiceBroadcastUseCase
|
import im.vector.app.features.voicebroadcast.recording.usecase.StopOngoingVoiceBroadcastUseCase
|
||||||
import im.vector.lib.core.utils.compat.getParcelableExtraCompat
|
import im.vector.lib.core.utils.compat.getParcelableExtraCompat
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
@ -26,11 +26,11 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadca
|
|||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastListeningItem_
|
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastListeningItem_
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem
|
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem_
|
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem_
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastPlayer
|
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
|
|
||||||
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
|
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
import org.matrix.android.sdk.api.session.getUserOrDefault
|
import org.matrix.android.sdk.api.session.getUserOrDefault
|
||||||
|
@ -25,9 +25,9 @@ import im.vector.app.R
|
|||||||
import im.vector.app.core.extensions.tintBackground
|
import im.vector.app.core.extensions.tintBackground
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.core.resources.DrawableProvider
|
import im.vector.app.core.resources.DrawableProvider
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastPlayer
|
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
|
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
|
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
|
||||||
abstract class AbsMessageVoiceBroadcastItem<H : AbsMessageVoiceBroadcastItem.Holder> : AbsMessageItem<H>() {
|
abstract class AbsMessageVoiceBroadcastItem<H : AbsMessageVoiceBroadcastItem.Holder> : AbsMessageItem<H>() {
|
||||||
|
@ -23,7 +23,7 @@ import com.airbnb.epoxy.EpoxyModelClass
|
|||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.epoxy.onClick
|
import im.vector.app.core.epoxy.onClick
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailAction
|
import im.vector.app.features.home.room.detail.RoomDetailAction
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastPlayer
|
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer
|
||||||
import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
||||||
|
|
||||||
@EpoxyModelClass
|
@EpoxyModelClass
|
||||||
|
@ -22,8 +22,8 @@ import com.airbnb.epoxy.EpoxyModelClass
|
|||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.epoxy.onClick
|
import im.vector.app.core.epoxy.onClick
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction
|
import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
|
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
|
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||||
import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
||||||
|
|
||||||
@EpoxyModelClass
|
@EpoxyModelClass
|
||||||
|
@ -16,10 +16,11 @@
|
|||||||
|
|
||||||
package im.vector.app.features.voicebroadcast
|
package im.vector.app.features.voicebroadcast
|
||||||
|
|
||||||
import im.vector.app.features.voicebroadcast.usecase.PauseVoiceBroadcastUseCase
|
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer
|
||||||
import im.vector.app.features.voicebroadcast.usecase.ResumeVoiceBroadcastUseCase
|
import im.vector.app.features.voicebroadcast.recording.usecase.PauseVoiceBroadcastUseCase
|
||||||
import im.vector.app.features.voicebroadcast.usecase.StartVoiceBroadcastUseCase
|
import im.vector.app.features.voicebroadcast.recording.usecase.ResumeVoiceBroadcastUseCase
|
||||||
import im.vector.app.features.voicebroadcast.usecase.StopVoiceBroadcastUseCase
|
import im.vector.app.features.voicebroadcast.recording.usecase.StartVoiceBroadcastUseCase
|
||||||
|
import im.vector.app.features.voicebroadcast.recording.usecase.StopVoiceBroadcastUseCase
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.voicebroadcast.listening
|
||||||
|
|
||||||
|
interface VoiceBroadcastPlayer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current playing voice broadcast identifier, if any.
|
||||||
|
*/
|
||||||
|
val currentVoiceBroadcastId: String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current playing [State], [State.IDLE] by default.
|
||||||
|
*/
|
||||||
|
val playingState: State
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start playback of the given voice broadcast.
|
||||||
|
*/
|
||||||
|
fun playOrResume(roomId: String, voiceBroadcastId: String)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pause playback of the current voice broadcast, if any.
|
||||||
|
*/
|
||||||
|
fun pause()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop playback of the current voice broadcast, if any, and reset the player state.
|
||||||
|
*/
|
||||||
|
fun stop()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a [Listener] to the given voice broadcast id.
|
||||||
|
*/
|
||||||
|
fun addListener(voiceBroadcastId: String, listener: Listener)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a [Listener] from the given voice broadcast id.
|
||||||
|
*/
|
||||||
|
fun removeListener(voiceBroadcastId: String, listener: Listener)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Player states.
|
||||||
|
*/
|
||||||
|
enum class State {
|
||||||
|
PLAYING,
|
||||||
|
PAUSED,
|
||||||
|
BUFFERING,
|
||||||
|
IDLE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener related to [VoiceBroadcastPlayer].
|
||||||
|
*/
|
||||||
|
fun interface Listener {
|
||||||
|
/**
|
||||||
|
* Notify about [VoiceBroadcastPlayer.playingState] changes.
|
||||||
|
*/
|
||||||
|
fun onStateChanged(state: State)
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.voicebroadcast
|
package im.vector.app.features.voicebroadcast.listening
|
||||||
|
|
||||||
import android.media.AudioAttributes
|
import android.media.AudioAttributes
|
||||||
import android.media.MediaPlayer
|
import android.media.MediaPlayer
|
||||||
@ -22,49 +22,43 @@ import androidx.annotation.MainThread
|
|||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
|
import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
|
||||||
import im.vector.app.features.voice.VoiceFailure
|
import im.vector.app.features.voice.VoiceFailure
|
||||||
|
import im.vector.app.features.voicebroadcast.getVoiceBroadcastChunk
|
||||||
|
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer.Listener
|
||||||
|
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer.State
|
||||||
|
import im.vector.app.features.voicebroadcast.listening.usecase.GetLiveVoiceBroadcastChunksUseCase
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.sequence
|
||||||
import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastUseCase
|
import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastUseCase
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
|
||||||
import org.matrix.android.sdk.api.session.room.Room
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioEvent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioEvent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class VoiceBroadcastPlayer @Inject constructor(
|
class VoiceBroadcastPlayerImpl @Inject constructor(
|
||||||
private val sessionHolder: ActiveSessionHolder,
|
private val sessionHolder: ActiveSessionHolder,
|
||||||
private val playbackTracker: AudioMessagePlaybackTracker,
|
private val playbackTracker: AudioMessagePlaybackTracker,
|
||||||
private val getVoiceBroadcastUseCase: GetVoiceBroadcastUseCase,
|
private val getVoiceBroadcastUseCase: GetVoiceBroadcastUseCase,
|
||||||
) {
|
private val getLiveVoiceBroadcastChunksUseCase: GetLiveVoiceBroadcastChunksUseCase
|
||||||
|
) : VoiceBroadcastPlayer {
|
||||||
|
|
||||||
private val session
|
private val session
|
||||||
get() = sessionHolder.getActiveSession()
|
get() = sessionHolder.getActiveSession()
|
||||||
|
|
||||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||||
private var voiceBroadcastStateJob: Job? = null
|
private var voiceBroadcastStateJob: Job? = null
|
||||||
private var currentTimeline: Timeline? = null
|
|
||||||
set(value) {
|
|
||||||
field?.removeAllListeners()
|
|
||||||
field?.dispose()
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
|
|
||||||
private val mediaPlayerListener = MediaPlayerListener()
|
private val mediaPlayerListener = MediaPlayerListener()
|
||||||
private var timelineListener: TimelineListener? = null
|
|
||||||
|
|
||||||
private var currentMediaPlayer: MediaPlayer? = null
|
private var currentMediaPlayer: MediaPlayer? = null
|
||||||
private var nextMediaPlayer: MediaPlayer? = null
|
private var nextMediaPlayer: MediaPlayer? = null
|
||||||
@ -74,10 +68,13 @@ class VoiceBroadcastPlayer @Inject constructor(
|
|||||||
}
|
}
|
||||||
private var currentSequence: Int? = null
|
private var currentSequence: Int? = null
|
||||||
|
|
||||||
|
private var fetchPlaylistJob: Job? = null
|
||||||
private var playlist = emptyList<MessageAudioEvent>()
|
private var playlist = emptyList<MessageAudioEvent>()
|
||||||
var currentVoiceBroadcastId: String? = null
|
private var isLive: Boolean = false
|
||||||
|
|
||||||
private var state: State = State.IDLE
|
override var currentVoiceBroadcastId: String? = null
|
||||||
|
|
||||||
|
override var playingState = State.IDLE
|
||||||
@MainThread
|
@MainThread
|
||||||
set(value) {
|
set(value) {
|
||||||
Timber.w("## VoiceBroadcastPlayer state: $field -> $value")
|
Timber.w("## VoiceBroadcastPlayer state: $field -> $value")
|
||||||
@ -94,25 +91,26 @@ class VoiceBroadcastPlayer @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
private val listeners: MutableMap<String, CopyOnWriteArrayList<Listener>> = mutableMapOf()
|
private val listeners: MutableMap<String, CopyOnWriteArrayList<Listener>> = mutableMapOf()
|
||||||
|
|
||||||
fun playOrResume(roomId: String, eventId: String) {
|
override fun playOrResume(roomId: String, voiceBroadcastId: String) {
|
||||||
val hasChanged = currentVoiceBroadcastId != eventId
|
val hasChanged = currentVoiceBroadcastId != voiceBroadcastId
|
||||||
when {
|
when {
|
||||||
hasChanged -> startPlayback(roomId, eventId)
|
hasChanged -> startPlayback(roomId, voiceBroadcastId)
|
||||||
state == State.PAUSED -> resumePlayback()
|
playingState == State.PAUSED -> resumePlayback()
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pause() {
|
override fun pause() {
|
||||||
currentMediaPlayer?.pause()
|
currentMediaPlayer?.pause()
|
||||||
currentVoiceBroadcastId?.let { playbackTracker.pausePlayback(it) }
|
currentVoiceBroadcastId?.let { playbackTracker.pausePlayback(it) }
|
||||||
state = State.PAUSED
|
playingState = State.PAUSED
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
override fun stop() {
|
||||||
// Stop playback
|
// Stop playback
|
||||||
currentMediaPlayer?.stop()
|
currentMediaPlayer?.stop()
|
||||||
currentVoiceBroadcastId?.let { playbackTracker.stopPlayback(it) }
|
currentVoiceBroadcastId?.let { playbackTracker.stopPlayback(it) }
|
||||||
|
isLive = false
|
||||||
|
|
||||||
// Release current player
|
// Release current player
|
||||||
release(currentMediaPlayer)
|
release(currentMediaPlayer)
|
||||||
@ -126,58 +124,78 @@ class VoiceBroadcastPlayer @Inject constructor(
|
|||||||
voiceBroadcastStateJob?.cancel()
|
voiceBroadcastStateJob?.cancel()
|
||||||
voiceBroadcastStateJob = null
|
voiceBroadcastStateJob = null
|
||||||
|
|
||||||
// In case of live broadcast, stop observing new chunks
|
// Do not fetch the playlist anymore
|
||||||
currentTimeline = null
|
fetchPlaylistJob?.cancel()
|
||||||
timelineListener = null
|
fetchPlaylistJob = null
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
state = State.IDLE
|
playingState = State.IDLE
|
||||||
|
|
||||||
// Clear playlist
|
// Clear playlist
|
||||||
playlist = emptyList()
|
playlist = emptyList()
|
||||||
currentSequence = null
|
currentSequence = null
|
||||||
|
|
||||||
currentRoomId = null
|
currentRoomId = null
|
||||||
currentVoiceBroadcastId = null
|
currentVoiceBroadcastId = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override fun addListener(voiceBroadcastId: String, listener: Listener) {
|
||||||
* Add a [Listener] to the given voice broadcast id.
|
|
||||||
*/
|
|
||||||
fun addListener(voiceBroadcastId: String, listener: Listener) {
|
|
||||||
listeners[voiceBroadcastId]?.add(listener) ?: run {
|
listeners[voiceBroadcastId]?.add(listener) ?: run {
|
||||||
listeners[voiceBroadcastId] = CopyOnWriteArrayList<Listener>().apply { add(listener) }
|
listeners[voiceBroadcastId] = CopyOnWriteArrayList<Listener>().apply { add(listener) }
|
||||||
}
|
}
|
||||||
if (voiceBroadcastId == currentVoiceBroadcastId) listener.onStateChanged(state) else listener.onStateChanged(State.IDLE)
|
if (voiceBroadcastId == currentVoiceBroadcastId) listener.onStateChanged(playingState) else listener.onStateChanged(State.IDLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override fun removeListener(voiceBroadcastId: String, listener: Listener) {
|
||||||
* Remove a [Listener] from the given voice broadcast id.
|
|
||||||
*/
|
|
||||||
fun removeListener(voiceBroadcastId: String, listener: Listener) {
|
|
||||||
listeners[voiceBroadcastId]?.remove(listener)
|
listeners[voiceBroadcastId]?.remove(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startPlayback(roomId: String, eventId: String) {
|
private fun startPlayback(roomId: String, eventId: String) {
|
||||||
val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId")
|
|
||||||
// Stop listening previous voice broadcast if any
|
// Stop listening previous voice broadcast if any
|
||||||
if (state != State.IDLE) stop()
|
if (playingState != State.IDLE) stop()
|
||||||
|
|
||||||
currentRoomId = roomId
|
currentRoomId = roomId
|
||||||
currentVoiceBroadcastId = eventId
|
currentVoiceBroadcastId = eventId
|
||||||
|
|
||||||
state = State.BUFFERING
|
playingState = State.BUFFERING
|
||||||
|
|
||||||
val voiceBroadcastState = getVoiceBroadcastUseCase.execute(roomId, eventId)?.content?.voiceBroadcastState
|
val voiceBroadcastState = getVoiceBroadcastUseCase.execute(roomId, eventId)?.content?.voiceBroadcastState
|
||||||
if (voiceBroadcastState == VoiceBroadcastState.STOPPED) {
|
isLive = voiceBroadcastState != null && voiceBroadcastState != VoiceBroadcastState.STOPPED
|
||||||
// Get static playlist
|
fetchPlaylistAndStartPlayback(roomId, eventId)
|
||||||
updatePlaylist(getExistingChunks(room, eventId))
|
}
|
||||||
startPlayback(false)
|
|
||||||
} else {
|
private fun fetchPlaylistAndStartPlayback(roomId: String, voiceBroadcastId: String) {
|
||||||
playLiveVoiceBroadcast(room, eventId)
|
fetchPlaylistJob = getLiveVoiceBroadcastChunksUseCase.execute(roomId, voiceBroadcastId)
|
||||||
|
.onEach(this::updatePlaylist)
|
||||||
|
.launchIn(coroutineScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updatePlaylist(playlist: List<MessageAudioEvent>) {
|
||||||
|
this.playlist = playlist.sortedBy { it.getVoiceBroadcastChunk()?.sequence?.toLong() ?: it.root.originServerTs }
|
||||||
|
onPlaylistUpdated()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onPlaylistUpdated() {
|
||||||
|
when (playingState) {
|
||||||
|
State.PLAYING -> {
|
||||||
|
if (nextMediaPlayer == null) {
|
||||||
|
coroutineScope.launch { nextMediaPlayer = prepareNextMediaPlayer() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
State.PAUSED -> {
|
||||||
|
if (nextMediaPlayer == null) {
|
||||||
|
coroutineScope.launch { nextMediaPlayer = prepareNextMediaPlayer() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
State.BUFFERING -> {
|
||||||
|
val newMediaContent = getNextAudioContent()
|
||||||
|
if (newMediaContent != null) startPlayback()
|
||||||
|
}
|
||||||
|
State.IDLE -> startPlayback()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startPlayback(isLive: Boolean) {
|
private fun startPlayback() {
|
||||||
val event = if (isLive) playlist.lastOrNull() else playlist.firstOrNull()
|
val event = if (isLive) playlist.lastOrNull() else playlist.firstOrNull()
|
||||||
val content = event?.content ?: run { Timber.w("## VoiceBroadcastPlayer: No content to play"); return }
|
val content = event?.content ?: run { Timber.w("## VoiceBroadcastPlayer: No content to play"); return }
|
||||||
val sequence = event.getVoiceBroadcastChunk()?.sequence
|
val sequence = event.getVoiceBroadcastChunk()?.sequence
|
||||||
@ -187,7 +205,7 @@ class VoiceBroadcastPlayer @Inject constructor(
|
|||||||
currentMediaPlayer?.start()
|
currentMediaPlayer?.start()
|
||||||
currentVoiceBroadcastId?.let { playbackTracker.startPlayback(it) }
|
currentVoiceBroadcastId?.let { playbackTracker.startPlayback(it) }
|
||||||
currentSequence = sequence
|
currentSequence = sequence
|
||||||
withContext(Dispatchers.Main) { state = State.PLAYING }
|
withContext(Dispatchers.Main) { playingState = State.PLAYING }
|
||||||
nextMediaPlayer = prepareNextMediaPlayer()
|
nextMediaPlayer = prepareNextMediaPlayer()
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
Timber.e(failure, "Unable to start playback")
|
Timber.e(failure, "Unable to start playback")
|
||||||
@ -196,39 +214,15 @@ class VoiceBroadcastPlayer @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun playLiveVoiceBroadcast(room: Room, eventId: String) {
|
|
||||||
room.timelineService().getTimelineEvent(eventId)?.root?.asVoiceBroadcastEvent() ?: error("Cannot retrieve voice broadcast $eventId")
|
|
||||||
updatePlaylist(getExistingChunks(room, eventId))
|
|
||||||
startPlayback(true)
|
|
||||||
observeIncomingEvents(room, eventId)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getExistingChunks(room: Room, eventId: String): List<MessageAudioEvent> {
|
|
||||||
return room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, eventId)
|
|
||||||
.mapNotNull { it.root.asMessageAudioEvent() }
|
|
||||||
.filter { it.isVoiceBroadcast() }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun observeIncomingEvents(room: Room, eventId: String) {
|
|
||||||
currentTimeline = room.timelineService().createTimeline(null, TimelineSettings(5)).also { timeline ->
|
|
||||||
timelineListener = TimelineListener(eventId).also { timeline.addListener(it) }
|
|
||||||
timeline.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun resumePlayback() {
|
private fun resumePlayback() {
|
||||||
currentMediaPlayer?.start()
|
currentMediaPlayer?.start()
|
||||||
currentVoiceBroadcastId?.let { playbackTracker.startPlayback(it) }
|
currentVoiceBroadcastId?.let { playbackTracker.startPlayback(it) }
|
||||||
state = State.PLAYING
|
playingState = State.PLAYING
|
||||||
}
|
|
||||||
|
|
||||||
private fun updatePlaylist(playlist: List<MessageAudioEvent>) {
|
|
||||||
this.playlist = playlist.sortedBy { it.getVoiceBroadcastChunk()?.sequence?.toLong() ?: it.root.originServerTs }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getNextAudioContent(): MessageAudioContent? {
|
private fun getNextAudioContent(): MessageAudioContent? {
|
||||||
val nextSequence = currentSequence?.plus(1)
|
val nextSequence = currentSequence?.plus(1)
|
||||||
?: timelineListener?.let { playlist.lastOrNull()?.sequence }
|
?: playlist.lastOrNull()?.sequence
|
||||||
?: 1
|
?: 1
|
||||||
return playlist.find { it.getVoiceBroadcastChunk()?.sequence == nextSequence }?.content
|
return playlist.find { it.getVoiceBroadcastChunk()?.sequence == nextSequence }?.content
|
||||||
}
|
}
|
||||||
@ -274,37 +268,6 @@ class VoiceBroadcastPlayer @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class TimelineListener(private val voiceBroadcastId: String) : Timeline.Listener {
|
|
||||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
|
||||||
val currentSequences = playlist.map { it.sequence }
|
|
||||||
val newChunks = snapshot
|
|
||||||
.mapNotNull { timelineEvent ->
|
|
||||||
timelineEvent.root.asMessageAudioEvent()
|
|
||||||
?.takeIf { it.isVoiceBroadcast() && it.getVoiceBroadcastEventId() == voiceBroadcastId && it.sequence !in currentSequences }
|
|
||||||
}
|
|
||||||
if (newChunks.isEmpty()) return
|
|
||||||
updatePlaylist(playlist + newChunks)
|
|
||||||
|
|
||||||
when (state) {
|
|
||||||
State.PLAYING -> {
|
|
||||||
if (nextMediaPlayer == null) {
|
|
||||||
coroutineScope.launch { nextMediaPlayer = prepareNextMediaPlayer() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
State.PAUSED -> {
|
|
||||||
if (nextMediaPlayer == null) {
|
|
||||||
coroutineScope.launch { nextMediaPlayer = prepareNextMediaPlayer() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
State.BUFFERING -> {
|
|
||||||
val newMediaContent = getNextAudioContent()
|
|
||||||
if (newMediaContent != null) startPlayback(true)
|
|
||||||
}
|
|
||||||
State.IDLE -> startPlayback(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class MediaPlayerListener : MediaPlayer.OnInfoListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener {
|
private inner class MediaPlayerListener : MediaPlayer.OnInfoListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener {
|
||||||
|
|
||||||
override fun onInfo(mp: MediaPlayer, what: Int, extra: Int): Boolean {
|
override fun onInfo(mp: MediaPlayer, what: Int, extra: Int): Boolean {
|
||||||
@ -324,13 +287,13 @@ class VoiceBroadcastPlayer @Inject constructor(
|
|||||||
val roomId = currentRoomId ?: return
|
val roomId = currentRoomId ?: return
|
||||||
val voiceBroadcastId = currentVoiceBroadcastId ?: return
|
val voiceBroadcastId = currentVoiceBroadcastId ?: return
|
||||||
val voiceBroadcastEventContent = getVoiceBroadcastUseCase.execute(roomId, voiceBroadcastId)?.content ?: return
|
val voiceBroadcastEventContent = getVoiceBroadcastUseCase.execute(roomId, voiceBroadcastId)?.content ?: return
|
||||||
val isLive = voiceBroadcastEventContent.voiceBroadcastState != null && voiceBroadcastEventContent.voiceBroadcastState != VoiceBroadcastState.STOPPED
|
isLive = voiceBroadcastEventContent.voiceBroadcastState != null && voiceBroadcastEventContent.voiceBroadcastState != VoiceBroadcastState.STOPPED
|
||||||
|
|
||||||
if (!isLive && voiceBroadcastEventContent.lastChunkSequence == currentSequence) {
|
if (!isLive && voiceBroadcastEventContent.lastChunkSequence == currentSequence) {
|
||||||
// We'll not receive new chunks anymore so we can stop the live listening
|
// We'll not receive new chunks anymore so we can stop the live listening
|
||||||
stop()
|
stop()
|
||||||
} else {
|
} else {
|
||||||
state = State.BUFFERING
|
playingState = State.BUFFERING
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,15 +302,4 @@ class VoiceBroadcastPlayer @Inject constructor(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class State {
|
|
||||||
PLAYING,
|
|
||||||
PAUSED,
|
|
||||||
BUFFERING,
|
|
||||||
IDLE
|
|
||||||
}
|
|
||||||
|
|
||||||
fun interface Listener {
|
|
||||||
fun onStateChanged(state: State)
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.voicebroadcast.listening.usecase
|
||||||
|
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import im.vector.app.features.voicebroadcast.getVoiceBroadcastEventId
|
||||||
|
import im.vector.app.features.voicebroadcast.isVoiceBroadcast
|
||||||
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
||||||
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
|
import im.vector.app.features.voicebroadcast.sequence
|
||||||
|
import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastUseCase
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.coroutines.flow.runningReduce
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioEvent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a [Flow] of [MessageAudioEvent]s related to the given voice broadcast.
|
||||||
|
*/
|
||||||
|
class GetLiveVoiceBroadcastChunksUseCase @Inject constructor(
|
||||||
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
|
private val getVoiceBroadcastUseCase: GetVoiceBroadcastUseCase,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun execute(roomId: String, voiceBroadcastId: String): Flow<List<MessageAudioEvent>> {
|
||||||
|
val session = activeSessionHolder.getSafeActiveSession() ?: return emptyFlow()
|
||||||
|
val room = session.roomService().getRoom(roomId) ?: return emptyFlow()
|
||||||
|
val timeline = room.timelineService().createTimeline(null, TimelineSettings(5))
|
||||||
|
|
||||||
|
// Get initial chunks
|
||||||
|
val existingChunks = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcastId)
|
||||||
|
.mapNotNull { timelineEvent -> timelineEvent.root.asMessageAudioEvent().takeIf { it.isVoiceBroadcast() } }
|
||||||
|
|
||||||
|
val voiceBroadcastEvent = getVoiceBroadcastUseCase.execute(roomId, voiceBroadcastId)
|
||||||
|
val voiceBroadcastState = voiceBroadcastEvent?.content?.voiceBroadcastState
|
||||||
|
|
||||||
|
return if (voiceBroadcastState == null || voiceBroadcastState == VoiceBroadcastState.STOPPED) {
|
||||||
|
// Just send the existing chunks if voice broadcast is stopped
|
||||||
|
flowOf(existingChunks)
|
||||||
|
} else {
|
||||||
|
// Observe new timeline events if voice broadcast is ongoing
|
||||||
|
callbackFlow {
|
||||||
|
// Init with existing chunks
|
||||||
|
send(existingChunks)
|
||||||
|
|
||||||
|
// Observe new timeline events
|
||||||
|
val listener = object : Timeline.Listener {
|
||||||
|
private var lastEventId: String? = null
|
||||||
|
private var lastSequence: Int? = null
|
||||||
|
|
||||||
|
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||||
|
val newEvents = lastEventId?.let { eventId -> snapshot.subList(0, snapshot.indexOfFirst { it.eventId == eventId }) } ?: snapshot
|
||||||
|
|
||||||
|
// Detect a potential stopped voice broadcast state event
|
||||||
|
val stopEvent = newEvents.findStopEvent()
|
||||||
|
if (stopEvent != null) {
|
||||||
|
lastSequence = stopEvent.content?.lastChunkSequence
|
||||||
|
}
|
||||||
|
|
||||||
|
val newChunks = newEvents.mapToChunkEvents(voiceBroadcastId, voiceBroadcastEvent.root.senderId)
|
||||||
|
|
||||||
|
// Notify about new chunks
|
||||||
|
if (newChunks.isNotEmpty()) {
|
||||||
|
trySend(newChunks)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Automatically stop observing the timeline if the last chunk has been received
|
||||||
|
if (lastSequence != null && newChunks.any { it.sequence == lastSequence }) {
|
||||||
|
timeline.removeListener(this)
|
||||||
|
timeline.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
lastEventId = snapshot.firstOrNull()?.eventId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timeline.addListener(listener)
|
||||||
|
timeline.start()
|
||||||
|
awaitClose {
|
||||||
|
timeline.removeListener(listener)
|
||||||
|
timeline.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.runningReduce { accumulator: List<MessageAudioEvent>, value: List<MessageAudioEvent> -> accumulator.plus(value) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a [VoiceBroadcastEvent] with a [VoiceBroadcastState.STOPPED] state.
|
||||||
|
*/
|
||||||
|
private fun List<TimelineEvent>.findStopEvent(): VoiceBroadcastEvent? =
|
||||||
|
this.mapNotNull { it.root.asVoiceBroadcastEvent() }
|
||||||
|
.find { it.content?.voiceBroadcastState == VoiceBroadcastState.STOPPED }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform the list of [TimelineEvent] to a mapped list of [MessageAudioEvent] related to a given voice broadcast.
|
||||||
|
*/
|
||||||
|
private fun List<TimelineEvent>.mapToChunkEvents(voiceBroadcastId: String, senderId: String?): List<MessageAudioEvent> =
|
||||||
|
this.mapNotNull { timelineEvent ->
|
||||||
|
timelineEvent.root.asMessageAudioEvent()
|
||||||
|
?.takeIf {
|
||||||
|
it.isVoiceBroadcast() && it.getVoiceBroadcastEventId() == voiceBroadcastId &&
|
||||||
|
it.root.senderId == senderId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.voicebroadcast
|
package im.vector.app.features.voicebroadcast.recording
|
||||||
|
|
||||||
import androidx.annotation.IntRange
|
import androidx.annotation.IntRange
|
||||||
import im.vector.app.features.voice.VoiceRecorder
|
import im.vector.app.features.voice.VoiceRecorder
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.voicebroadcast
|
package im.vector.app.features.voicebroadcast.recording
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.media.MediaRecorder
|
import android.media.MediaRecorder
|
@ -14,13 +14,13 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.voicebroadcast.usecase
|
package im.vector.app.features.voicebroadcast.recording.usecase
|
||||||
|
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
|
|
||||||
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
|
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
@ -14,13 +14,13 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.voicebroadcast.usecase
|
package im.vector.app.features.voicebroadcast.recording.usecase
|
||||||
|
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
|
|
||||||
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
|
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
@ -14,17 +14,18 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.voicebroadcast.usecase
|
package im.vector.app.features.voicebroadcast.recording.usecase
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import im.vector.app.core.resources.BuildMeta
|
import im.vector.app.core.resources.BuildMeta
|
||||||
import im.vector.app.features.attachments.toContentAttachmentData
|
import im.vector.app.features.attachments.toContentAttachmentData
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
|
|
||||||
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastChunk
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastChunk
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
|
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||||
|
import im.vector.app.features.voicebroadcast.usecase.GetOngoingVoiceBroadcastsUseCase
|
||||||
import im.vector.lib.multipicker.utils.toMultiPickerAudioType
|
import im.vector.lib.multipicker.utils.toMultiPickerAudioType
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
@ -14,11 +14,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.voicebroadcast.usecase
|
package im.vector.app.features.voicebroadcast.recording.usecase
|
||||||
|
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
|
import im.vector.app.features.voicebroadcast.usecase.GetOngoingVoiceBroadcastsUseCase
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
@ -14,13 +14,13 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.voicebroadcast.usecase
|
package im.vector.app.features.voicebroadcast.recording.usecase
|
||||||
|
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
|
|
||||||
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
|
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
@ -17,9 +17,10 @@
|
|||||||
package im.vector.app.features.voicebroadcast.usecase
|
package im.vector.app.features.voicebroadcast.usecase
|
||||||
|
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
|
|
||||||
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
|
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||||
|
import im.vector.app.features.voicebroadcast.recording.usecase.PauseVoiceBroadcastUseCase
|
||||||
import im.vector.app.test.fakes.FakeRoom
|
import im.vector.app.test.fakes.FakeRoom
|
||||||
import im.vector.app.test.fakes.FakeRoomService
|
import im.vector.app.test.fakes.FakeRoomService
|
||||||
import im.vector.app.test.fakes.FakeSession
|
import im.vector.app.test.fakes.FakeSession
|
||||||
|
@ -17,9 +17,10 @@
|
|||||||
package im.vector.app.features.voicebroadcast.usecase
|
package im.vector.app.features.voicebroadcast.usecase
|
||||||
|
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
|
|
||||||
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
|
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||||
|
import im.vector.app.features.voicebroadcast.recording.usecase.ResumeVoiceBroadcastUseCase
|
||||||
import im.vector.app.test.fakes.FakeRoom
|
import im.vector.app.test.fakes.FakeRoom
|
||||||
import im.vector.app.test.fakes.FakeRoomService
|
import im.vector.app.test.fakes.FakeRoomService
|
||||||
import im.vector.app.test.fakes.FakeSession
|
import im.vector.app.test.fakes.FakeSession
|
||||||
|
@ -17,10 +17,11 @@
|
|||||||
package im.vector.app.features.voicebroadcast.usecase
|
package im.vector.app.features.voicebroadcast.usecase
|
||||||
|
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
|
|
||||||
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
|
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||||
|
import im.vector.app.features.voicebroadcast.recording.usecase.StartVoiceBroadcastUseCase
|
||||||
import im.vector.app.test.fakes.FakeContext
|
import im.vector.app.test.fakes.FakeContext
|
||||||
import im.vector.app.test.fakes.FakeRoom
|
import im.vector.app.test.fakes.FakeRoom
|
||||||
import im.vector.app.test.fakes.FakeRoomService
|
import im.vector.app.test.fakes.FakeRoomService
|
||||||
|
@ -17,9 +17,10 @@
|
|||||||
package im.vector.app.features.voicebroadcast.usecase
|
package im.vector.app.features.voicebroadcast.usecase
|
||||||
|
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
|
|
||||||
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
|
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||||
|
import im.vector.app.features.voicebroadcast.recording.usecase.StopVoiceBroadcastUseCase
|
||||||
import im.vector.app.test.fakes.FakeRoom
|
import im.vector.app.test.fakes.FakeRoom
|
||||||
import im.vector.app.test.fakes.FakeRoomService
|
import im.vector.app.test.fakes.FakeRoomService
|
||||||
import im.vector.app.test.fakes.FakeSession
|
import im.vector.app.test.fakes.FakeSession
|
||||||
|
Loading…
x
Reference in New Issue
Block a user