正在显示
11 个修改的文件
包含
84 行增加
和
26 行删除
| @@ -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> |
-
请 注册 或 登录 后发表评论