1+ package io.github.thibaultbee.krtmp.rtmp
2+
3+ import io.github.thibaultbee.krtmp.rtmp.client.RtmpClient
4+ import io.github.thibaultbee.krtmp.rtmp.client.RtmpClientCallbackBuilder
5+ import io.github.thibaultbee.krtmp.rtmp.client.RtmpClientSettings
6+ import io.github.thibaultbee.krtmp.rtmp.extensions.clientHandshake
7+ import io.github.thibaultbee.krtmp.rtmp.extensions.isSecureRtmp
8+ import io.github.thibaultbee.krtmp.rtmp.extensions.isTunneledRtmp
9+ import io.github.thibaultbee.krtmp.rtmp.extensions.validateRtmp
10+ import io.github.thibaultbee.krtmp.rtmp.server.RtmpServer
11+ import io.github.thibaultbee.krtmp.rtmp.server.RtmpServerCallbackBuilder
12+ import io.github.thibaultbee.krtmp.rtmp.server.RtmpServerSettings
13+ import io.github.thibaultbee.krtmp.rtmp.util.RtmpURLBuilder
14+ import io.github.thibaultbee.krtmp.rtmp.util.RtmpURLProtocol
15+ import io.github.thibaultbee.krtmp.rtmp.util.extensions.startWithScheme
16+ import io.github.thibaultbee.krtmp.rtmp.util.sockets.ISocket
17+ import io.github.thibaultbee.krtmp.rtmp.util.sockets.http.HttpSocket
18+ import io.github.thibaultbee.krtmp.rtmp.util.sockets.tcp.TcpSocket
19+ import io.ktor.http.URLBuilder
20+ import io.ktor.http.Url
21+ import io.ktor.network.selector.SelectorManager
22+ import io.ktor.network.sockets.InetSocketAddress
23+ import io.ktor.network.sockets.ServerSocket
24+ import io.ktor.network.sockets.SocketAddress
25+ import io.ktor.network.sockets.SocketOptions
26+ import io.ktor.network.sockets.aSocket
27+ import io.ktor.network.tls.tls
28+
29+ /* *
30+ * Builder class for creating RTMP connections, both clients and servers.
31+ *
32+ * @param selectorManager the [SelectorManager] to use for socket operations
33+ */
34+ class RtmpConnectionBuilder (val selectorManager : SelectorManager ) {
35+ private val tcpSocketBuilder = aSocket(selectorManager).tcp()
36+
37+ /* *
38+ * The socket options used for TCP connections.
39+ */
40+ val socketOptions: SocketOptions
41+ get() = tcpSocketBuilder.options
42+
43+ /* *
44+ * Connects to the given [urlBuilder] and performs the RTMP handshake.
45+ *
46+ * The [urlBuilder] must use the `rtmp`, `rtmps`, `rtmpt` or `rtmpts` protocol.
47+ *
48+ * Don't forget to call [RtmpClient.connect] after this to complete the RTMP connection.
49+ *
50+ * @param urlBuilder the URL to connect to
51+ * @param configure the settings for the RTMP client
52+ * @param message the callback to handle RTMP client events
53+ */
54+ suspend fun connect (
55+ urlBuilder : URLBuilder ,
56+ configure : RtmpClientSettings .() -> Unit = {},
57+ message : RtmpClientCallbackBuilder .() -> Unit = {}
58+ ): RtmpClient {
59+ urlBuilder.validateRtmp()
60+ val socket = if (urlBuilder.protocol.isTunneledRtmp) {
61+ HttpSocket (urlBuilder)
62+ } else {
63+ val tcpSocket = tcpSocketBuilder.connect(urlBuilder.host, urlBuilder.port).apply {
64+ if (urlBuilder.protocol.isSecureRtmp) {
65+ tls(selectorManager.coroutineContext)
66+ }
67+ }
68+ TcpSocket (tcpSocket, urlBuilder)
69+ }
70+
71+ return connect(socket, configure, message)
72+ }
73+
74+ /* *
75+ * Connects to the given [socket] and performs the RTMP handshake.
76+ */
77+ private suspend fun connect (
78+ socket : ISocket ,
79+ configure : RtmpClientSettings .() -> Unit ,
80+ message : RtmpClientCallbackBuilder .() -> Unit
81+ ): RtmpClient {
82+ val settings = RtmpClientSettings ().apply { configure() }
83+ try {
84+ socket.clientHandshake(settings.clock)
85+ } catch (t: Throwable ) {
86+ socket.close()
87+ throw t
88+ }
89+ return RtmpClient (
90+ socket,
91+ settings,
92+ RtmpClientCallbackBuilder ().apply { message() }
93+ )
94+ }
95+
96+ /* *
97+ * Binds a new [RtmpServer] to the given [localAddress].
98+ *
99+ * @param localAddress the local address to bind to. If null, binds to a random port on all interfaces.
100+ * @param configure the settings for the RTMP server
101+ * @param message the callback to handle RTMP server events
102+ * @return a new [RtmpServer] instance
103+ */
104+ suspend fun bind (
105+ localAddress : SocketAddress ? = null,
106+ configure : RtmpServerSettings .() -> Unit = {},
107+ message : RtmpServerCallbackBuilder .() -> Unit = {}
108+ ): RtmpServer {
109+ val serverSocket = tcpSocketBuilder.bind(localAddress)
110+
111+ return bind(serverSocket, configure, message)
112+ }
113+
114+ /* *
115+ * Binds a new [RtmpServer] to the given [serverSocket].
116+ */
117+ private fun bind (
118+ serverSocket : ServerSocket ,
119+ settings : RtmpServerSettings .() -> Unit ,
120+ messages : RtmpServerCallbackBuilder .() -> Unit
121+ ) = RtmpServer (
122+ serverSocket,
123+ RtmpServerSettings ().apply { settings() },
124+ RtmpServerCallbackBuilder ().apply { messages() }
125+ )
126+ }
127+
128+ /* *
129+ * Connects to the given [urlString] and performs the RTMP handshake.
130+ *
131+ * The [urlString] must use the `rtmp`, `rtmps`, `rtmpt` or `rtmpts` protocol.
132+ *
133+ * Don't forget to call [RtmpClient.connect] after this to complete the RTMP connection.
134+ *
135+ * @param urlString the RTMP URL to connect to
136+ * @param configure the settings for the RTMP client
137+ * @param message the callback to handle RTMP client events
138+ */
139+ suspend fun RtmpConnectionBuilder.connect (
140+ urlString : String ,
141+ configure : RtmpClientSettings .() -> Unit = {},
142+ message : RtmpClientCallbackBuilder .() -> Unit = {}
143+ ) = connect(RtmpURLBuilder (urlString), configure, message)
144+
145+ /* *
146+ * Binds a new [RtmpServer] to the given [urlString].
147+ *
148+ * The [urlString] must be in the format `tcp://host:port` or `host:port`.
149+ *
150+ * @param urlString the URL string to bind to
151+ * @param configure the settings for the RTMP server
152+ * @param message the callback to handle RTMP server events
153+ * @return a new [RtmpServer] instance
154+ */
155+ suspend fun RtmpConnectionBuilder.bind (
156+ urlString : String ,
157+ configure : RtmpServerSettings .() -> Unit = {},
158+ message : RtmpServerCallbackBuilder .() -> Unit = {}
159+ ): RtmpServer {
160+ val url = if (urlString.startWithScheme()) {
161+ Url (urlString)
162+ } else {
163+ Url (" rtmp://$urlString " )
164+ }
165+ val localAddress = InetSocketAddress (
166+ url.host, if (url.specifiedPort == 0 ) {
167+ RtmpURLProtocol .createOrDefault(url.protocol.name).defaultPort
168+ } else {
169+ url.port
170+ }
171+ )
172+ return bind(localAddress, configure, message)
173+ }
0 commit comments