Committed by
GitHub
mock e2e tests (#19)
* mock e2e tests * fix peer connection mock initialization
正在显示
18 个修改的文件
包含
653 行增加
和
163 行删除
| @@ -43,7 +43,7 @@ android { | @@ -43,7 +43,7 @@ android { | ||
| 43 | targetCompatibility java_version | 43 | targetCompatibility java_version |
| 44 | } | 44 | } |
| 45 | kotlinOptions { | 45 | kotlinOptions { |
| 46 | - freeCompilerArgs = ["-Xinline-classes"] | 46 | + freeCompilerArgs = ["-Xinline-classes", "-Xopt-in=kotlin.RequiresOptIn"] |
| 47 | jvmTarget = java_version | 47 | jvmTarget = java_version |
| 48 | } | 48 | } |
| 49 | } | 49 | } |
| @@ -117,6 +117,7 @@ dependencies { | @@ -117,6 +117,7 @@ dependencies { | ||
| 117 | testImplementation "org.mockito.kotlin:mockito-kotlin:3.1.0" | 117 | testImplementation "org.mockito.kotlin:mockito-kotlin:3.1.0" |
| 118 | testImplementation 'androidx.test:core:1.4.0' | 118 | testImplementation 'androidx.test:core:1.4.0' |
| 119 | testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.3" | 119 | testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.3" |
| 120 | + kaptTest 'com.google.dagger:dagger-compiler:2.38' | ||
| 120 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' | 121 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' |
| 121 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' | 122 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' |
| 122 | } | 123 | } |
| @@ -8,27 +8,25 @@ import kotlinx.serialization.json.Json | @@ -8,27 +8,25 @@ import kotlinx.serialization.json.Json | ||
| 8 | import javax.inject.Named | 8 | import javax.inject.Named |
| 9 | 9 | ||
| 10 | @Module | 10 | @Module |
| 11 | -class JsonFormatModule { | ||
| 12 | - companion object { | ||
| 13 | - @Provides | ||
| 14 | - fun protobufJsonFormatParser(): JsonFormat.Parser { | ||
| 15 | - return JsonFormat.parser() | ||
| 16 | - } | 11 | +object JsonFormatModule { |
| 12 | + @Provides | ||
| 13 | + fun protobufJsonFormatParser(): JsonFormat.Parser { | ||
| 14 | + return JsonFormat.parser() | ||
| 15 | + } | ||
| 17 | 16 | ||
| 18 | - @Provides | ||
| 19 | - fun protobufJsonFormatPrinter(): JsonFormat.Printer { | ||
| 20 | - return JsonFormat.printer() | ||
| 21 | - } | 17 | + @Provides |
| 18 | + fun protobufJsonFormatPrinter(): JsonFormat.Printer { | ||
| 19 | + return JsonFormat.printer() | ||
| 20 | + } | ||
| 22 | 21 | ||
| 23 | - @Provides | ||
| 24 | - @Reusable | ||
| 25 | - fun kotlinSerializationJson(): Json = | ||
| 26 | - Json { | ||
| 27 | - ignoreUnknownKeys = true | ||
| 28 | - } | 22 | + @Provides |
| 23 | + @Reusable | ||
| 24 | + fun kotlinSerializationJson(): Json = | ||
| 25 | + Json { | ||
| 26 | + ignoreUnknownKeys = true | ||
| 27 | + } | ||
| 29 | 28 | ||
| 30 | - @Provides | ||
| 31 | - @Named(InjectionNames.SIGNAL_JSON_ENABLED) | ||
| 32 | - fun signalJsonEnabled(): Boolean = false | ||
| 33 | - } | 29 | + @Provides |
| 30 | + @Named(InjectionNames.SIGNAL_JSON_ENABLED) | ||
| 31 | + fun signalJsonEnabled(): Boolean = false | ||
| 34 | } | 32 | } |
| @@ -13,143 +13,141 @@ import javax.inject.Singleton | @@ -13,143 +13,141 @@ import javax.inject.Singleton | ||
| 13 | 13 | ||
| 14 | 14 | ||
| 15 | @Module | 15 | @Module |
| 16 | -class RTCModule { | ||
| 17 | - companion object { | ||
| 18 | - @Provides | ||
| 19 | - @Singleton | ||
| 20 | - fun audioModule(appContext: Context): AudioDeviceModule { | ||
| 21 | - | ||
| 22 | - // Set audio record error callbacks. | ||
| 23 | - val audioRecordErrorCallback = object : JavaAudioDeviceModule.AudioRecordErrorCallback { | ||
| 24 | - override fun onWebRtcAudioRecordInitError(errorMessage: String?) { | ||
| 25 | - LKLog.e { "onWebRtcAudioRecordInitError: $errorMessage" } | ||
| 26 | - } | ||
| 27 | - | ||
| 28 | - override fun onWebRtcAudioRecordStartError( | ||
| 29 | - errorCode: JavaAudioDeviceModule.AudioRecordStartErrorCode?, | ||
| 30 | - errorMessage: String? | ||
| 31 | - ) { | ||
| 32 | - LKLog.e { "onWebRtcAudioRecordStartError: $errorCode. $errorMessage" } | ||
| 33 | - } | ||
| 34 | - | ||
| 35 | - override fun onWebRtcAudioRecordError(errorMessage: String?) { | ||
| 36 | - LKLog.e { "onWebRtcAudioRecordError: $errorMessage" } | ||
| 37 | - } | 16 | +object RTCModule { |
| 17 | + @Provides | ||
| 18 | + @Singleton | ||
| 19 | + fun audioModule(appContext: Context): AudioDeviceModule { | ||
| 20 | + | ||
| 21 | + // Set audio record error callbacks. | ||
| 22 | + val audioRecordErrorCallback = object : JavaAudioDeviceModule.AudioRecordErrorCallback { | ||
| 23 | + override fun onWebRtcAudioRecordInitError(errorMessage: String?) { | ||
| 24 | + LKLog.e { "onWebRtcAudioRecordInitError: $errorMessage" } | ||
| 38 | } | 25 | } |
| 39 | 26 | ||
| 40 | - val audioTrackErrorCallback = object : JavaAudioDeviceModule.AudioTrackErrorCallback { | ||
| 41 | - override fun onWebRtcAudioTrackInitError(errorMessage: String?) { | ||
| 42 | - LKLog.e { "onWebRtcAudioTrackInitError: $errorMessage" } | ||
| 43 | - } | ||
| 44 | - | ||
| 45 | - override fun onWebRtcAudioTrackStartError( | ||
| 46 | - errorCode: JavaAudioDeviceModule.AudioTrackStartErrorCode?, | ||
| 47 | - errorMessage: String? | ||
| 48 | - ) { | ||
| 49 | - LKLog.e { "onWebRtcAudioTrackStartError: $errorCode. $errorMessage" } | ||
| 50 | - } | ||
| 51 | - | ||
| 52 | - override fun onWebRtcAudioTrackError(errorMessage: String?) { | ||
| 53 | - LKLog.e { "onWebRtcAudioTrackError: $errorMessage" } | ||
| 54 | - } | 27 | + override fun onWebRtcAudioRecordStartError( |
| 28 | + errorCode: JavaAudioDeviceModule.AudioRecordStartErrorCode?, | ||
| 29 | + errorMessage: String? | ||
| 30 | + ) { | ||
| 31 | + LKLog.e { "onWebRtcAudioRecordStartError: $errorCode. $errorMessage" } | ||
| 32 | + } | ||
| 55 | 33 | ||
| 34 | + override fun onWebRtcAudioRecordError(errorMessage: String?) { | ||
| 35 | + LKLog.e { "onWebRtcAudioRecordError: $errorMessage" } | ||
| 56 | } | 36 | } |
| 57 | - val audioRecordStateCallback: JavaAudioDeviceModule.AudioRecordStateCallback = object : | ||
| 58 | - JavaAudioDeviceModule.AudioRecordStateCallback { | ||
| 59 | - override fun onWebRtcAudioRecordStart() { | ||
| 60 | - LKLog.i { "Audio recording starts" } | ||
| 61 | - } | ||
| 62 | - | ||
| 63 | - override fun onWebRtcAudioRecordStop() { | ||
| 64 | - LKLog.i { "Audio recording stops" } | ||
| 65 | - } | 37 | + } |
| 38 | + | ||
| 39 | + val audioTrackErrorCallback = object : JavaAudioDeviceModule.AudioTrackErrorCallback { | ||
| 40 | + override fun onWebRtcAudioTrackInitError(errorMessage: String?) { | ||
| 41 | + LKLog.e { "onWebRtcAudioTrackInitError: $errorMessage" } | ||
| 66 | } | 42 | } |
| 67 | 43 | ||
| 68 | - // Set audio track state callbacks. | ||
| 69 | - val audioTrackStateCallback: JavaAudioDeviceModule.AudioTrackStateCallback = object : | ||
| 70 | - JavaAudioDeviceModule.AudioTrackStateCallback { | ||
| 71 | - override fun onWebRtcAudioTrackStart() { | ||
| 72 | - LKLog.i { "Audio playout starts" } | ||
| 73 | - } | 44 | + override fun onWebRtcAudioTrackStartError( |
| 45 | + errorCode: JavaAudioDeviceModule.AudioTrackStartErrorCode?, | ||
| 46 | + errorMessage: String? | ||
| 47 | + ) { | ||
| 48 | + LKLog.e { "onWebRtcAudioTrackStartError: $errorCode. $errorMessage" } | ||
| 49 | + } | ||
| 74 | 50 | ||
| 75 | - override fun onWebRtcAudioTrackStop() { | ||
| 76 | - LKLog.i { "Audio playout stops" } | ||
| 77 | - } | 51 | + override fun onWebRtcAudioTrackError(errorMessage: String?) { |
| 52 | + LKLog.e { "onWebRtcAudioTrackError: $errorMessage" } | ||
| 78 | } | 53 | } |
| 79 | 54 | ||
| 80 | - return JavaAudioDeviceModule.builder(appContext) | ||
| 81 | - .setUseHardwareAcousticEchoCanceler(true) | ||
| 82 | - .setUseHardwareNoiseSuppressor(true) | ||
| 83 | - .setAudioRecordErrorCallback(audioRecordErrorCallback) | ||
| 84 | - .setAudioTrackErrorCallback(audioTrackErrorCallback) | ||
| 85 | - .setAudioRecordStateCallback(audioRecordStateCallback) | ||
| 86 | - .setAudioTrackStateCallback(audioTrackStateCallback) | ||
| 87 | - .createAudioDeviceModule() | ||
| 88 | } | 55 | } |
| 56 | + val audioRecordStateCallback: JavaAudioDeviceModule.AudioRecordStateCallback = object : | ||
| 57 | + JavaAudioDeviceModule.AudioRecordStateCallback { | ||
| 58 | + override fun onWebRtcAudioRecordStart() { | ||
| 59 | + LKLog.i { "Audio recording starts" } | ||
| 60 | + } | ||
| 89 | 61 | ||
| 90 | - @Provides | ||
| 91 | - @Singleton | ||
| 92 | - fun eglBase(): EglBase { | ||
| 93 | - return EglBase.create() | 62 | + override fun onWebRtcAudioRecordStop() { |
| 63 | + LKLog.i { "Audio recording stops" } | ||
| 64 | + } | ||
| 94 | } | 65 | } |
| 95 | 66 | ||
| 96 | - @Provides | ||
| 97 | - fun eglContext(eglBase: EglBase): EglBase.Context = eglBase.eglBaseContext | ||
| 98 | - | ||
| 99 | - @Provides | ||
| 100 | - fun videoEncoderFactory( | ||
| 101 | - @Named(InjectionNames.OPTIONS_VIDEO_HW_ACCEL) | ||
| 102 | - videoHwAccel: Boolean, | ||
| 103 | - eglContext: EglBase.Context | ||
| 104 | - ): VideoEncoderFactory { | ||
| 105 | - | ||
| 106 | - return if (videoHwAccel) { | ||
| 107 | - SimulcastVideoEncoderFactoryWrapper( | ||
| 108 | - eglContext, | ||
| 109 | - enableIntelVp8Encoder = true, | ||
| 110 | - enableH264HighProfile = false, | ||
| 111 | - ) | ||
| 112 | - } else { | ||
| 113 | - SoftwareVideoEncoderFactory() | 67 | + // Set audio track state callbacks. |
| 68 | + val audioTrackStateCallback: JavaAudioDeviceModule.AudioTrackStateCallback = object : | ||
| 69 | + JavaAudioDeviceModule.AudioTrackStateCallback { | ||
| 70 | + override fun onWebRtcAudioTrackStart() { | ||
| 71 | + LKLog.i { "Audio playout starts" } | ||
| 114 | } | 72 | } |
| 115 | - } | ||
| 116 | 73 | ||
| 117 | - @Provides | ||
| 118 | - fun videoDecoderFactory( | ||
| 119 | - @Named(InjectionNames.OPTIONS_VIDEO_HW_ACCEL) | ||
| 120 | - videoHwAccel: Boolean, | ||
| 121 | - eglContext: EglBase.Context, | ||
| 122 | - ): VideoDecoderFactory { | ||
| 123 | - return if (videoHwAccel) { | ||
| 124 | - DefaultVideoDecoderFactory(eglContext) | ||
| 125 | - } else { | ||
| 126 | - SoftwareVideoDecoderFactory() | 74 | + override fun onWebRtcAudioTrackStop() { |
| 75 | + LKLog.i { "Audio playout stops" } | ||
| 127 | } | 76 | } |
| 128 | } | 77 | } |
| 129 | 78 | ||
| 130 | - @Provides | ||
| 131 | - @Singleton | ||
| 132 | - fun peerConnectionFactory( | ||
| 133 | - appContext: Context, | ||
| 134 | - audioDeviceModule: AudioDeviceModule, | ||
| 135 | - videoEncoderFactory: VideoEncoderFactory, | ||
| 136 | - videoDecoderFactory: VideoDecoderFactory, | ||
| 137 | - ): PeerConnectionFactory { | ||
| 138 | - PeerConnectionFactory.initialize( | ||
| 139 | - PeerConnectionFactory.InitializationOptions | ||
| 140 | - .builder(appContext) | ||
| 141 | - .createInitializationOptions() | ||
| 142 | - ) | 79 | + return JavaAudioDeviceModule.builder(appContext) |
| 80 | + .setUseHardwareAcousticEchoCanceler(true) | ||
| 81 | + .setUseHardwareNoiseSuppressor(true) | ||
| 82 | + .setAudioRecordErrorCallback(audioRecordErrorCallback) | ||
| 83 | + .setAudioTrackErrorCallback(audioTrackErrorCallback) | ||
| 84 | + .setAudioRecordStateCallback(audioRecordStateCallback) | ||
| 85 | + .setAudioTrackStateCallback(audioTrackStateCallback) | ||
| 86 | + .createAudioDeviceModule() | ||
| 87 | + } | ||
| 143 | 88 | ||
| 144 | - return PeerConnectionFactory.builder() | ||
| 145 | - .setAudioDeviceModule(audioDeviceModule) | ||
| 146 | - .setVideoEncoderFactory(videoEncoderFactory) | ||
| 147 | - .setVideoDecoderFactory(videoDecoderFactory) | ||
| 148 | - .createPeerConnectionFactory() | 89 | + @Provides |
| 90 | + @Singleton | ||
| 91 | + fun eglBase(): EglBase { | ||
| 92 | + return EglBase.create() | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + @Provides | ||
| 96 | + fun eglContext(eglBase: EglBase): EglBase.Context = eglBase.eglBaseContext | ||
| 97 | + | ||
| 98 | + @Provides | ||
| 99 | + fun videoEncoderFactory( | ||
| 100 | + @Named(InjectionNames.OPTIONS_VIDEO_HW_ACCEL) | ||
| 101 | + videoHwAccel: Boolean, | ||
| 102 | + eglContext: EglBase.Context | ||
| 103 | + ): VideoEncoderFactory { | ||
| 104 | + | ||
| 105 | + return if (videoHwAccel) { | ||
| 106 | + SimulcastVideoEncoderFactoryWrapper( | ||
| 107 | + eglContext, | ||
| 108 | + enableIntelVp8Encoder = true, | ||
| 109 | + enableH264HighProfile = false, | ||
| 110 | + ) | ||
| 111 | + } else { | ||
| 112 | + SoftwareVideoEncoderFactory() | ||
| 149 | } | 113 | } |
| 114 | + } | ||
| 150 | 115 | ||
| 151 | - @Provides | 116 | + @Provides |
| 117 | + fun videoDecoderFactory( | ||
| 152 | @Named(InjectionNames.OPTIONS_VIDEO_HW_ACCEL) | 118 | @Named(InjectionNames.OPTIONS_VIDEO_HW_ACCEL) |
| 153 | - fun videoHwAccel() = true | 119 | + videoHwAccel: Boolean, |
| 120 | + eglContext: EglBase.Context, | ||
| 121 | + ): VideoDecoderFactory { | ||
| 122 | + return if (videoHwAccel) { | ||
| 123 | + DefaultVideoDecoderFactory(eglContext) | ||
| 124 | + } else { | ||
| 125 | + SoftwareVideoDecoderFactory() | ||
| 126 | + } | ||
| 154 | } | 127 | } |
| 128 | + | ||
| 129 | + @Provides | ||
| 130 | + @Singleton | ||
| 131 | + fun peerConnectionFactory( | ||
| 132 | + appContext: Context, | ||
| 133 | + audioDeviceModule: AudioDeviceModule, | ||
| 134 | + videoEncoderFactory: VideoEncoderFactory, | ||
| 135 | + videoDecoderFactory: VideoDecoderFactory, | ||
| 136 | + ): PeerConnectionFactory { | ||
| 137 | + PeerConnectionFactory.initialize( | ||
| 138 | + PeerConnectionFactory.InitializationOptions | ||
| 139 | + .builder(appContext) | ||
| 140 | + .createInitializationOptions() | ||
| 141 | + ) | ||
| 142 | + | ||
| 143 | + return PeerConnectionFactory.builder() | ||
| 144 | + .setAudioDeviceModule(audioDeviceModule) | ||
| 145 | + .setVideoEncoderFactory(videoEncoderFactory) | ||
| 146 | + .setVideoDecoderFactory(videoDecoderFactory) | ||
| 147 | + .createPeerConnectionFactory() | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + @Provides | ||
| 151 | + @Named(InjectionNames.OPTIONS_VIDEO_HW_ACCEL) | ||
| 152 | + fun videoHwAccel() = true | ||
| 155 | } | 153 | } |
| @@ -7,17 +7,15 @@ import okhttp3.WebSocket | @@ -7,17 +7,15 @@ import okhttp3.WebSocket | ||
| 7 | import javax.inject.Singleton | 7 | import javax.inject.Singleton |
| 8 | 8 | ||
| 9 | @Module | 9 | @Module |
| 10 | -class WebModule { | ||
| 11 | - companion object { | ||
| 12 | - @Provides | ||
| 13 | - @Singleton | ||
| 14 | - fun okHttpClient(): OkHttpClient { | ||
| 15 | - return OkHttpClient() | ||
| 16 | - } | 10 | +object WebModule { |
| 11 | + @Provides | ||
| 12 | + @Singleton | ||
| 13 | + fun okHttpClient(): OkHttpClient { | ||
| 14 | + return OkHttpClient() | ||
| 15 | + } | ||
| 17 | 16 | ||
| 18 | - @Provides | ||
| 19 | - fun websocketFactory(okHttpClient: OkHttpClient): WebSocket.Factory { | ||
| 20 | - return okHttpClient | ||
| 21 | - } | 17 | + @Provides |
| 18 | + fun websocketFactory(okHttpClient: OkHttpClient): WebSocket.Factory { | ||
| 19 | + return okHttpClient | ||
| 22 | } | 20 | } |
| 23 | } | 21 | } |
| 1 | +package io.livekit.android.mock | ||
| 2 | + | ||
| 3 | +import org.webrtc.DataChannel | ||
| 4 | + | ||
| 5 | +class MockDataChannel(private val label: String?) : DataChannel(1L) { | ||
| 6 | + | ||
| 7 | + var observer: DataChannel.Observer? = null | ||
| 8 | + override fun registerObserver(observer: Observer?) { | ||
| 9 | + this.observer = observer | ||
| 10 | + } | ||
| 11 | + | ||
| 12 | + override fun unregisterObserver() { | ||
| 13 | + observer = null | ||
| 14 | + } | ||
| 15 | + | ||
| 16 | + override fun label(): String? { | ||
| 17 | + return label | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + override fun id(): Int { | ||
| 21 | + return 0 | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + override fun state(): State { | ||
| 25 | + return State.OPEN | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + override fun bufferedAmount(): Long { | ||
| 29 | + return 0 | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + override fun send(buffer: Buffer?): Boolean { | ||
| 33 | + return true | ||
| 34 | + } | ||
| 35 | +} |
| 1 | +package io.livekit.android.mock | ||
| 2 | + | ||
| 3 | +import org.webrtc.* | ||
| 4 | + | ||
| 5 | +private class MockNativePeerConnectionFactory : NativePeerConnectionFactory { | ||
| 6 | + override fun createNativePeerConnection(): Long = 0L | ||
| 7 | +} | ||
| 8 | + | ||
| 9 | +class MockPeerConnection( | ||
| 10 | + private val observer: PeerConnection.Observer? | ||
| 11 | +) : PeerConnection(MockNativePeerConnectionFactory()) { | ||
| 12 | + | ||
| 13 | + var localDesc: SessionDescription? = null | ||
| 14 | + var remoteDesc: SessionDescription? = null | ||
| 15 | + override fun getLocalDescription(): SessionDescription? = localDesc | ||
| 16 | + override fun setLocalDescription(observer: SdpObserver?, sdp: SessionDescription?) { | ||
| 17 | + localDesc = sdp | ||
| 18 | + observer?.onSetSuccess() | ||
| 19 | + } | ||
| 20 | + | ||
| 21 | + override fun getRemoteDescription(): SessionDescription? = remoteDesc | ||
| 22 | + override fun setRemoteDescription(observer: SdpObserver?, sdp: SessionDescription?) { | ||
| 23 | + remoteDesc = sdp | ||
| 24 | + observer?.onSetSuccess() | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + override fun getCertificate(): RtcCertificatePem? { | ||
| 28 | + return null | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + override fun createDataChannel(label: String?, init: DataChannel.Init?): DataChannel { | ||
| 32 | + return MockDataChannel(label) | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + override fun createOffer(observer: SdpObserver?, constraints: MediaConstraints?) { | ||
| 36 | + val sdp = SessionDescription(SessionDescription.Type.OFFER, "") | ||
| 37 | + observer?.onCreateSuccess(sdp) | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + override fun createAnswer(observer: SdpObserver?, constraints: MediaConstraints?) { | ||
| 41 | + val sdp = SessionDescription(SessionDescription.Type.ANSWER, "") | ||
| 42 | + observer?.onCreateSuccess(sdp) | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + | ||
| 46 | + override fun setAudioPlayout(playout: Boolean) { | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + override fun setAudioRecording(recording: Boolean) { | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + override fun setConfiguration(config: RTCConfiguration?): Boolean { | ||
| 53 | + return true | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + override fun addIceCandidate(candidate: IceCandidate?): Boolean { | ||
| 57 | + return true | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + override fun removeIceCandidates(candidates: Array<out IceCandidate>?): Boolean { | ||
| 61 | + return true | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + override fun addStream(stream: MediaStream?): Boolean { | ||
| 65 | + return super.addStream(stream) | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + override fun removeStream(stream: MediaStream?) { | ||
| 69 | + super.removeStream(stream) | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + override fun createSender(kind: String?, stream_id: String?): RtpSender { | ||
| 73 | + return super.createSender(kind, stream_id) | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + override fun getSenders(): MutableList<RtpSender> { | ||
| 77 | + return super.getSenders() | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + override fun getReceivers(): MutableList<RtpReceiver> { | ||
| 81 | + return super.getReceivers() | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + override fun getTransceivers(): MutableList<RtpTransceiver> { | ||
| 85 | + return super.getTransceivers() | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + override fun addTrack(track: MediaStreamTrack?): RtpSender { | ||
| 89 | + return super.addTrack(track) | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + override fun addTrack(track: MediaStreamTrack?, streamIds: MutableList<String>?): RtpSender { | ||
| 93 | + return super.addTrack(track, streamIds) | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + override fun removeTrack(sender: RtpSender?): Boolean { | ||
| 97 | + return super.removeTrack(sender) | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + override fun addTransceiver(track: MediaStreamTrack?): RtpTransceiver { | ||
| 101 | + return super.addTransceiver(track) | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + override fun addTransceiver( | ||
| 105 | + track: MediaStreamTrack?, | ||
| 106 | + init: RtpTransceiver.RtpTransceiverInit? | ||
| 107 | + ): RtpTransceiver { | ||
| 108 | + return super.addTransceiver(track, init) | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + override fun addTransceiver(mediaType: MediaStreamTrack.MediaType?): RtpTransceiver { | ||
| 112 | + return super.addTransceiver(mediaType) | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + override fun addTransceiver( | ||
| 116 | + mediaType: MediaStreamTrack.MediaType?, | ||
| 117 | + init: RtpTransceiver.RtpTransceiverInit? | ||
| 118 | + ): RtpTransceiver { | ||
| 119 | + return super.addTransceiver(mediaType, init) | ||
| 120 | + } | ||
| 121 | + | ||
| 122 | + override fun getStats(observer: StatsObserver?, track: MediaStreamTrack?): Boolean { | ||
| 123 | + observer?.onComplete(emptyArray()) | ||
| 124 | + return true | ||
| 125 | + } | ||
| 126 | + | ||
| 127 | + override fun getStats(callback: RTCStatsCollectorCallback?) { | ||
| 128 | + callback?.onStatsDelivered(RTCStatsReport(0, emptyMap())) | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + override fun setBitrate(min: Int?, current: Int?, max: Int?): Boolean { | ||
| 132 | + return true | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + override fun startRtcEventLog(file_descriptor: Int, max_size_bytes: Int): Boolean { | ||
| 136 | + return true | ||
| 137 | + } | ||
| 138 | + | ||
| 139 | + override fun stopRtcEventLog() { | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + override fun signalingState(): SignalingState { | ||
| 143 | + return super.signalingState() | ||
| 144 | + } | ||
| 145 | + | ||
| 146 | + override fun iceConnectionState(): IceConnectionState { | ||
| 147 | + return super.iceConnectionState() | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + override fun connectionState(): PeerConnectionState { | ||
| 151 | + return super.connectionState() | ||
| 152 | + } | ||
| 153 | + | ||
| 154 | + override fun iceGatheringState(): IceGatheringState { | ||
| 155 | + return super.iceGatheringState() | ||
| 156 | + } | ||
| 157 | + | ||
| 158 | + override fun close() { | ||
| 159 | + } | ||
| 160 | + | ||
| 161 | + override fun dispose() { | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + override fun getNativePeerConnection(): Long = 0L | ||
| 165 | +} |
| 1 | +package io.livekit.android.mock | ||
| 2 | + | ||
| 3 | +import io.livekit.android.room.PeerConnectionTransport | ||
| 4 | +import kotlinx.coroutines.CoroutineDispatcher | ||
| 5 | +import org.webrtc.PeerConnection | ||
| 6 | +import org.webrtc.PeerConnectionFactory | ||
| 7 | + | ||
| 8 | +internal class MockPeerConnectionTransportFactory( | ||
| 9 | + private val dispatcher: CoroutineDispatcher, | ||
| 10 | +) : PeerConnectionTransport.Factory { | ||
| 11 | + override fun create( | ||
| 12 | + config: PeerConnection.RTCConfiguration, | ||
| 13 | + pcObserver: PeerConnection.Observer, | ||
| 14 | + listener: PeerConnectionTransport.Listener? | ||
| 15 | + ): PeerConnectionTransport { | ||
| 16 | + return PeerConnectionTransport( | ||
| 17 | + config, | ||
| 18 | + pcObserver, | ||
| 19 | + listener, | ||
| 20 | + dispatcher, | ||
| 21 | + PeerConnectionFactory.builder() | ||
| 22 | + .createPeerConnectionFactory() | ||
| 23 | + ) | ||
| 24 | + } | ||
| 25 | +} |
| 1 | +package io.livekit.android.mock | ||
| 2 | + | ||
| 3 | +import okhttp3.Request | ||
| 4 | +import okhttp3.WebSocket | ||
| 5 | +import okhttp3.WebSocketListener | ||
| 6 | +import org.mockito.Mockito | ||
| 7 | + | ||
| 8 | +class MockWebsocketFactory : WebSocket.Factory { | ||
| 9 | + lateinit var ws: WebSocket | ||
| 10 | + lateinit var request: Request | ||
| 11 | + lateinit var listener: WebSocketListener | ||
| 12 | + override fun newWebSocket(request: Request, listener: WebSocketListener): WebSocket { | ||
| 13 | + this.ws = Mockito.mock(WebSocket::class.java) | ||
| 14 | + this.listener = listener | ||
| 15 | + this.request = request | ||
| 16 | + return ws | ||
| 17 | + } | ||
| 18 | +} |
| 1 | +package io.livekit.android.mock.dagger | ||
| 2 | + | ||
| 3 | +import dagger.Module | ||
| 4 | +import dagger.Provides | ||
| 5 | +import io.livekit.android.dagger.InjectionNames | ||
| 6 | +import kotlinx.coroutines.CoroutineDispatcher | ||
| 7 | +import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
| 8 | +import kotlinx.coroutines.test.TestCoroutineDispatcher | ||
| 9 | +import javax.inject.Named | ||
| 10 | + | ||
| 11 | +@Module | ||
| 12 | +object TestCoroutinesModule { | ||
| 13 | + | ||
| 14 | + @OptIn(ExperimentalCoroutinesApi::class) | ||
| 15 | + val coroutineDispatcher: CoroutineDispatcher = TestCoroutineDispatcher() | ||
| 16 | + | ||
| 17 | + @Provides | ||
| 18 | + @Named(InjectionNames.DISPATCHER_DEFAULT) | ||
| 19 | + fun defaultDispatcher() = coroutineDispatcher | ||
| 20 | + | ||
| 21 | + @Provides | ||
| 22 | + @Named(InjectionNames.DISPATCHER_IO) | ||
| 23 | + fun ioDispatcher() = coroutineDispatcher | ||
| 24 | + | ||
| 25 | + @Provides | ||
| 26 | + @Named(InjectionNames.DISPATCHER_MAIN) | ||
| 27 | + fun mainDispatcher() = coroutineDispatcher | ||
| 28 | + | ||
| 29 | + @Provides | ||
| 30 | + @Named(InjectionNames.DISPATCHER_UNCONFINED) | ||
| 31 | + fun unconfinedDispatcher() = coroutineDispatcher | ||
| 32 | +} |
| 1 | +package io.livekit.android.mock.dagger | ||
| 2 | + | ||
| 3 | +import android.content.Context | ||
| 4 | +import dagger.BindsInstance | ||
| 5 | +import dagger.Component | ||
| 6 | +import io.livekit.android.dagger.JsonFormatModule | ||
| 7 | +import io.livekit.android.dagger.LiveKitComponent | ||
| 8 | +import io.livekit.android.mock.MockWebsocketFactory | ||
| 9 | +import javax.inject.Singleton | ||
| 10 | + | ||
| 11 | +@Singleton | ||
| 12 | +@Component( | ||
| 13 | + modules = [ | ||
| 14 | + TestCoroutinesModule::class, | ||
| 15 | + TestRTCModule::class, | ||
| 16 | + TestWebModule::class, | ||
| 17 | + JsonFormatModule::class, | ||
| 18 | + ] | ||
| 19 | +) | ||
| 20 | +interface TestLiveKitComponent : LiveKitComponent { | ||
| 21 | + | ||
| 22 | + fun websocketFactory(): MockWebsocketFactory | ||
| 23 | + | ||
| 24 | + @Component.Factory | ||
| 25 | + interface Factory { | ||
| 26 | + fun create(@BindsInstance appContext: Context): TestLiveKitComponent | ||
| 27 | + } | ||
| 28 | +} |
| 1 | +package io.livekit.android.mock.dagger | ||
| 2 | + | ||
| 3 | +import android.content.Context | ||
| 4 | +import dagger.Module | ||
| 5 | +import dagger.Provides | ||
| 6 | +import io.livekit.android.dagger.InjectionNames | ||
| 7 | +import io.livekit.android.mock.MockEglBase | ||
| 8 | +import org.webrtc.* | ||
| 9 | +import javax.inject.Named | ||
| 10 | +import javax.inject.Singleton | ||
| 11 | + | ||
| 12 | + | ||
| 13 | +@Module | ||
| 14 | +object TestRTCModule { | ||
| 15 | + | ||
| 16 | + @Provides | ||
| 17 | + @Singleton | ||
| 18 | + fun eglBase(): EglBase { | ||
| 19 | + return MockEglBase() | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + @Provides | ||
| 23 | + fun eglContext(eglBase: EglBase): EglBase.Context = eglBase.eglBaseContext | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + @Provides | ||
| 27 | + @Singleton | ||
| 28 | + fun peerConnectionFactory( | ||
| 29 | + appContext: Context | ||
| 30 | + ): PeerConnectionFactory { | ||
| 31 | + try { | ||
| 32 | + ContextUtils.initialize(appContext) | ||
| 33 | + NativeLibraryLoaderTestHelper.initialize() | ||
| 34 | + } catch (e: Throwable) { | ||
| 35 | + // do nothing. this is expected. | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + return MockPeerConnectionFactory() | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + @Provides | ||
| 42 | + @Named(InjectionNames.OPTIONS_VIDEO_HW_ACCEL) | ||
| 43 | + fun videoHwAccel() = true | ||
| 44 | +} |
| 1 | +package io.livekit.android.mock.dagger | ||
| 2 | + | ||
| 3 | +import dagger.Module | ||
| 4 | +import dagger.Provides | ||
| 5 | +import io.livekit.android.mock.MockWebsocketFactory | ||
| 6 | +import okhttp3.OkHttpClient | ||
| 7 | +import okhttp3.Response | ||
| 8 | +import okhttp3.WebSocket | ||
| 9 | +import javax.inject.Singleton | ||
| 10 | + | ||
| 11 | +@Module | ||
| 12 | +object TestWebModule { | ||
| 13 | + | ||
| 14 | + @Provides | ||
| 15 | + @Singleton | ||
| 16 | + fun okHttpClient(): OkHttpClient { | ||
| 17 | + return OkHttpClient.Builder() | ||
| 18 | + .addInterceptor { | ||
| 19 | + // Don't make actual network calls | ||
| 20 | + Response.Builder() | ||
| 21 | + .code(200) | ||
| 22 | + .build() | ||
| 23 | + } | ||
| 24 | + .build() | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + @Provides | ||
| 28 | + @Singleton | ||
| 29 | + fun websocketFactory(websocketFactory: MockWebsocketFactory): WebSocket.Factory { | ||
| 30 | + return websocketFactory | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + @Provides | ||
| 34 | + @Singleton | ||
| 35 | + fun mockWebsocketFactory(): MockWebsocketFactory { | ||
| 36 | + return MockWebsocketFactory() | ||
| 37 | + } | ||
| 38 | +} |
| 1 | +package io.livekit.android.room | ||
| 2 | + | ||
| 3 | +import android.content.Context | ||
| 4 | +import androidx.test.core.app.ApplicationProvider | ||
| 5 | +import io.livekit.android.mock.MockWebsocketFactory | ||
| 6 | +import io.livekit.android.mock.dagger.DaggerTestLiveKitComponent | ||
| 7 | +import io.livekit.android.util.toOkioByteString | ||
| 8 | +import kotlinx.coroutines.CoroutineExceptionHandler | ||
| 9 | +import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
| 10 | +import kotlinx.coroutines.launch | ||
| 11 | +import kotlinx.coroutines.test.TestCoroutineScope | ||
| 12 | +import kotlinx.coroutines.test.runBlockingTest | ||
| 13 | +import org.junit.Assert | ||
| 14 | +import org.junit.Before | ||
| 15 | +import org.junit.Rule | ||
| 16 | +import org.junit.Test | ||
| 17 | +import org.junit.runner.RunWith | ||
| 18 | +import org.mockito.junit.MockitoJUnit | ||
| 19 | +import org.robolectric.RobolectricTestRunner | ||
| 20 | + | ||
| 21 | +@ExperimentalCoroutinesApi | ||
| 22 | +@RunWith(RobolectricTestRunner::class) | ||
| 23 | +class RoomMockE2ETest { | ||
| 24 | + | ||
| 25 | + @get:Rule | ||
| 26 | + var mockitoRule = MockitoJUnit.rule() | ||
| 27 | + | ||
| 28 | + lateinit var context: Context | ||
| 29 | + lateinit var room: Room | ||
| 30 | + lateinit var wsFactory: MockWebsocketFactory | ||
| 31 | + | ||
| 32 | + @Before | ||
| 33 | + fun setup() { | ||
| 34 | + context = ApplicationProvider.getApplicationContext() | ||
| 35 | + val component = DaggerTestLiveKitComponent | ||
| 36 | + .factory() | ||
| 37 | + .create(context) | ||
| 38 | + | ||
| 39 | + room = component.roomFactory() | ||
| 40 | + .create(context) | ||
| 41 | + wsFactory = component.websocketFactory() | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + @Test | ||
| 45 | + fun connectTest() { | ||
| 46 | + val job = TestCoroutineScope().launch { | ||
| 47 | + room.connect( | ||
| 48 | + url = "http://www.example.com", | ||
| 49 | + token = "", | ||
| 50 | + options = null | ||
| 51 | + ) | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + wsFactory.listener.onMessage(wsFactory.ws, SignalClientTest.JOIN.toOkioByteString()) | ||
| 55 | + | ||
| 56 | + runBlockingTest { | ||
| 57 | + job.join() | ||
| 58 | + } | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + @Test | ||
| 62 | + fun roomUpdateTest() { | ||
| 63 | + val handler = CoroutineExceptionHandler { _, exception -> | ||
| 64 | + println("CoroutineExceptionHandler got $exception") | ||
| 65 | + exception.printStackTrace() | ||
| 66 | + } | ||
| 67 | + val job = TestCoroutineScope().launch(handler) { | ||
| 68 | + room.connect( | ||
| 69 | + url = "http://www.example.com", | ||
| 70 | + token = "", | ||
| 71 | + options = null | ||
| 72 | + ) | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + wsFactory.listener.onMessage(wsFactory.ws, SignalClientTest.JOIN.toOkioByteString()) | ||
| 76 | + | ||
| 77 | + runBlockingTest { | ||
| 78 | + job.join() | ||
| 79 | + } | ||
| 80 | + wsFactory.listener.onMessage(wsFactory.ws, SignalClientTest.ROOM_UPDATE.toOkioByteString()) | ||
| 81 | + | ||
| 82 | + Assert.assertEquals( | ||
| 83 | + SignalClientTest.ROOM_UPDATE.roomUpdate.room.metadata, | ||
| 84 | + room.metadata | ||
| 85 | + ) | ||
| 86 | + } | ||
| 87 | +} |
| @@ -2,7 +2,7 @@ package io.livekit.android.room | @@ -2,7 +2,7 @@ package io.livekit.android.room | ||
| 2 | 2 | ||
| 3 | import android.content.Context | 3 | import android.content.Context |
| 4 | import androidx.test.core.app.ApplicationProvider | 4 | import androidx.test.core.app.ApplicationProvider |
| 5 | -import io.livekit.android.room.mock.MockEglBase | 5 | +import io.livekit.android.mock.MockEglBase |
| 6 | import io.livekit.android.room.participant.LocalParticipant | 6 | import io.livekit.android.room.participant.LocalParticipant |
| 7 | import kotlinx.coroutines.ExperimentalCoroutinesApi | 7 | import kotlinx.coroutines.ExperimentalCoroutinesApi |
| 8 | import kotlinx.coroutines.launch | 8 | import kotlinx.coroutines.launch |
| @@ -18,7 +18,6 @@ import org.mockito.Mockito | @@ -18,7 +18,6 @@ import org.mockito.Mockito | ||
| 18 | import org.mockito.junit.MockitoJUnit | 18 | import org.mockito.junit.MockitoJUnit |
| 19 | import org.robolectric.RobolectricTestRunner | 19 | import org.robolectric.RobolectricTestRunner |
| 20 | import org.webrtc.EglBase | 20 | import org.webrtc.EglBase |
| 21 | -import org.webrtc.PeerConnectionFactory | ||
| 22 | 21 | ||
| 23 | @ExperimentalCoroutinesApi | 22 | @ExperimentalCoroutinesApi |
| 24 | @RunWith(RobolectricTestRunner::class) | 23 | @RunWith(RobolectricTestRunner::class) |
| 1 | package io.livekit.android.room | 1 | package io.livekit.android.room |
| 2 | 2 | ||
| 3 | import com.google.protobuf.util.JsonFormat | 3 | import com.google.protobuf.util.JsonFormat |
| 4 | +import io.livekit.android.mock.MockWebsocketFactory | ||
| 4 | import io.livekit.android.util.toOkioByteString | 5 | import io.livekit.android.util.toOkioByteString |
| 5 | import kotlinx.coroutines.ExperimentalCoroutinesApi | 6 | import kotlinx.coroutines.ExperimentalCoroutinesApi |
| 6 | import kotlinx.coroutines.async | 7 | import kotlinx.coroutines.async |
| @@ -29,16 +30,6 @@ class SignalClientTest { | @@ -29,16 +30,6 @@ class SignalClientTest { | ||
| 29 | lateinit var coroutineDispatcher: TestCoroutineDispatcher | 30 | lateinit var coroutineDispatcher: TestCoroutineDispatcher |
| 30 | lateinit var coroutineScope: TestCoroutineScope | 31 | lateinit var coroutineScope: TestCoroutineScope |
| 31 | 32 | ||
| 32 | - class MockWebsocketFactory : WebSocket.Factory { | ||
| 33 | - lateinit var ws: WebSocket | ||
| 34 | - lateinit var request: Request | ||
| 35 | - override fun newWebSocket(request: Request, listener: WebSocketListener): WebSocket { | ||
| 36 | - ws = Mockito.mock(WebSocket::class.java) | ||
| 37 | - this.request = request | ||
| 38 | - return ws | ||
| 39 | - } | ||
| 40 | - } | ||
| 41 | - | ||
| 42 | @Before | 33 | @Before |
| 43 | fun setup() { | 34 | fun setup() { |
| 44 | coroutineDispatcher = TestCoroutineDispatcher() | 35 | coroutineDispatcher = TestCoroutineDispatcher() |
| @@ -139,7 +130,7 @@ class SignalClientTest { | @@ -139,7 +130,7 @@ class SignalClientTest { | ||
| 139 | companion object { | 130 | companion object { |
| 140 | private const val EXAMPLE_URL = "http://www.example.com" | 131 | private const val EXAMPLE_URL = "http://www.example.com" |
| 141 | 132 | ||
| 142 | - private val JOIN = with(LivekitRtc.SignalResponse.newBuilder()) { | 133 | + val JOIN = with(LivekitRtc.SignalResponse.newBuilder()) { |
| 143 | join = with(joinBuilder) { | 134 | join = with(joinBuilder) { |
| 144 | room = with(roomBuilder) { | 135 | room = with(roomBuilder) { |
| 145 | name = "roomname" | 136 | name = "roomname" |
| @@ -150,7 +141,8 @@ class SignalClientTest { | @@ -150,7 +141,8 @@ class SignalClientTest { | ||
| 150 | } | 141 | } |
| 151 | build() | 142 | build() |
| 152 | } | 143 | } |
| 153 | - private val OFFER = with(LivekitRtc.SignalResponse.newBuilder()) { | 144 | + |
| 145 | + val OFFER = with(LivekitRtc.SignalResponse.newBuilder()) { | ||
| 154 | offer = with(offerBuilder) { | 146 | offer = with(offerBuilder) { |
| 155 | sdp = "" | 147 | sdp = "" |
| 156 | type = "offer" | 148 | type = "offer" |
| @@ -158,5 +150,16 @@ class SignalClientTest { | @@ -158,5 +150,16 @@ class SignalClientTest { | ||
| 158 | } | 150 | } |
| 159 | build() | 151 | build() |
| 160 | } | 152 | } |
| 153 | + | ||
| 154 | + val ROOM_UPDATE = with(LivekitRtc.SignalResponse.newBuilder()) { | ||
| 155 | + roomUpdate = with(roomUpdateBuilder) { | ||
| 156 | + room = with(roomBuilder) { | ||
| 157 | + metadata = "metadata" | ||
| 158 | + build() | ||
| 159 | + } | ||
| 160 | + build() | ||
| 161 | + } | ||
| 162 | + build() | ||
| 163 | + } | ||
| 161 | } | 164 | } |
| 162 | } | 165 | } |
| 1 | +package org.webrtc | ||
| 2 | + | ||
| 3 | +import io.livekit.android.mock.MockPeerConnection | ||
| 4 | + | ||
| 5 | +class MockPeerConnectionFactory : PeerConnectionFactory(1L) { | ||
| 6 | + override fun createPeerConnectionInternal( | ||
| 7 | + rtcConfig: PeerConnection.RTCConfiguration?, | ||
| 8 | + constraints: MediaConstraints?, | ||
| 9 | + observer: PeerConnection.Observer?, | ||
| 10 | + sslCertificateVerifier: SSLCertificateVerifier? | ||
| 11 | + ): PeerConnection { | ||
| 12 | + return MockPeerConnection(observer) | ||
| 13 | + } | ||
| 14 | +} |
-
请 注册 或 登录 后发表评论