David Liu

More fixing up sample app

@@ -9,5 +9,7 @@ class InjectionNames { @@ -9,5 +9,7 @@ class InjectionNames {
9 const val DISPATCHER_UNCONFINED = "dispatcher_unconfined" 9 const val DISPATCHER_UNCONFINED = "dispatcher_unconfined"
10 10
11 const val SIGNAL_JSON_ENABLED = "signal_json_enabled" 11 const val SIGNAL_JSON_ENABLED = "signal_json_enabled"
  12 +
  13 + const val OPTIONS_VIDEO_HW_ACCEL = "options_video_hw_accel"
12 } 14 }
13 } 15 }
@@ -7,6 +7,7 @@ import dagger.Provides @@ -7,6 +7,7 @@ import dagger.Provides
7 import org.webrtc.* 7 import org.webrtc.*
8 import org.webrtc.audio.AudioDeviceModule 8 import org.webrtc.audio.AudioDeviceModule
9 import org.webrtc.audio.JavaAudioDeviceModule 9 import org.webrtc.audio.JavaAudioDeviceModule
  10 +import javax.inject.Named
10 import javax.inject.Singleton 11 import javax.inject.Singleton
11 12
12 13
@@ -76,8 +77,6 @@ class RTCModule { @@ -76,8 +77,6 @@ class RTCModule {
76 } 77 }
77 78
78 return JavaAudioDeviceModule.builder(appContext) 79 return JavaAudioDeviceModule.builder(appContext)
79 - .setUseHardwareAcousticEchoCanceler(true)  
80 - .setUseHardwareNoiseSuppressor(true)  
81 .setAudioRecordErrorCallback(audioRecordErrorCallback) 80 .setAudioRecordErrorCallback(audioRecordErrorCallback)
82 .setAudioTrackErrorCallback(audioTrackErrorCallback) 81 .setAudioTrackErrorCallback(audioTrackErrorCallback)
83 .setAudioRecordStateCallback(audioRecordStateCallback) 82 .setAudioRecordStateCallback(audioRecordStateCallback)
@@ -95,16 +94,35 @@ class RTCModule { @@ -95,16 +94,35 @@ class RTCModule {
95 fun eglContext(eglBase: EglBase): EglBase.Context = eglBase.eglBaseContext 94 fun eglContext(eglBase: EglBase): EglBase.Context = eglBase.eglBaseContext
96 95
97 @Provides 96 @Provides
98 - fun videoEncoderFactory(eglContext: EglBase.Context): VideoEncoderFactory =  
99 - DefaultVideoEncoderFactory(  
100 - eglContext,  
101 - true,  
102 - true  
103 - ) 97 + fun videoEncoderFactory(
  98 + @Named(InjectionNames.OPTIONS_VIDEO_HW_ACCEL)
  99 + videoHwAccel: Boolean,
  100 + eglContext: EglBase.Context
  101 + ): VideoEncoderFactory {
  102 +
  103 + return if (videoHwAccel) {
  104 + DefaultVideoEncoderFactory(
  105 + eglContext,
  106 + true,
  107 + true
  108 + )
  109 + } else {
  110 + SoftwareVideoEncoderFactory()
  111 + }
  112 + }
104 113
105 @Provides 114 @Provides
106 - fun videoDecoderFactory(eglContext: EglBase.Context): VideoDecoderFactory =  
107 - DefaultVideoDecoderFactory(eglContext) 115 + fun videoDecoderFactory(
  116 + @Named(InjectionNames.OPTIONS_VIDEO_HW_ACCEL)
  117 + videoHwAccel: Boolean,
  118 + eglContext: EglBase.Context,
  119 + ): VideoDecoderFactory {
  120 + return if (videoHwAccel) {
  121 + DefaultVideoDecoderFactory(eglContext)
  122 + } else {
  123 + SoftwareVideoDecoderFactory()
  124 + }
  125 + }
108 126
109 @Provides 127 @Provides
110 @Singleton 128 @Singleton
@@ -127,5 +145,8 @@ class RTCModule { @@ -127,5 +145,8 @@ class RTCModule {
127 .createPeerConnectionFactory() 145 .createPeerConnectionFactory()
128 } 146 }
129 147
  148 + @Provides
  149 + @Named(InjectionNames.OPTIONS_VIDEO_HW_ACCEL)
  150 + fun videoHwAccel() = false
130 } 151 }
131 } 152 }
@@ -199,6 +199,11 @@ constructor( @@ -199,6 +199,11 @@ constructor(
199 override fun onUpdateParticipants(updates: List<Model.ParticipantInfo>) { 199 override fun onUpdateParticipants(updates: List<Model.ParticipantInfo>) {
200 for (info in updates) { 200 for (info in updates) {
201 val participantSid = Participant.Sid(info.sid) 201 val participantSid = Participant.Sid(info.sid)
  202 +
  203 + if(localParticipant?.sid == participantSid) {
  204 + localParticipant?.updateFromInfo(info)
  205 + }
  206 +
202 val isNewParticipant = remoteParticipants.contains(participantSid) 207 val isNewParticipant = remoteParticipants.contains(participantSid)
203 val participant = getOrCreateRemoteParticipant(participantSid, info) 208 val participant = getOrCreateRemoteParticipant(participantSid, info)
204 209
@@ -16,7 +16,7 @@ class SubscriberTransportObserver( @@ -16,7 +16,7 @@ class SubscriberTransportObserver(
16 16
17 override fun onAddTrack(receiver: RtpReceiver, streams: Array<out MediaStream>) { 17 override fun onAddTrack(receiver: RtpReceiver, streams: Array<out MediaStream>) {
18 val track = receiver.track() ?: return 18 val track = receiver.track() ?: return
19 - Timber.v { "onAddTrack: $track, ${streams.fold("") { sum, it -> "$sum, $it" }}" } 19 + Timber.v { "onAddTrack: ${track.kind()}, ${track.id()}, ${streams.fold("") { sum, it -> "$sum, $it" }}" }
20 engine.listener?.onAddTrack(track, streams) 20 engine.listener?.onAddTrack(track, streams)
21 } 21 }
22 22
@@ -29,6 +29,12 @@ class LocalParticipant(sid: Sid, name: String? = null) : @@ -29,6 +29,12 @@ class LocalParticipant(sid: Sid, name: String? = null) :
29 var engine: RTCEngine? = null 29 var engine: RTCEngine? = null
30 val listener: Listener? = null 30 val listener: Listener? = null
31 31
  32 + fun updateFromInfo(info: Model.ParticipantInfo) {
  33 + sid = Sid(info.sid)
  34 + name = info.identity
  35 + metadata = info.metadata
  36 + }
  37 +
32 suspend fun publishAudioTrack( 38 suspend fun publishAudioTrack(
33 track: LocalAudioTrack, 39 track: LocalAudioTrack,
34 options: LocalTrackPublicationOptions? = null 40 options: LocalTrackPublicationOptions? = null
1 package io.livekit.android.room.track 1 package io.livekit.android.room.track
2 2
3 class RemoteAudioTrack( 3 class RemoteAudioTrack(
4 - sid: Track.Sid, 4 + sid: Sid,
5 playbackEnabled: Boolean = true, 5 playbackEnabled: Boolean = true,
6 name: String, 6 name: String,
7 rtcTrack: org.webrtc.AudioTrack 7 rtcTrack: org.webrtc.AudioTrack
8 ) : AudioTrack(name, rtcTrack), RemoteTrack { 8 ) : AudioTrack(name, rtcTrack), RemoteTrack {
9 9
10 - override var sid: Track.Sid = sid 10 +
  11 + override var sid: Sid = sid
11 var playbackEnabled = playbackEnabled 12 var playbackEnabled = playbackEnabled
12 internal set 13 internal set
13 14
@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 3
4 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> 4 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
5 <uses-permission android:name="android.permission.INTERNET" /> 5 <uses-permission android:name="android.permission.INTERNET" />
  6 + <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
6 7
7 <application 8 <application
8 android:name=".SampleApplication" 9 android:name=".SampleApplication"
1 package io.livekit.android.sample 1 package io.livekit.android.sample
2 2
  3 +import android.media.AudioManager
3 import android.os.Bundle 4 import android.os.Bundle
4 import android.os.Parcelable 5 import android.os.Parcelable
5 import androidx.appcompat.app.AppCompatActivity 6 import androidx.appcompat.app.AppCompatActivity
  7 +import com.github.ajalt.timberkt.Timber
6 import com.snakydesign.livedataextensions.combineLatest 8 import com.snakydesign.livedataextensions.combineLatest
7 import com.xwray.groupie.GroupieAdapter 9 import com.xwray.groupie.GroupieAdapter
8 import io.livekit.android.sample.databinding.CallActivityBinding 10 import io.livekit.android.sample.databinding.CallActivityBinding
@@ -25,6 +27,7 @@ class CallActivity : AppCompatActivity() { @@ -25,6 +27,7 @@ class CallActivity : AppCompatActivity() {
25 27
26 setContentView(binding.root) 28 setContentView(binding.root)
27 29
  30 + // Viewpager setup
28 val adapter = GroupieAdapter() 31 val adapter = GroupieAdapter()
29 binding.viewPager.apply { 32 binding.viewPager.apply {
30 this.adapter = adapter 33 this.adapter = adapter
@@ -39,6 +42,22 @@ class CallActivity : AppCompatActivity() { @@ -39,6 +42,22 @@ class CallActivity : AppCompatActivity() {
39 val items = participants.map { participant -> ParticipantItem(room, participant) } 42 val items = participants.map { participant -> ParticipantItem(room, participant) }
40 adapter.update(items) 43 adapter.update(items)
41 } 44 }
  45 +
  46 + val audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
  47 + with(audioManager) {
  48 + isSpeakerphoneOn = true
  49 + isMicrophoneMute = false
  50 + mode = AudioManager.MODE_IN_COMMUNICATION
  51 + }
  52 + val result = audioManager.requestAudioFocus(
  53 + { },
  54 + AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN
  55 + )
  56 + if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
  57 + Timber.v { "Audio focus request granted for VOICE_CALL streams" }
  58 + } else {
  59 + Timber.v { "Audio focus request failed" }
  60 + }
42 } 61 }
43 62
44 63
@@ -35,6 +35,7 @@ class CallViewModel( @@ -35,6 +35,7 @@ class CallViewModel(
35 ConnectOptions(false), 35 ConnectOptions(false),
36 object : Room.Listener { 36 object : Room.Listener {
37 override fun onConnect(room: Room) { 37 override fun onConnect(room: Room) {
  38 + updateParticipants(room)
38 } 39 }
39 40
40 override fun onDisconnect(room: Room, error: Exception?) { 41 override fun onDisconnect(room: Room, error: Exception?) {
@@ -61,6 +62,7 @@ class CallViewModel( @@ -61,6 +62,7 @@ class CallViewModel(
61 } 62 }
62 63
63 override fun onReconnect(room: Room) { 64 override fun onReconnect(room: Room) {
  65 + updateParticipants(room)
64 } 66 }
65 67
66 override fun onStartRecording(room: Room) { 68 override fun onStartRecording(room: Room) {
@@ -28,21 +28,20 @@ class ParticipantItem( @@ -28,21 +28,20 @@ class ParticipantItem(
28 override fun bind(viewBinding: ParticipantItemBinding, position: Int) { 28 override fun bind(viewBinding: ParticipantItemBinding, position: Int) {
29 viewBinding.run { 29 viewBinding.run {
30 30
  31 + remoteParticipant.listener = object : RemoteParticipant.Listener {
  32 + override fun onSubscribe(
  33 + videoTrack: RemoteVideoTrackPublication,
  34 + participant: RemoteParticipant
  35 + ) {
  36 + val track = videoTrack.videoTrack
  37 + if (track != null) {
  38 + setupVideoIfNeeded(track, viewBinding)
  39 + }
  40 + }
  41 + }
31 val existingTrack = getVideoTrack() 42 val existingTrack = getVideoTrack()
32 if (existingTrack != null) { 43 if (existingTrack != null) {
33 setupVideoIfNeeded(existingTrack, viewBinding) 44 setupVideoIfNeeded(existingTrack, viewBinding)
34 - } else {  
35 - remoteParticipant.listener = object : RemoteParticipant.Listener {  
36 - override fun onSubscribe(  
37 - videoTrack: RemoteVideoTrackPublication,  
38 - participant: RemoteParticipant  
39 - ) {  
40 - val track = videoTrack.videoTrack  
41 - if (track != null) {  
42 - setupVideoIfNeeded(track, viewBinding)  
43 - }  
44 - }  
45 - }  
46 } 45 }
47 } 46 }
48 } 47 }
@@ -60,6 +59,7 @@ class ParticipantItem( @@ -60,6 +59,7 @@ class ParticipantItem(
60 return 59 return
61 } 60 }
62 61
  62 + videoBound = true
63 Timber.v { "adding renderer to $videoTrack" } 63 Timber.v { "adding renderer to $videoTrack" }
64 videoTrack.addRenderer(viewBinding.renderer) 64 videoTrack.addRenderer(viewBinding.renderer)
65 } 65 }
@@ -7,11 +7,12 @@ @@ -7,11 +7,12 @@
7 android:layout_width="match_parent" 7 android:layout_width="match_parent"
8 android:layout_height="match_parent" /> 8 android:layout_height="match_parent" />
9 9
  10 + <!--
10 <org.webrtc.SurfaceViewRenderer 11 <org.webrtc.SurfaceViewRenderer
11 android:id="@+id/pip_video_view" 12 android:id="@+id/pip_video_view"
12 android:layout_height="144dp" 13 android:layout_height="144dp"
13 android:layout_width="wrap_content" 14 android:layout_width="wrap_content"
14 android:layout_gravity="bottom|end" 15 android:layout_gravity="bottom|end"
15 android:layout_margin="16dp" /> 16 android:layout_margin="16dp" />
16 - 17 + -->
17 </FrameLayout> 18 </FrameLayout>