Qandeel Abbassi
Qandeel Abbassi

Reputation: 999

how to play video stream from websocket url on exoplayer?

I am trying to stream a video from websocket url in android. The url looks something like this ws://abc.com:80/api/streaming/download/mp4/ . I am not really sure how to proceed because this thing is new for me. I tried searching on the internet and i found only one solution on stackoverflow which says to connect with websocket using okhttp and then use okhttpdatasource with Exoplayer. So I tried connecting to websocket url using okhttp and i am successfully recieving bytestream. Here is the code:

public void startListen() {
    //   Request request = new Request.Builder().url(mContext.getResources().getString(R.string.ws_url)).build();
    Request request = new Request.Builder().url(getApplicationContext().getResources().getString(R.string.myUrl)).build();
    //final ByteBuffer buf = ByteBuffer.allocate(MediaBlockSize);
    mWebSocket = mOkHttpClient.newWebSocket(request, new WebSocketListener() {
        @Override
        public void onOpen(WebSocket webSocket, Response response) {
            super.onOpen(webSocket, response);
            Log.d("MNMN", String.valueOf(response));

        }

        @Override
        public void onMessage(WebSocket webSocket, String text) {
            super.onMessage(webSocket, text);
            Log.d("MNMN", "text = " + text);

        }

        @Override
        public void onMessage(WebSocket webSocket, ByteString bytes) {
            super.onMessage(webSocket, bytes);
            //Log.d("MNMNx", "size = " + bytes.size());
            final byte b[] = bytes.toByteArray();
            try {
                mOutputStream.write(b);

            } catch (IOException e) {
                e.printStackTrace();
            }

        }

        @Override
        public void onClosing(WebSocket webSocket, int code, String reason) {
            super.onClosing(webSocket, code, reason);
        }

        @Override
        public void onClosed(WebSocket webSocket, int code, String reason) {
            super.onClosed(webSocket, code, reason);
        }

        @Override
        public void onFailure(WebSocket webSocket, Throwable t, Response response) {
            super.onFailure(webSocket, t, response);
            Log.d("MNMN", String.valueOf(response));
        }
    });
}

But i don't really know how to make it work with Exoplayer. Exoplayer has extension for okhttpdatasource but i didn't find any good tutorial of using it. Can someone guide me how can I use stream received from okhttp with exoplayer to play the video?

Upvotes: 2

Views: 2716

Answers (1)

Suryavel TR
Suryavel TR

Reputation: 3786

I know its late to answer. But in case someone else looking for this.

I had similar situation. I don't think OkHttpDataSource is meant for wss.

I came up with my own Exo DataSource that works as expected.

  1. First collect the data received from wss using OkHttp's WebSocketListener
class WssDataStreamCollector @Inject constructor() : WebSocketListener() {
    private val wssData = ConcurrentSkipListSet<ByteString>()

    override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
        wssData.add(bytes)
    }

    override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
        super.onClosing(webSocket, code, reason)
        wssData.removeAll(wssData)
    }

    fun canStream(): Boolean {
        return wssData.size > 0
    }

    fun getNextStream(): ByteString {
        return wssData.pollFirst()
    }
}
  1. Create the DataSource
class WssStreamDataSource : BaseDataSource(true) {

    @Inject
    lateinit var httpClient: OkHttpClient

    @Inject
    lateinit var dataStreamCollector: WssDataStreamCollector

    var webSocketClient: WebSocket? = null

    private var currentByteStream: ByteArray? = null
    private var currentPosition = 0;
    private var remainingBytes = 0;

    override fun open(dataSpec: DataSpec): Long {
        // Form the request and open the socket.
        // Provide the listener
        // which collects the data for us (Previous class).
        webSocketClient = httpClient.newWebSocket(
            Request.Builder().apply {
                    dataSpec.httpRequestHeaders.forEach { entry ->
                        addHeader(entry.key, entry.value)
                    }
                }.url(dataSpec.uri.toString()).build(),
            dataStreamCollector
        )

        return -1 // Return -1 as the size is unknown (streaming)
    }

    override fun getUri(): Uri? {
        webSocketClient?.request()?.url?.let {
            return Uri.parse(it.toString())
        }

        return null
    }

    override fun read(target: ByteArray, offset: Int, length: Int): Int {
        // return 0 (nothing read) when no data present...
        if (currentByteStream == null && !dataStreamCollector.canStream()) {
            return 0
        }

        // parse one (data) ByteString at a time.
        // reset the current position and remaining bytes 
        // for every new data
        if (currentByteStream == null) {
            currentByteStream = dataStreamCollector.getNextStream().toByteArray()
            currentPosition = 0
            remainingBytes = currentByteStream?.size ?: 0
        }

        val readSize = min(length, remainingBytes)

        currentByteStream?.copyInto(target, offset, currentPosition, currentPosition + readSize)
        currentPosition += readSize
        remainingBytes -= readSize

        // once the data is read set currentByteStream to null
        // so the next data would be collected to process in next
        // iteration.
        if (remainingBytes == 0) {
            currentByteStream = null
        }

        return readSize
    }

    override fun close() {
        // close the socket and relase the resources
        webSocketClient?.cancel()
    }

    // Factory class for DataSource
    class Factory : DataSource.Factory {
        override fun createDataSource(): DataSource = WssStreamDataSource()
    }
}

That's all, you are good to go.

  1. Now use ProgressiveMediaSource with the DataSource we created like below.
SimpleExoPlayer(yourBuilder).apply {
    setVideoSurfaceView(surfaceView)
    val mediaItem = MediaItem.fromUri(uri) // URI with wss://
    val factory = ProgressiveMediaSource.Factory(WssStreamDataSource.Factory())
    addMediaSource(factory.createMediaSource(mediaItem))
    prepare()
    playWhenReady = true
    play()
}

Upvotes: 2

Related Questions