diff --git a/core/src/main/java/io/github/thibaultbee/streampack/streamers/interfaces/ILiveStreamer.kt b/core/src/main/java/io/github/thibaultbee/streampack/streamers/interfaces/ILiveStreamer.kt index f56307811..e12d681b9 100644 --- a/core/src/main/java/io/github/thibaultbee/streampack/streamers/interfaces/ILiveStreamer.kt +++ b/core/src/main/java/io/github/thibaultbee/streampack/streamers/interfaces/ILiveStreamer.kt @@ -29,7 +29,7 @@ interface ILiveStreamer { val isConnected: Boolean /** - * Connect to an remove server. + * Connect to a remote server. * To avoid creating an unresponsive UI, do not call on main thread. * * @param url server url @@ -53,4 +53,4 @@ interface ILiveStreamer { * @throws Exception if connection has failed or configuration has failed or [startStream] has failed too. */ suspend fun startStream(url: String) -} \ No newline at end of file +} diff --git a/demos/camera/src/main/java/io/github/thibaultbee/streampack/app/utils/StreamerManager.kt b/demos/camera/src/main/java/io/github/thibaultbee/streampack/app/utils/StreamerManager.kt index 49678b864..f4fcef208 100644 --- a/demos/camera/src/main/java/io/github/thibaultbee/streampack/app/utils/StreamerManager.kt +++ b/demos/camera/src/main/java/io/github/thibaultbee/streampack/app/utils/StreamerManager.kt @@ -20,13 +20,22 @@ import android.content.Context import android.os.Build import androidx.annotation.RequiresPermission import io.github.thibaultbee.streampack.app.configuration.Configuration +import io.github.thibaultbee.streampack.ext.srt.data.SrtConnection import io.github.thibaultbee.streampack.ext.srt.streamers.interfaces.ISrtLiveStreamer import io.github.thibaultbee.streampack.listeners.OnConnectionListener import io.github.thibaultbee.streampack.listeners.OnErrorListener import io.github.thibaultbee.streampack.streamers.StreamerLifeCycleObserver import io.github.thibaultbee.streampack.streamers.interfaces.IStreamer import io.github.thibaultbee.streampack.streamers.interfaces.settings.IBaseCameraStreamerSettings -import io.github.thibaultbee.streampack.utils.* +import io.github.thibaultbee.streampack.utils.CameraSettings +import io.github.thibaultbee.streampack.utils.ChunkedFileOutputStream +import io.github.thibaultbee.streampack.utils.backCameraList +import io.github.thibaultbee.streampack.utils.frontCameraList +import io.github.thibaultbee.streampack.utils.getCameraStreamer +import io.github.thibaultbee.streampack.utils.getFileStreamer +import io.github.thibaultbee.streampack.utils.getLiveStreamer +import io.github.thibaultbee.streampack.utils.getStreamer +import io.github.thibaultbee.streampack.utils.isBackCamera import io.github.thibaultbee.streampack.views.PreviewView import kotlinx.coroutines.runBlocking import java.io.File @@ -87,13 +96,14 @@ class StreamerManager( suspend fun startStream() { if (streamer?.getLiveStreamer() != null) { getSrtLiveStreamer()?.let { - it.streamId = - configuration.endpoint.srt.streamID - it.passPhrase = + val connection = SrtConnection( + configuration.endpoint.srt.ip, + configuration.endpoint.srt.port, + configuration.endpoint.srt.streamID, configuration.endpoint.srt.passPhrase + ) it.connect( - configuration.endpoint.srt.ip, - configuration.endpoint.srt.port + connection ) } ?: streamer?.getLiveStreamer()?.connect( configuration.endpoint.rtmp.url diff --git a/demos/screenrecorder/src/main/java/io/github/thibaultbee/streampack/screenrecorder/MainActivity.kt b/demos/screenrecorder/src/main/java/io/github/thibaultbee/streampack/screenrecorder/MainActivity.kt index 568237f5e..675fbf403 100644 --- a/demos/screenrecorder/src/main/java/io/github/thibaultbee/streampack/screenrecorder/MainActivity.kt +++ b/demos/screenrecorder/src/main/java/io/github/thibaultbee/streampack/screenrecorder/MainActivity.kt @@ -36,6 +36,7 @@ import io.github.thibaultbee.streampack.data.AudioConfig import io.github.thibaultbee.streampack.data.BitrateRegulatorConfig import io.github.thibaultbee.streampack.data.VideoConfig import io.github.thibaultbee.streampack.ext.rtmp.services.ScreenRecorderRtmpLiveService +import io.github.thibaultbee.streampack.ext.srt.data.SrtConnection import io.github.thibaultbee.streampack.ext.srt.services.ScreenRecorderSrtLiveService import io.github.thibaultbee.streampack.ext.srt.streamers.interfaces.ISrtLiveStreamer import io.github.thibaultbee.streampack.internal.encoders.MediaCodecHelper @@ -214,14 +215,13 @@ class MainActivity : AppCompatActivity() { runBlocking { streamer.getStreamer()?.let { - it.streamId = - configuration.endpoint.srt.streamID - it.passPhrase = - configuration.endpoint.srt.passPhrase - it.connect( + val connection = SrtConnection( configuration.endpoint.srt.ip, - configuration.endpoint.srt.port + configuration.endpoint.srt.port, + configuration.endpoint.srt.streamID, + configuration.endpoint.srt.passPhrase ) + it.connect(connection) } ?: streamer.getStreamer()?.connect( configuration.endpoint.rtmp.url ) diff --git a/extensions/rtmp/build.gradle b/extensions/rtmp/build.gradle index 998f78d2b..91281f760 100644 --- a/extensions/rtmp/build.gradle +++ b/extensions/rtmp/build.gradle @@ -16,4 +16,7 @@ dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' implementation "androidx.core:core-ktx:${androidxCoreVersion}" + + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' } \ No newline at end of file diff --git a/extensions/rtmp/src/androidTest/java/io/github/thibaultbee/streampack/ext/rtmp/data/RtmpConnectionTest.kt b/extensions/rtmp/src/androidTest/java/io/github/thibaultbee/streampack/ext/rtmp/data/RtmpConnectionTest.kt new file mode 100644 index 000000000..5c7c2f02d --- /dev/null +++ b/extensions/rtmp/src/androidTest/java/io/github/thibaultbee/streampack/ext/rtmp/data/RtmpConnectionTest.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 Thibault B. + * + * 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 io.github.thibaultbee.streampack.ext.rtmp.data + +import org.junit.Assert +import org.junit.Assert.assertEquals +import org.junit.Test + +class RtmpConnectionTest { + @Test + fun fromUrl() { + val url = "rtmp://broadcast.host.com:1234/app/streamKey" + val connection = RtmpConnection.fromUrl(url) + assertEquals("rtmp", connection.scheme) + assertEquals("broadcast.host.com", connection.host) + assertEquals(1234, connection.port) + assertEquals("app", connection.app) + assertEquals("streamKey", connection.streamKey) + } + + @Test + fun fromRtmpsUrl() { + val url = "rtmps://broadcast.host.com:1234/app/streamKey" + val connection = RtmpConnection.fromUrl(url) + assertEquals("rtmps", connection.scheme) + assertEquals("broadcast.host.com", connection.host) + assertEquals(1234, connection.port) + assertEquals("app", connection.app) + assertEquals("streamKey", connection.streamKey) + } + + @Test + fun fromUrlWithDefaultPort() { + val url = "rtmp://broadcast.host.com/app/streamKey" + val connection = RtmpConnection.fromUrl(url) + assertEquals("rtmp", connection.scheme) + assertEquals("broadcast.host.com", connection.host) + assertEquals(1935, connection.port) + assertEquals("app", connection.app) + assertEquals("streamKey", connection.streamKey) + } + + @Test + fun fromRtmpsUrlWithDefaultPort() { + val url = "rtmps://broadcast.host.com/app/streamKey" + val connection = RtmpConnection.fromUrl(url) + assertEquals("rtmps", connection.scheme) + assertEquals("broadcast.host.com", connection.host) + assertEquals(443, connection.port) + assertEquals("app", connection.app) + assertEquals("streamKey", connection.streamKey) + } + + @Test + fun fromUrlWithBadScheme() { + val url = "rtp://broadcast.host.com:1234/app/streamKey" + try { + RtmpConnection.fromUrl(url) + Assert.fail("Should throw an exception") + } catch (_: Exception) { + } + } +} \ No newline at end of file diff --git a/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/data/RtmpConnection.kt b/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/data/RtmpConnection.kt new file mode 100644 index 000000000..0ad18e416 --- /dev/null +++ b/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/data/RtmpConnection.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2023 Thibault B. + * + * 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 io.github.thibaultbee.streampack.ext.rtmp.data + +import android.net.Uri +import java.security.InvalidParameterException + +/** + * RTMP connection parameters + * + * @param scheme the RTMP scheme (rtmp, rtmps, rtmpt, rtmpe, rtmfp, rtmpte, rtmpts) + * @param host the server ip + * @param port the server port + * @param app the application name + * @param streamKey the stream key + */ +data class RtmpConnection( + val scheme: String, val host: String, val port: Int, val app: String, val streamKey: String +) { + val uri = Uri.Builder() + .scheme(scheme) + .encodedAuthority("$host:$port") + .appendEncodedPath(app) + .appendEncodedPath(streamKey) + .build() + + val url = uri.toString() + + init { + require(scheme == RTMP_SCHEME || scheme == RTMPS_SCHEME || scheme == RTMPT_SCHEME || scheme == RTMPE_SCHEME || scheme == RTMFP_SCHEME || scheme == RTMPTE_SCHEME || scheme == RTMPTS_SCHEME) { "Invalid scheme $scheme" } + require(host.isNotBlank()) { "Invalid host $host" } + require(port > 0) { "Invalid port $port" } + require(port < 65536) { "Invalid port $port" } + require(app.isNotBlank()) { "Invalid app $app" } + require(streamKey.isNotBlank()) { "Invalid streamKey $streamKey" } + } + + companion object { + private const val RTMP_SCHEME = "rtmp" + private const val RTMPS_SCHEME = "rtmps" + private const val RTMPT_SCHEME = "rtmpt" + private const val RTMPE_SCHEME = "rtmpe" + private const val RTMFP_SCHEME = "rtmfp" + private const val RTMPTE_SCHEME = "rtmpte" + private const val RTMPTS_SCHEME = "rtmpts" + + private const val DEFAULT_PORT = 1935 + private const val SSL_DEFAULT_PORT = 443 + private const val HTTP_DEFAULT_PORT = 80 + + /** + * Creates a RTMP connection from an URL + * + * @param url the server url (syntax: rtmp://host:port/app/streamKey) + * @return RTMP connection + */ + fun fromUrl(url: String): RtmpConnection { + val uri = Uri.parse(url) + + val scheme = + uri.scheme ?: throw InvalidParameterException("Invalid scheme ${uri.scheme}") + val host = uri.host ?: throw InvalidParameterException("Invalid host ${uri.host}") + val port = if (uri.port > 0) { + uri.port + } else { + if ((scheme == RTMPS_SCHEME) || (scheme == RTMPTS_SCHEME)) { + SSL_DEFAULT_PORT + } else if ((scheme == RTMPT_SCHEME) || (scheme == RTMPTE_SCHEME)) { + HTTP_DEFAULT_PORT + } else { + DEFAULT_PORT + } + } + if (uri.pathSegments.size < 2) { + throw InvalidParameterException("Invalid path ${uri.path} expected /app/streamKey") + } + val app = uri.pathSegments.minus(uri.lastPathSegment).joinToString("/") + val streamKey = uri.lastPathSegment + ?: throw InvalidParameterException("Invalid streamKey ${uri.lastPathSegment}") + return RtmpConnection(scheme, host, port, app, streamKey) + } + } +} \ No newline at end of file diff --git a/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/internal/endpoints/RtmpProducer.kt b/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/internal/endpoints/RtmpProducer.kt index 25409f074..8ca416d32 100644 --- a/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/internal/endpoints/RtmpProducer.kt +++ b/extensions/rtmp/src/main/java/io/github/thibaultbee/streampack/ext/rtmp/internal/endpoints/RtmpProducer.kt @@ -15,6 +15,7 @@ */ package io.github.thibaultbee.streampack.ext.rtmp.internal.endpoints +import io.github.thibaultbee.streampack.ext.rtmp.data.RtmpConnection import io.github.thibaultbee.streampack.internal.data.Packet import io.github.thibaultbee.streampack.internal.endpoints.ILiveEndpoint import io.github.thibaultbee.streampack.listeners.OnConnectionListener @@ -23,12 +24,10 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import video.api.rtmpdroid.Rtmp -import java.security.InvalidParameterException class RtmpProducer( private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO -) : - ILiveEndpoint { +) : ILiveEndpoint { override var onConnectionListener: OnConnectionListener? = null private var socket = Rtmp() @@ -51,16 +50,8 @@ class RtmpProducer( } override suspend fun connect(url: String) { - if (!url.startsWith(RTMP_PREFIX) && - !url.startsWith(RTMPS_PREFIX) && - !url.startsWith(RTMPT_PREFIX) && - !url.startsWith(RTMPE_PREFIX) && - !url.startsWith(RTMFP_PREFIX) && - !url.startsWith(RTMPTE_PREFIX) && - !url.startsWith(RTMPTS_PREFIX) - ) { - throw InvalidParameterException("URL must start with $RTMP_PREFIX, $RTMPS_PREFIX, $RTMPT_PREFIX, $RTMPE_PREFIX, $RTMFP_PREFIX, $RTMPTE_PREFIX or $RTMPTS_PREFIX") - } + RtmpConnection.fromUrl(url) // URL validation + withContext(coroutineDispatcher) { try { isOnError = false @@ -134,26 +125,5 @@ class RtmpProducer( companion object { private const val TAG = "RtmpProducer" - - private const val RTMP_SCHEME = "rtmp" - private const val RTMP_PREFIX = "$RTMP_SCHEME://" - - private const val RTMPS_SCHEME = "rtmps" - private const val RTMPS_PREFIX = "$RTMPS_SCHEME://" - - private const val RTMPT_SCHEME = "rtmpt" - private const val RTMPT_PREFIX = "$RTMPT_SCHEME://" - - private const val RTMPE_SCHEME = "rtmpe" - private const val RTMPE_PREFIX = "$RTMPE_SCHEME://" - - private const val RTMFP_SCHEME = "rtmfp" - private const val RTMFP_PREFIX = "$RTMFP_SCHEME://" - - private const val RTMPTE_SCHEME = "rtmpte" - private const val RTMPTE_PREFIX = "$RTMPTE_SCHEME://" - - private const val RTMPTS_SCHEME = "rtmpts" - private const val RTMPTS_PREFIX = "$RTMPTS_SCHEME://" } -} \ No newline at end of file +} diff --git a/extensions/srt/build.gradle b/extensions/srt/build.gradle index 2bec8e54d..0d84a9c23 100644 --- a/extensions/srt/build.gradle +++ b/extensions/srt/build.gradle @@ -16,4 +16,7 @@ dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' implementation "androidx.core:core-ktx:${androidxCoreVersion}" + + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' } \ No newline at end of file diff --git a/extensions/srt/src/androidTest/java/io/github/thibaultbee/streampack/ext/srt/data/SrtConnectionTest.kt b/extensions/srt/src/androidTest/java/io/github/thibaultbee/streampack/ext/srt/data/SrtConnectionTest.kt new file mode 100644 index 000000000..5a04e1c2d --- /dev/null +++ b/extensions/srt/src/androidTest/java/io/github/thibaultbee/streampack/ext/srt/data/SrtConnectionTest.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 Thibault B. + * + * 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 io.github.thibaultbee.streampack.ext.srt.data + +import org.junit.Assert.assertEquals +import org.junit.Assert.fail +import org.junit.Test + +class SrtConnectionTest { + @Test + fun fromUrl() { + val url = "srt://broadcast.host.com:1234" + val connection = SrtConnection.fromUrl(url) + assertEquals("broadcast.host.com", connection.host) + assertEquals(1234, connection.port) + } + + @Test + fun fromIp() { + val url = "srt://192.168.1.12:1234" + val connection = SrtConnection.fromUrl(url) + assertEquals("192.168.1.12", connection.host) + assertEquals(1234, connection.port) + } + + @Test + fun fromUrlWithParameters() { + val url = "srt://host.com:1234?streamid=streamId&passphrase=passPhrase" + val connection = SrtConnection.fromUrl(url) + assertEquals("host.com", connection.host) + assertEquals(1234, connection.port) + assertEquals("streamId", connection.streamId) + assertEquals("passPhrase", connection.passPhrase) + } + + @Test + fun fromUrlWithBadScheme() { + val url = "srtp://broadcast.host.com:1234" + try { + SrtConnection.fromUrl(url) + fail("Should throw an exception") + } catch (_: Exception) { + } + } + + @Test + fun fromUrlWithUnknownParam() { + val url = "srt://host.com:1234?streamid=streamId&passphrase=passPhrase&unknown=unknown" + try { + SrtConnection.fromUrl(url) + } catch (e: Exception) { + assertEquals("Failed to parse URL $url: unknown parameter(s): unknown", e.message) + } + } +} \ No newline at end of file diff --git a/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/data/SrtConnection.kt b/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/data/SrtConnection.kt new file mode 100644 index 000000000..5ed0968e5 --- /dev/null +++ b/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/data/SrtConnection.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2023 Thibault B. + * + * 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 io.github.thibaultbee.streampack.ext.srt.data + +import android.net.Uri +import java.security.InvalidParameterException + + +/** + * SRT connection parameters + * + * @param host server ip + * @param port server port + * @param streamId SRT stream ID + * @param passPhrase SRT passPhrase + */ +data class SrtConnection( + val host: String, + val port: Int, + val streamId: String? = null, + val passPhrase: String? = null +) { + init { + require(host.isNotBlank()) { "Invalid host $host" } + require( + host.startsWith(SRT_PREFIX).not() + ) { "Invalid host $host: must not start with prefix srt://" } + require(port > 0) { "Invalid port $port" } + require(port < 65536) { "Invalid port $port" } + } + + companion object { + private const val SRT_SCHEME = "srt" + private const val SRT_PREFIX = "$SRT_SCHEME://" + + private const val STREAM_ID_QUERY_PARAMETER = "streamid" + private const val PASS_PHRASE_QUERY_PARAMETER = "passphrase" + + private val queryParameterList = listOf( + STREAM_ID_QUERY_PARAMETER, + PASS_PHRASE_QUERY_PARAMETER + ) + + /** + * Creates a SRT connection from an URL + * + * @param url server url (syntax: srt://host:port?streamid=streamId&passphrase=passPhrase) + * @return SRT connection + */ + fun fromUrl(url: String): SrtConnection { + val uri = Uri.parse(url) + if (uri.scheme != SRT_SCHEME) { + throw InvalidParameterException("URL $url is not an srt URL") + } + val host = uri.host + ?: throw InvalidParameterException("Failed to parse URL $url: unknown host") + val port = uri.port + val streamId = uri.getQueryParameter(STREAM_ID_QUERY_PARAMETER) + val passPhrase = uri.getQueryParameter(PASS_PHRASE_QUERY_PARAMETER) + val unknownParameters = + uri.queryParameterNames.find { queryParameterList.contains(it).not() } + if (unknownParameters != null) { + throw InvalidParameterException("Failed to parse URL $url: unknown parameter(s): $unknownParameters") + } + return SrtConnection(host, port, streamId, passPhrase) + } + + /** + * Creates a SRT connection from an URL and given parameters. + * Query parameters are ignored. + * + * @param url server url (syntax: srt://host:port) + * @param streamId SRT stream ID + * @param passPhrase SRT passPhrase + * @return SRT connection + */ + fun fromUrlAndParameters( + url: String, + streamId: String? = null, + passPhrase: String? = null + ): SrtConnection { + val uri = Uri.parse(url) + if (uri.scheme != SRT_SCHEME) { + throw InvalidParameterException("URL $url is not an srt URL") + } + val host = uri.host + ?: throw InvalidParameterException("Failed to parse URL $url: unknown host") + val port = uri.port + + return SrtConnection(host, port, streamId, passPhrase) + } + } +} \ No newline at end of file diff --git a/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/internal/endpoints/SrtProducer.kt b/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/internal/endpoints/SrtProducer.kt index 32321456f..4708f55e7 100644 --- a/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/internal/endpoints/SrtProducer.kt +++ b/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/internal/endpoints/SrtProducer.kt @@ -15,7 +15,6 @@ */ package io.github.thibaultbee.streampack.ext.srt.internal.endpoints -import android.net.Uri import io.github.thibaultbee.srtdroid.Srt import io.github.thibaultbee.srtdroid.enums.Boundary import io.github.thibaultbee.srtdroid.enums.ErrorType @@ -25,6 +24,7 @@ import io.github.thibaultbee.srtdroid.listeners.SocketListener import io.github.thibaultbee.srtdroid.models.MsgCtrl import io.github.thibaultbee.srtdroid.models.Socket import io.github.thibaultbee.srtdroid.models.Stats +import io.github.thibaultbee.streampack.ext.srt.data.SrtConnection import io.github.thibaultbee.streampack.internal.data.Packet import io.github.thibaultbee.streampack.internal.data.SrtPacket import io.github.thibaultbee.streampack.internal.endpoints.ILiveEndpoint @@ -34,7 +34,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.net.ConnectException import java.net.InetSocketAddress -import java.security.InvalidParameterException class SrtProducer( private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO, @@ -45,13 +44,6 @@ class SrtProducer( private var bitrate = 0L private var isOnError = false - companion object { - private const val PAYLOAD_SIZE = 1316 - - private const val SRT_SCHEME = "srt" - private const val SRT_PREFIX = "$SRT_SCHEME://" - } - /** * Get/set SRT stream ID */ @@ -87,23 +79,10 @@ class SrtProducer( this.bitrate = config.toLong() } - override suspend fun connect(url: String) { - val uri = Uri.parse(url) - if (uri.scheme != SRT_SCHEME) { - throw InvalidParameterException("URL $url is not an srt URL") - } - uri.getQueryParameter("streamid")?.let { streamId = it } - uri.getQueryParameter("passphrase")?.let { passPhrase = it } - uri.getQueryParameter("latency")?.let { latency = it.toInt() } - uri.host?.let { connect(it, uri.port) } - ?: throw InvalidParameterException("Failed to parse URL $url: unknown host") - } + override suspend fun connect(url: String) = connect(SrtConnection.fromUrl(url)) - suspend fun connect(ip: String, port: Int) { + suspend fun connect(connection: SrtConnection) { withContext(coroutineDispatcher) { - if (ip.isBlank()) { - throw InvalidParameterException("Invalid IP $ip") - } try { socket.listener = object : SocketListener { override fun onConnectionLost( @@ -125,8 +104,12 @@ class SrtProducer( } socket.setSockFlag(SockOpt.PAYLOADSIZE, PAYLOAD_SIZE) socket.setSockFlag(SockOpt.TRANSTYPE, Transtype.LIVE) + + connection.streamId?.let { streamId = it } + connection.passPhrase?.let { passPhrase = it } + isOnError = false - socket.connect(ip.removePrefix(SRT_PREFIX), port) + socket.connect(connection.host, connection.port) onConnectionListener?.onSuccess() } catch (e: Exception) { socket = Socket() @@ -186,4 +169,8 @@ class SrtProducer( override fun release() { Srt.cleanUp() } -} \ No newline at end of file + + companion object { + private const val PAYLOAD_SIZE = 1316 + } +} diff --git a/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/streamers/AudioOnlySrtLiveStreamer.kt b/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/streamers/AudioOnlySrtLiveStreamer.kt index 29819ce0f..3863f63fb 100644 --- a/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/streamers/AudioOnlySrtLiveStreamer.kt +++ b/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/streamers/AudioOnlySrtLiveStreamer.kt @@ -16,6 +16,7 @@ package io.github.thibaultbee.streampack.ext.srt.streamers import android.content.Context +import io.github.thibaultbee.streampack.ext.srt.data.SrtConnection import io.github.thibaultbee.streampack.ext.srt.internal.endpoints.SrtProducer import io.github.thibaultbee.streampack.ext.srt.streamers.interfaces.ISrtLiveStreamer import io.github.thibaultbee.streampack.internal.muxers.ts.TSMuxer @@ -108,8 +109,24 @@ class AudioOnlySrtLiveStreamer( * @param port server port * @throws Exception if connection has failed or configuration has failed */ + @Deprecated( + "Use the new connect(SrtConnection) method", + replaceWith = ReplaceWith("connect(SrtConnection)") + ) override suspend fun connect(ip: String, port: Int) { - srtProducer.connect(ip, port) + val connection = SrtConnection(ip, port) + srtProducer.connect(connection) + } + + /** + * Connect to an SRT server with correct Live streaming parameters. + * To avoid creating an unresponsive UI, do not call on main thread. + * + * @param connection the SRT connection + * @throws Exception if connection has failed or configuration has failed + */ + override suspend fun connect(connection: SrtConnection) { + srtProducer.connect(connection) } /** @@ -121,8 +138,25 @@ class AudioOnlySrtLiveStreamer( * @param port server port * @throws Exception if connection has failed or configuration has failed or [startStream] has failed too. */ + @Deprecated( + "Use the new startStream(SrtConnection) method", + replaceWith = ReplaceWith("startStream(SrtConnection)") + ) override suspend fun startStream(ip: String, port: Int) { - connect(ip, port) + val connection = SrtConnection(ip, port) + startStream(connection) + } + + /** + * Connect to an SRT server and start stream. + * Same as calling [connect], then [startStream]. + * To avoid creating an unresponsive UI, do not call on main thread. + * + * @param connection the SRT connection + * @throws Exception if connection has failed or configuration has failed or [startStream] has failed too. + */ + override suspend fun startStream(connection: SrtConnection) { + connect(connection) startStream() } } diff --git a/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/streamers/CameraSrtLiveStreamer.kt b/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/streamers/CameraSrtLiveStreamer.kt index bcafa2fac..d4ffe4cd1 100644 --- a/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/streamers/CameraSrtLiveStreamer.kt +++ b/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/streamers/CameraSrtLiveStreamer.kt @@ -17,6 +17,7 @@ package io.github.thibaultbee.streampack.ext.srt.streamers import android.content.Context import io.github.thibaultbee.streampack.data.BitrateRegulatorConfig +import io.github.thibaultbee.streampack.ext.srt.data.SrtConnection import io.github.thibaultbee.streampack.ext.srt.internal.endpoints.SrtProducer import io.github.thibaultbee.streampack.ext.srt.regulator.srt.SrtBitrateRegulator import io.github.thibaultbee.streampack.ext.srt.streamers.interfaces.ISrtLiveStreamer @@ -140,28 +141,44 @@ class CameraSrtLiveStreamer( * @param port server port * @throws Exception if connection has failed or configuration has failed */ + @Deprecated( + "Use the new connect(SrtConnection) method", + replaceWith = ReplaceWith("connect(SrtConnection)") + ) override suspend fun connect(ip: String, port: Int) { - srtProducer.connect(ip, port) + val connection = SrtConnection(ip, port) + srtProducer.connect(connection) + } + + /** + * Connect to an SRT server with correct Live streaming parameters. + * To avoid creating an unresponsive UI, do not call on main thread. + * + * @param connection the SRT connection + * @throws Exception if connection has failed or configuration has failed + */ + override suspend fun connect(connection: SrtConnection) { + srtProducer.connect(connection) } /** * Same as [BaseCameraLiveStreamer.startStream] but also starts bitrate regulator. */ override suspend fun startStream() { + super.startStream() if (bitrateRegulator != null) { scheduler.start() } - super.startStream() } /** * Same as [BaseCameraLiveStreamer.startStream] but also starts bitrate regulator. */ override suspend fun startStream(url: String) { + super.startStream(url) if (bitrateRegulator != null) { scheduler.start() } - super.startStream(url) } /** @@ -173,8 +190,25 @@ class CameraSrtLiveStreamer( * @param port server port * @throws Exception if connection has failed or configuration has failed or [startStream] has failed too. */ + @Deprecated( + "Use the new startStream(SrtConnection) method", + replaceWith = ReplaceWith("startStream(SrtConnection)") + ) override suspend fun startStream(ip: String, port: Int) { - connect(ip, port) + val connection = SrtConnection(ip, port) + startStream(connection) + } + + /** + * Connect to an SRT server and start stream. + * Same as calling [connect], then [startStream]. + * To avoid creating an unresponsive UI, do not call on main thread. + * + * @param connection the SRT connection + * @throws Exception if connection has failed or configuration has failed or [startStream] has failed too. + */ + override suspend fun startStream(connection: SrtConnection) { + connect(connection) startStream() } diff --git a/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/streamers/ScreenRecorderSrtLiveStreamer.kt b/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/streamers/ScreenRecorderSrtLiveStreamer.kt index a6f103877..94847c1a4 100644 --- a/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/streamers/ScreenRecorderSrtLiveStreamer.kt +++ b/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/streamers/ScreenRecorderSrtLiveStreamer.kt @@ -18,6 +18,7 @@ package io.github.thibaultbee.streampack.ext.srt.streamers import android.app.Service import android.content.Context import io.github.thibaultbee.streampack.data.BitrateRegulatorConfig +import io.github.thibaultbee.streampack.ext.srt.data.SrtConnection import io.github.thibaultbee.streampack.ext.srt.internal.endpoints.SrtProducer import io.github.thibaultbee.streampack.ext.srt.regulator.srt.SrtBitrateRegulator import io.github.thibaultbee.streampack.ext.srt.services.ScreenRecorderSrtLiveService @@ -139,35 +140,51 @@ class ScreenRecorderSrtLiveStreamer( } /** - * Connect to an SRT server. + * Connect to an SRT server with correct Live streaming parameters. * To avoid creating an unresponsive UI, do not call on main thread. * * @param ip server ip * @param port server port * @throws Exception if connection has failed or configuration has failed */ + @Deprecated( + "Use the new connect(SrtConnection) method", + replaceWith = ReplaceWith("connect(SrtConnection)") + ) override suspend fun connect(ip: String, port: Int) { - srtProducer.connect(ip, port) + val connection = SrtConnection(ip, port) + srtProducer.connect(connection) + } + + /** + * Connect to an SRT server with correct Live streaming parameters. + * To avoid creating an unresponsive UI, do not call on main thread. + * + * @param connection the SRT connection + * @throws Exception if connection has failed or configuration has failed + */ + override suspend fun connect(connection: SrtConnection) { + srtProducer.connect(connection) } /** * Same as [BaseScreenRecorderLiveStreamer.startStream] but also starts bitrate regulator. */ override suspend fun startStream() { + super.startStream() if (bitrateRegulator != null) { scheduler.start() } - super.startStream() } /** * Same as [BaseScreenRecorderLiveStreamer.startStream] but also starts bitrate regulator. */ override suspend fun startStream(url: String) { + super.startStream(url) if (bitrateRegulator != null) { scheduler.start() } - super.startStream(url) } /** @@ -179,8 +196,25 @@ class ScreenRecorderSrtLiveStreamer( * @param port server port * @throws Exception if connection has failed or configuration has failed or [startStream] has failed too. */ + @Deprecated( + "Use the new startStream(SrtConnection) method", + replaceWith = ReplaceWith("startStream(SrtConnection)") + ) override suspend fun startStream(ip: String, port: Int) { - connect(ip, port) + val connection = SrtConnection(ip, port) + startStream(connection) + } + + /** + * Connect to an SRT server and start stream. + * Same as calling [connect], then [startStream]. + * To avoid creating an unresponsive UI, do not call on main thread. + * + * @param connection the SRT connection + * @throws Exception if connection has failed or configuration has failed or [startStream] has failed too. + */ + override suspend fun startStream(connection: SrtConnection) { + connect(connection) startStream() } diff --git a/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/streamers/interfaces/ISrtLiveStreamer.kt b/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/streamers/interfaces/ISrtLiveStreamer.kt index 7ba8ddd75..0d463f842 100644 --- a/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/streamers/interfaces/ISrtLiveStreamer.kt +++ b/extensions/srt/src/main/java/io/github/thibaultbee/streampack/ext/srt/streamers/interfaces/ISrtLiveStreamer.kt @@ -15,6 +15,7 @@ */ package io.github.thibaultbee.streampack.ext.srt.streamers.interfaces +import io.github.thibaultbee.streampack.ext.srt.data.SrtConnection import io.github.thibaultbee.streampack.streamers.interfaces.ILiveStreamer interface ISrtLiveStreamer : ILiveStreamer { @@ -40,8 +41,20 @@ interface ISrtLiveStreamer : ILiveStreamer { * @param port server port * @throws Exception if connection has failed or configuration has failed */ + @Deprecated( + "Use connect(SrtConnection) instead", + replaceWith = ReplaceWith("connect(SrtConnection)") + ) suspend fun connect(ip: String, port: Int) + /** + * Connect to a remote server. + * + * @param connection the SRT connection + * @throws Exception if connection has failed or configuration has failed + */ + suspend fun connect(connection: SrtConnection) + /** * Same as [connect] then [startStream]. * @@ -49,5 +62,17 @@ interface ISrtLiveStreamer : ILiveStreamer { * @param port server port * @throws Exception if connection has failed or configuration has failed or startStream failed. */ + @Deprecated( + "Use startStream(SrtConnection) instead", + replaceWith = ReplaceWith("startStream(SrtConnection)") + ) suspend fun startStream(ip: String, port: Int) + + /** + * Same as [connect] then [startStream]. + * + * @param connection the SRT connection + * @throws Exception if connection has failed or configuration has failed or startStream failed. + */ + suspend fun startStream(connection: SrtConnection) } \ No newline at end of file