Alireza Peer
Alireza Peer

Reputation: 908

WebRTC: Play MediaStream AudioTrack

code when remote stream received for videoCall:

/**
 * Received remote peer's media stream. we will get the first video track and render it
 */
private void gotRemoteStream(MediaStream stream) {
    LogUtil.prependCallLocation("REMOTE STREAM GOTEN");
    //we have remote video stream. add to the renderer.

    final VideoTrack videoTrack = stream.videoTracks.get(0);
    runOnUiThread(() -> {
        try {
            videoTrack.addSink(remoteVideoView);
        } catch (Exception e) {
            e.printStackTrace();
            LogUtil.prependCallLocation("ERROR HAPPEND HERE");
        }
    });

}

Now I am sending JUST audiotrack to a peer, so I want to play that audioTrack. (I want to implement voice call only with no video streaming)

new Code,

private void gotRemoteStream(MediaStream stream) {
    LogUtil.prependCallLocation("REMOTE STREAM GOTEN");

    //Im getting Audio Track of the stream and i should play it somehow
    final AudioTrack audioTrack = stream.audioTracks.get(0);
    runOnUiThread(() -> {
        try {

        } catch (Exception e) {
            e.printStackTrace();
            LogUtil.prependCallLocation("ERROR HAPPEND HERE");
        }
    });
}

How can I play the audioTrack on local?

Upvotes: 1

Views: 2808

Answers (1)

Alireza Peer
Alireza Peer

Reputation: 908

Solved.

If anyone has the same problem. WebRTC handles the audio stream itself and you don't have to try to play it.

so, if you want to just create a Voice call, remove all video rendering related requests and it works.

keep in mind when creating offer, you have to remove "OfferToReceiveVideo" from MediaConstraints.

(in my case that caused crash with no log error).

Complete AudioCall Activity class:

public class AudioCallActivity extends AppCompatActivity implements View.OnClickListener, SignallingClient.SignalingInterface {
    PeerConnectionFactory peerConnectionFactory;
    MediaConstraints audioConstraints;
    MediaConstraints sdpConstraints;
    AudioSource audioSource;
    AudioTrack localAudioTrack;


    Button hangup;
    PeerConnection localPeer;
    List<IceServer> iceServers;

    boolean gotUserMedia;


    private boolean hangedUp;

    BlastVisualizer mVisualizer;

    List<PeerConnection.IceServer> peerIceServers = new ArrayList<>();

