Search code examples
androidvideo-streamingsrt

How to play a SRT (Secure Reliable Transfer) in android?


I was wondering if it's possible to play a video using the Secure Reliable Transfer protocol. Videos of this type start with srt://. I know that VideoView only supports https and another protocol I don't remember but not srt.

Any attempt at googling this yields results concerning srt subtitle files, which isn't what I am looking for.

Does anyone know how one might play an srt video on android?


Solution

  • If anyone comes here looking for an answer, here's what I did to get it working. My approach was for a live stream but it can be modified to work with single file type videos.

    I managed to implement a custom ExoPlayer DataSource that works with live streaming SRT.

    Uses srtdroid library

    Factory

    class SrtLiveStreamDataSourceFactory(
        private val srtUrl: String,
        private val port: Int,
        private val passPhrase: String? = null
    ) :
        DataSource.Factory {
        override fun createDataSource(): DataSource {
            return SrtLiveStreamDataSource(srtUrl, port, passPhrase)
        }
    }
    

    DataSource

    const val PAYLOAD_SIZE = 1316
    
    
    class SrtLiveStreamDataSource(
        private val srtUrl: String,
        private val port: Int,
        private val passPhrase: String?
    
    ) :
        BaseDataSource(/*isNetwork*/true) {
    
        private var socket: Socket? = null
        private val byteQueue: Queue<Byte> = LinkedList()
    
    
        override fun open(dataSpec: DataSpec): Long {
            socket = Socket()
            socket?.setSockFlag(SockOpt.TRANSTYPE, Transtype.LIVE)
            socket?.setSockFlag(SockOpt.PAYLOADSIZE, PAYLOAD_SIZE)
            if(passPhrase != null){
                socket?.setSockFlag(SockOpt.PASSPHRASE, passPhrase)
            }
            socket?.connect(srtUrl, port)
            return C.LENGTH_UNSET.toLong()
        }
    
    
        /**
         * Receives from SRT socket and feeds into a queue. Depending on the length requested
         * from exoplayer, that amount of bytes is polled from queue and onto the buffer with the given offset.
         *
         * You cannot directly receive at the given length from the socket, because SRT uses a
         * predetermined payload size that cannot be dynamic
         */
        override fun read(buffer: ByteArray, offset: Int, length: Int): Int {
            if (length == 0) {
                return 0
            }
            var bytesReceived = 0
            if (socket != null) {
                val received = socket!!.recv(PAYLOAD_SIZE)
                for (byte in received.second /*received byte array*/) {
                    byteQueue.offer(byte)
                }
                repeat(length) { index ->
                    val byte = byteQueue.poll()
                    if (byte != null) {
                        buffer[index + offset] = byte
                        bytesReceived++
                    }
                }
                return bytesReceived
            }
            throw IOException("Couldn't read bytes at offset: $offset")
        }
    
        override fun getUri(): Uri? {
            return Uri.parse("srt://$srtUrl:$port")
        }
    
        override fun close() {
            socket?.close()
            socket = null
        }
    }
    

    Simple usage

    class MainActivity : AppCompatActivity() {
    
        private lateinit var binding: ActivityMainBinding
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            binding = ActivityMainBinding.inflate(layoutInflater)
            setContentView(binding.root)
    
    
            val url = SRT_URL_HERE (exclude srt://)
            val port = PORT_HERE
    
            val source = ProgressiveMediaSource.Factory(
                SrtLiveStreamDataSourceFactory(
                    url,
                    port,
                ),
            ).createMediaSource(MediaItem.fromUri(Uri.EMPTY))
    
    
            val player = ExoPlayer.Builder(this)
                .build()
            player.setMediaSource(source)
            binding.playerView.player = player
    
    
            player.prepare()
            player.play()
            player.playWhenReady = true
    
        }
    }