    AudioManager audioManager;
    private String roomName;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_audio_call);

        initViews();

        getIceServers();

        roomName = Objects.requireNonNull(getIntent().getExtras()).getString("roomName",null);
        Toast.makeText(this, "roomName = " + roomName, Toast.LENGTH_SHORT).show();

    }


    private void initViews() {
        hangup = findViewById(R.id.end_call);
        hangup.setOnClickListener(this);
    }


    private void getIceServers() {
        hangedUp = false;
        //get Ice servers using xirsys
        byte[] data;
        data = ("yourICEServerAuthKey").getBytes(StandardCharsets.UTF_8);
        String authToken = "Basic " + Base64.encodeToString(data, Base64.NO_WRAP);
        Utils.getInstance().getRetrofitInstance().getIceCandidates(authToken).enqueue(new Callback<TurnServerPojo>() {
            @Override
            public void onResponse(@NonNull Call<TurnServerPojo> call, @NonNull Response<TurnServerPojo> response) {
                TurnServerPojo body = response.body();
                if (body != null) {
                    iceServers = body.iceServerList.iceServers;
                }
                // Toast.makeText(VideoCallActivity.this, "Ice Servers Received", Toast.LENGTH_SHORT).show();
                for (IceServer iceServer : iceServers) {
                    if (iceServer.credential == null) {
                        PeerConnection.IceServer peerIceServer =
                                PeerConnection
                                        .IceServer
                                        .builder(iceServer.url)
                                        .createIceServer();
                        peerIceServers
                                .add(peerIceServer);
                    } else {
                        PeerConnection.IceServer peerIceServer =
                                PeerConnection.IceServer.builder(iceServer.url)
                                        .setUsername(iceServer.username)
                                        .setPassword(iceServer.credential)
                                        .createIceServer();
                        peerIceServers.add(peerIceServer);
                    }
                }
                SignallingClient.getInstance().init(AudioCallActivity.this,roomName);

                start();
            }

            @Override
            public void onFailure(@NonNull Call<TurnServerPojo> call, @NonNull Throwable t) {
                t.printStackTrace();
                LogUtil.prependCallLocation("On Failure" + t);

            }
        });
    }


    public void start() {
        //Initialize PeerConnectionFactory globals.
        PeerConnectionFactory.InitializationOptions initializationOptions =
                PeerConnectionFactory.InitializationOptions.builder(this)
                        .createInitializationOptions();
        PeerConnectionFactory.initialize(initializationOptions);

        //Create a new PeerConnectionFactory instance - using Hardware encoder and decoder.
        PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
        peerConnectionFactory = PeerConnectionFactory.builder()
                .setOptions(options)
                .createPeerConnectionFactory();


        //Create MediaConstraints - Will be useful for specifying video and audio constraints.
        audioConstraints = new MediaConstraints();

        //create an AudioSource instance
        audioSource = peerConnectionFactory.createAudioSource(audioConstraints);
        localAudioTrack = peerConnectionFactory.createAudioTrack("101", audioSource);


        //showToast("GotUserMedia Sets True");
        gotUserMedia = true;
        if (SignallingClient.getInstance().isInitiator) {
            onTryToStart();
        }
    }


    /**
     * This method will be called directly by the app when it is the initiator and has got the local media
     * or when the remote peer sends a message through socket that it is ready to transmit AV data
     */
    @Override
    public void onTryToStart() {
        runOnUiThread(() -> {
            if (!SignallingClient.getInstance().isStarted && SignallingClient.getInstance().isChannelReady) {
                createPeerConnection();
                SignallingClient.getInstance().isStarted = true;
                if (SignallingClient.getInstance().isInitiator) {
                    doCall();
                }

            }
        });
    }


    /**
     * Creating the local peerconnection instance
     */
    private void createPeerConnection() {
        PeerConnection.RTCConfiguration rtcConfig =
                new PeerConnection.RTCConfiguration(peerIceServers);
        // TCP candidates are only useful when connecting to a server that supports
        // ICE-TCP.
        rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED;
        rtcConfig.bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE;
        rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE;
        rtcConfig.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY;
        // Use ECDSA encryption.
        rtcConfig.keyType = PeerConnection.KeyType.ECDSA;
        localPeer = peerConnectionFactory.createPeerConnection(rtcConfig, new CustomPeerConnectionObserver("localPeerCreation") {
            @Override
            public void onIceCandidate(IceCandidate iceCandidate) {
                super.onIceCandidate(iceCandidate);
                onIceCandidateReceived(iceCandidate);
            }
            @Override
            public void onAddStream(MediaStream mediaStream) {

                //showToast("Received Remote stream");
                super.onAddStream(mediaStream);
                audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
                audioManager.setSpeakerphoneOn(false);
            }
        });

        addStreamToLocalPeer();
    }

    /**
     * Adding the stream to the localpeer
     */
    private void addStreamToLocalPeer() {
        MediaStream stream = peerConnectionFactory.createLocalMediaStream("101");
        stream.addTrack(localAudioTrack);
        localPeer.addStream(stream);
    }

    /**
     * This method is called when the app is initiator - We generate the offer and send it over through socket
     * to remote peer
     */
    private void doCall() {
        sdpConstraints = new MediaConstraints();
        sdpConstraints.mandatory.add(
                new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
        localPeer.createOffer(new CustomSdpObserver("localCreateOffer") {
            @Override
            public void onCreateSuccess(SessionDescription sessionDescription) {
                super.onCreateSuccess(sessionDescription);
                localPeer.setLocalDescription(new CustomSdpObserver("localSetLocalDesc"), sessionDescription);
                LogUtil.prependCallLocation("SignallingClient emit ");
                SignallingClient.getInstance().emitMessage(sessionDescription);
            }
        }, sdpConstraints);
    }



    /**
     * Received local ice candidate. Send it to remote peer through signalling for negotiation
     */
    public void onIceCandidateReceived(IceCandidate iceCandidate) {
        //we have received ice candidate. We can set it to the other peer.
        SignallingClient.getInstance().emitIceCandidate(iceCandidate);
    }

    /**
     * SignallingCallback - called when the room is created - i.e. you are the initiator
     */
    @Override
    public void onCreatedRoom() {
        if (gotUserMedia) {
            SignallingClient.getInstance().emitMessage("got user media");
        }
    }

    /**
     * SignallingCallback - called when you join the room - you are a participant
     */
    @Override
    public void onJoinedRoom() {
        if (gotUserMedia) {
            SignallingClient.getInstance().emitMessage("got user media");
        }
    }

    @Override
    public void onNewPeerJoined() {
        showToast("Remote Peer Joined");
    }

    @Override
    public void onHangupReqReceived(String msg) {
        runOnUiThread(this::hangup);
    }

    /**
     * SignallingCallback - Called when remote peer sends offer
     */
    @Override
    public void onOfferReceived(final JSONObject data) {
        //showToast("Received Offer");
        runOnUiThread(() -> {
            if (!SignallingClient.getInstance().isInitiator && !SignallingClient.getInstance().isStarted) {
                onTryToStart();
            }

            try {
                localPeer.setRemoteDescription(new CustomSdpObserver("localSetRemote"), new SessionDescription(SessionDescription.Type.OFFER, data.getString("sdp")));
                doAnswer();
            } catch (JSONException e) {
                e.printStackTrace();
            }
        });
    }

    private void doAnswer() {
        localPeer.createAnswer(new CustomSdpObserver("localCreateAns") {
            @Override
            public void onCreateSuccess(SessionDescription sessionDescription) {
                super.onCreateSuccess(sessionDescription);
                localPeer.setLocalDescription(new CustomSdpObserver("localSetLocal"), sessionDescription);
                SignallingClient.getInstance().emitMessage(sessionDescription);
            }
        }, new MediaConstraints());
    }

    /**
     * SignallingCallback - Called when remote peer sends answer to your offer
     */

    @Override
    public void onAnswerReceived(JSONObject data) {
        // showToast("Received Answer");
        try {
            localPeer.setRemoteDescription(new CustomSdpObserver("localSetRemote"), new SessionDescription(SessionDescription.Type.fromCanonicalForm(data.getString("type").toLowerCase()), data.getString("sdp")));
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    /**
     * Remote IceCandidate received
     */
    @Override
    public void onIceCandidateReceived(JSONObject data) {
        try {
            localPeer.addIceCandidate(new IceCandidate(data.getString("id"), data.getInt("label"), data.getString("candidate")));
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    /**
     * Closing up - normal hangup and app destroye
     */

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.end_call) {
            sendHangUpReq();
        }
    }
    private void sendHangUpReq() {
        SignallingClient.getInstance().sendHangupReq();
    }

    private void hangup() {
        if(hangedUp)return;
        hangedUp = true;
        try {
            if(localPeer != null)
                localPeer.close();
            localPeer= null;

            SignallingClient.getInstance().close();

            finish();

        } catch (Exception e) {
            Log.e("Exception Happend","ExceptionHappend");
            e.printStackTrace();
        }

    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        hangup();
        finish();

    }

    public void showToast(final String msg) {
        runOnUiThread(() -> Toast.makeText(AudioCallActivity.this, msg, Toast.LENGTH_SHORT).show());
    }

}

Upvotes: 4

Related Questions